From 4413566fec883e6bdcb77765d9cc4f1953a3f2ff Mon Sep 17 00:00:00 2001 From: EthanShoeDev <13422990+EthanShoeDev@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:18:13 -0400 Subject: [PATCH] detail screen changes --- apps/mobile/package.json | 1 + apps/mobile/src/app/(tabs)/shell/detail.tsx | 171 ++++++++++---------- apps/mobile/src/app/_layout.tsx | 5 +- apps/mobile/src/lib/query-fns.ts | 6 +- apps/mobile/src/lib/secrets-manager.ts | 6 +- pnpm-lock.yaml | 17 ++ 6 files changed, 109 insertions(+), 97 deletions(-) diff --git a/apps/mobile/package.json b/apps/mobile/package.json index bae6d4d..52e3473 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -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", diff --git a/apps/mobile/src/app/(tabs)/shell/detail.tsx b/apps/mobile/src/app/(tabs)/shell/detail.tsx index 26a8779..f93a7c3 100644 --- a/apps/mobile/src/app/(tabs)/shell/detail.tsx +++ b/apps/mobile/src/app/(tabs)/shell/detail.tsx @@ -36,6 +36,8 @@ function ShellDetail() { : undefined; const [shellData, setShellData] = useState(''); + const [inputValue, setInputValue] = useState(''); + const hiddenInputRef = useRef(null); useEffect(() => { if (!connection) return; @@ -59,26 +61,46 @@ 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 ( ( - router.back()} - hitSlop={10} - style={{ paddingHorizontal: 4, paddingVertical: 4 }} - > - - - ), + headerLeft: + Platform.OS === 'android' + ? () => ( + router.back()} + hitSlop={10} + style={{ paddingHorizontal: 4, paddingVertical: 4 }} + > + + + ) + : undefined, headerRight: () => ( { try { await connection?.disconnect(); @@ -86,9 +108,7 @@ function ShellDetail() { router.replace('/shell'); }} > - - Disconnect - + ), }} @@ -96,7 +116,13 @@ function ShellDetail() { - + { + hiddenInputRef.current?.focus(); + return false; + }} + > + { + 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} + /> - { - await shell?.sendData( - Uint8Array.from(new TextEncoder().encode(command + '\n')).buffer, - ); - }} - /> ); } -function CommandInput(props: { - executeCommand: (command: string) => Promise; -}) { - const [command, setCommand] = useState(''); - - async function handleExecute() { - if (!command.trim()) return; - await props.executeCommand(command); - setCommand(''); - } - - return ( - - - - Execute - - - ); -} - 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', }, }); diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx index 9bf354d..d5676fc 100644 --- a/apps/mobile/src/app/_layout.tsx +++ b/apps/mobile/src/app/_layout.tsx @@ -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 ( - + + + ); diff --git a/apps/mobile/src/lib/query-fns.ts b/apps/mobile/src/lib/query-fns.ts index 4fc3f85..639600f 100644 --- a/apps/mobile/src/lib/query-fns.ts +++ b/apps/mobile/src/lib/query-fns.ts @@ -28,9 +28,9 @@ export const useSshConnMutation = () => { security: connectionDetails.security.type === 'password' ? { - type: 'password', - password: connectionDetails.security.password, - } + type: 'password', + password: connectionDetails.security.password, + } : { type: 'key', privateKey: 'TODO' }, onStatusChange: (status) => { console.log('SSH connection status', status); diff --git a/apps/mobile/src/lib/secrets-manager.ts b/apps/mobile/src/lib/secrets-manager.ts index 0055670..0fd766f 100644 --- a/apps/mobile/src/lib/secrets-manager.ts +++ b/apps/mobile/src/lib/secrets-manager.ts @@ -82,9 +82,9 @@ function makeBetterSecureStore< const unsafedRootManifest = rawRootManifestString ? JSON.parse(rawRootManifestString) : { - manifestVersion: rootManifestVersion, - manifestChunksIds: [], - }; + manifestVersion: rootManifestVersion, + manifestChunksIds: [], + }; const rootManifest = rootManifestSchema.parse(unsafedRootManifest); const manifestChunks = await Promise.all( rootManifest.manifestChunksIds.map(async (manifestChunkId) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87b1550..23f831c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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