detail screen changes

This commit is contained in:
EthanShoeDev
2025-09-16 11:18:13 -04:00
parent 60c7c57bed
commit 4413566fec
6 changed files with 109 additions and 97 deletions

View File

@@ -58,6 +58,7 @@
"react-dom": "19.1.0",
"react-native": "0.81.4",
"react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "1.18.5",
"react-native-mmkv": "^3.3.1",
"react-native-reanimated": "~4.1.0",
"react-native-safe-area-context": "~5.6.1",

View File

@@ -36,6 +36,8 @@ function ShellDetail() {
: undefined;
const [shellData, setShellData] = useState('');
const [inputValue, setInputValue] = useState('');
const hiddenInputRef = useRef<TextInput | null>(null);
useEffect(() => {
if (!connection) return;
@@ -59,12 +61,29 @@ function ShellDetail() {
scrollViewRef.current?.scrollToEnd({ animated: true });
}, [shellData]);
useEffect(() => {
const focusTimeout = setTimeout(() => {
hiddenInputRef.current?.focus();
}, 0);
return () => clearTimeout(focusTimeout);
}, []);
async function sendChunk(chunk: string) {
if (!shell || !chunk) return;
const bytes = Uint8Array.from(new TextEncoder().encode(chunk)).buffer;
try {
await shell.sendData(bytes);
} catch {}
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: theme.colors.background }}>
<Stack.Screen
options={{
headerBackVisible: true,
headerLeft: () => (
headerLeft:
Platform.OS === 'android'
? () => (
<Pressable
onPress={() => router.back()}
hitSlop={10}
@@ -76,9 +95,12 @@ function ShellDetail() {
color={theme.colors.textPrimary}
/>
</Pressable>
),
)
: undefined,
headerRight: () => (
<Pressable
accessibilityLabel="Disconnect"
hitSlop={10}
onPress={async () => {
try {
await connection?.disconnect();
@@ -86,9 +108,7 @@ function ShellDetail() {
router.replace('/shell');
}}
>
<Text style={{ color: theme.colors.primary, fontWeight: '700' }}>
Disconnect
</Text>
<Ionicons name="power" size={20} color={theme.colors.primary} />
</Pressable>
),
}}
@@ -96,7 +116,13 @@ function ShellDetail() {
<View
style={[styles.container, { backgroundColor: theme.colors.background }]}
>
<View style={styles.terminal}>
<View
style={styles.terminal}
onStartShouldSetResponder={() => {
hiddenInputRef.current?.focus();
return false;
}}
>
<ScrollView
ref={scrollViewRef}
contentContainerStyle={styles.terminalContent}
@@ -106,60 +132,43 @@ function ShellDetail() {
{shellData || 'Connected. Output will appear here...'}
</Text>
</ScrollView>
</View>
<CommandInput
executeCommand={async (command) => {
await shell?.sendData(
Uint8Array.from(new TextEncoder().encode(command + '\n')).buffer,
);
<TextInput
ref={hiddenInputRef}
value={inputValue}
onChangeText={async (text) => {
if (!text) return;
await sendChunk(text);
setInputValue('');
}}
onKeyPress={async (e) => {
const key = e.nativeEvent.key;
if (key === 'Backspace') {
await sendChunk('\b');
}
}}
onSubmitEditing={async () => {
await sendChunk('\n');
}}
style={styles.hiddenInput}
autoFocus
multiline
caretHidden
autoCorrect={false}
autoCapitalize="none"
keyboardType="visible-password"
blurOnSubmit={false}
/>
</View>
</View>
</SafeAreaView>
);
}
function CommandInput(props: {
executeCommand: (command: string) => Promise<void>;
}) {
const [command, setCommand] = useState('');
async function handleExecute() {
if (!command.trim()) return;
await props.executeCommand(command);
setCommand('');
}
return (
<View>
<TextInput
testID="command-input"
style={styles.commandInput}
value={command}
onChangeText={setCommand}
placeholder="Type a command and press Enter or Execute"
placeholderTextColor="#9AA0A6"
autoCapitalize="none"
autoCorrect={false}
returnKeyType="send"
onSubmitEditing={handleExecute}
/>
<Pressable
style={[styles.executeButton, { marginTop: 8 }]}
onPress={handleExecute}
testID="execute-button"
>
<Text style={styles.executeButtonText}>Execute</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0B1324',
padding: 16,
padding: 12,
},
terminal: {
flex: 1,
@@ -171,7 +180,9 @@ const styles = StyleSheet.create({
marginBottom: 12,
},
terminalContent: {
padding: 12,
paddingHorizontal: 12,
paddingTop: 4,
paddingBottom: 12,
},
terminalText: {
color: '#D1D5DB',
@@ -183,33 +194,13 @@ const styles = StyleSheet.create({
default: 'monospace',
}),
},
commandInput: {
flex: 1,
backgroundColor: '#0E172B',
borderWidth: 1,
borderColor: '#2A3655',
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 12,
color: '#E5E7EB',
fontSize: 16,
fontFamily: Platform.select({
ios: 'Menlo',
android: 'monospace',
default: 'monospace',
}),
},
executeButton: {
backgroundColor: '#2563EB',
borderRadius: 10,
paddingHorizontal: 16,
paddingVertical: 12,
alignItems: 'center',
justifyContent: 'center',
},
executeButtonText: {
color: '#FFFFFF',
fontWeight: '700',
fontSize: 14,
hiddenInput: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
opacity: 0,
color: 'transparent',
},
});

View File

@@ -2,6 +2,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
import { isLiquidGlassAvailable } from 'expo-glass-effect';
import { Stack } from 'expo-router';
import React from 'react';
import { KeyboardProvider } from 'react-native-keyboard-controller';
import { ThemeProvider } from '../lib/theme';
import { queryClient } from '../lib/utils';
@@ -13,7 +14,9 @@ export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<KeyboardProvider>
<Stack screenOptions={{ headerShown: false }} />
</KeyboardProvider>
</ThemeProvider>
</QueryClientProvider>
);

17
pnpm-lock.yaml generated
View File

@@ -139,6 +139,9 @@ importers:
react-native-gesture-handler:
specifier: ~2.28.0
version: 2.28.0(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native-keyboard-controller:
specifier: 1.18.5
version: 1.18.5(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native-mmkv:
specifier: ^3.3.1
version: 3.3.1(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
@@ -7191,6 +7194,13 @@ packages:
react: '*'
react-native: '*'
react-native-keyboard-controller@1.18.5:
resolution: {integrity: sha512-wbYN6Tcu3G5a05dhRYBgjgd74KqoYWuUmroLpigRg9cXy5uYo7prTMIvMgvLtARQtUF7BOtFggUnzgoBOgk0TQ==}
peerDependencies:
react: '*'
react-native: '*'
react-native-reanimated: '>=3.0.0'
react-native-mmkv@3.3.1:
resolution: {integrity: sha512-LYamDWQirPTUJZ9Re+BkCD+zLRGNr+EVJDeIeblvoJXGatWy9PXnChtajDSLqwjX3EXVeUyjgrembs7wlBw9ug==}
peerDependencies:
@@ -17305,6 +17315,13 @@ snapshots:
react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
react-native-keyboard-controller@1.18.5(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native-reanimated: 4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native-mmkv@3.3.1(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0