improve detail screen

This commit is contained in:
EthanShoeDev
2025-09-22 17:40:12 -04:00
parent ec3e0a5ded
commit 9433a2816d
8 changed files with 157 additions and 175 deletions

View File

@@ -21,7 +21,7 @@ import {
type InputConnectionDetails,
} from '@/lib/secrets-manager';
import { useTheme } from '@/lib/theme';
import { useBottomTabPadding } from '@/lib/useBottomTabPadding';
import { useBottomTabSpacing } from '@/lib/useBottomTabSpacing';
export default function TabsIndex() {
return <Host />;
@@ -45,7 +45,7 @@ function Host() {
const sshConnMutation = useSshConnMutation({
onConnectionProgress: (s) => setLastConnectionProgressEvent(s),
});
const { paddingBottom, onLayout } = useBottomTabPadding(12);
const marginBottom = useBottomTabSpacing();
const connectionForm = useAppForm({
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
defaultValues,
@@ -86,10 +86,9 @@ function Host() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: theme.colors.background }}>
<ScrollView
contentContainerStyle={[{ paddingBottom }]}
contentContainerStyle={[{ marginBottom }]}
keyboardShouldPersistTaps="handled"
style={{ backgroundColor: theme.colors.background }}
onLayout={onLayout}
>
<View
style={[

View File

@@ -12,14 +12,15 @@ import {
useFocusEffect,
} from 'expo-router';
import React, { startTransition, useEffect, useRef, useState } from 'react';
import { Dimensions, Platform, Pressable, Text, View } from 'react-native';
import { Pressable, Text, View } from 'react-native';
import {
SafeAreaView,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
KeyboardAvoidingView,
KeyboardToolbar,
} from 'react-native-keyboard-controller';
import { useSshStore } from '@/lib/ssh-store';
import { useTheme } from '@/lib/theme';
import { useBottomTabSpacing } from '@/lib/useBottomTabSpacing';
export default function TabsShellDetail() {
const [ready, setReady] = useState(false);
@@ -104,37 +105,20 @@ function ShellDetail() {
};
}, [shell]);
const insets = useSafeAreaInsets();
const estimatedTabBarHeight = Platform.select({
ios: 49,
android: 80,
default: 56,
});
const windowH = Dimensions.get('window').height;
const computeBottomExtra = (y: number, height: number) => {
const extra = windowH - (y + height);
return extra > 0 ? extra : 0;
};
// Measure any bottom overlap (e.g., native tab bar) and add padding to avoid it
const [bottomExtra, setBottomExtra] = useState(0);
const marginBottom = useBottomTabSpacing();
return (
<SafeAreaView
edges={['left', 'right']}
onLayout={(e) => {
const { y, height } = e.nativeEvent.layout;
const extra = computeBottomExtra(y, height);
if (extra !== bottomExtra) setBottomExtra(extra);
}}
<>
<View
style={{
flex: 1,
justifyContent: 'flex-start',
backgroundColor: theme.colors.background,
paddingTop: 2,
paddingLeft: 8,
paddingRight: 8,
paddingBottom: insets.bottom + (bottomExtra || estimatedTabBarHeight),
paddingBottom: 0,
marginBottom,
flex: 1,
}}
>
<Stack.Screen
@@ -158,6 +142,11 @@ function ShellDetail() {
),
}}
/>
<KeyboardAvoidingView
behavior="height"
keyboardVerticalOffset={210}
style={{ flex: 1 }}
>
<XtermJsWebView
ref={xtermRef}
style={{ flex: 1 }}
@@ -228,6 +217,13 @@ function ShellDetail() {
});
}}
/>
</SafeAreaView>
</KeyboardAvoidingView>
</View>
<KeyboardToolbar
offset={{
opened: -80,
}}
/>
</>
);
}

View File

@@ -9,6 +9,7 @@ import { useTheme } from '@/lib/theme';
export default function TabsLayout() {
const theme = useTheme();
return (
<NativeTabs
// common

View File

@@ -26,7 +26,7 @@ export default function ToolbarExample() {
>
<KeyboardAvoidingView
behavior="height"
keyboardVerticalOffset={150}
keyboardVerticalOffset={250}
style={{
flex: 1,
paddingHorizontal: 16,
@@ -49,7 +49,11 @@ export default function ToolbarExample() {
</View>
</KeyboardAvoidingView>
</View>
<KeyboardToolbar />
<KeyboardToolbar
offset={{
opened: -80,
}}
/>
</>
);
}
@@ -62,15 +66,21 @@ const TextInputAndLabel = (props: CustomTextInputProps) => {
const { title, ...rest } = props;
const [isFocused, setFocused] = useState(false);
const onFocus = useCallback<NonNullable<TextInputProps['onFocus']>>((e) => {
const onFocus = useCallback<NonNullable<TextInputProps['onFocus']>>(
(e) => {
setFocused(true);
props.onFocus?.(e);
}, []);
},
[props],
);
const onBlur = useCallback<NonNullable<TextInputProps['onBlur']>>((e) => {
const onBlur = useCallback<NonNullable<TextInputProps['onBlur']>>(
(e) => {
setFocused(false);
props.onBlur?.(e);
}, []);
},
[props],
);
return (
<>

View File

@@ -1,5 +1,5 @@
import { Redirect } from 'expo-router';
export default function RootRedirect() {
return <Redirect href="/(test)" />;
return <Redirect href="/(tabs)" />;
}

View File

@@ -1,36 +0,0 @@
import React from 'react';
import { Dimensions, Platform } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
type LayoutEvent = {
nativeEvent: {
layout: {
y: number;
height: number;
};
};
};
export function useBottomTabPadding(basePadding = 12) {
const insets = useSafeAreaInsets();
const windowH = Dimensions.get('window').height;
const estimatedTabBarHeight = Platform.select({
ios: 49,
android: 80,
default: 56,
});
const [bottomExtra, setBottomExtra] = React.useState(0);
const onLayout = React.useCallback(
(e: LayoutEvent) => {
const { y, height } = e.nativeEvent.layout;
const extra = windowH - (y + height);
setBottomExtra(extra > 0 ? extra : 0);
},
[windowH],
);
const paddingBottom =
basePadding + insets.bottom + (bottomExtra || estimatedTabBarHeight!);
return { paddingBottom, onLayout } as const;
}

View File

@@ -0,0 +1,12 @@
import { Platform } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
export function useBottomTabSpacing() {
const insets = useSafeAreaInsets();
const estimatedTabBarHeight = Platform.select({
ios: 49,
android: 80,
default: 56,
});
return insets.bottom + estimatedTabBarHeight;
}

View File

@@ -153,7 +153,7 @@ window.onload = () => {
window.addEventListener('message', handler);
// Initial handshake (send once)
setTimeout(() => sendToRn({ type: 'initialized' }), 50);
setTimeout(() => sendToRn({ type: 'initialized' }), 100);
} catch (e) {
sendToRn({
type: 'debug',