mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
detail screen changes
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,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 (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: theme.colors.background }}>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerBackVisible: true,
|
||||
headerLeft: () => (
|
||||
<Pressable
|
||||
onPress={() => router.back()}
|
||||
hitSlop={10}
|
||||
style={{ paddingHorizontal: 4, paddingVertical: 4 }}
|
||||
>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={22}
|
||||
color={theme.colors.textPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
),
|
||||
headerLeft:
|
||||
Platform.OS === 'android'
|
||||
? () => (
|
||||
<Pressable
|
||||
onPress={() => router.back()}
|
||||
hitSlop={10}
|
||||
style={{ paddingHorizontal: 4, paddingVertical: 4 }}
|
||||
>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={22}
|
||||
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>
|
||||
<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>
|
||||
<CommandInput
|
||||
executeCommand={async (command) => {
|
||||
await shell?.sendData(
|
||||
Uint8Array.from(new TextEncoder().encode(command + '\n')).buffer,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
<Stack screenOptions={{ headerShown: false }} />
|
||||
<KeyboardProvider>
|
||||
<Stack screenOptions={{ headerShown: false }} />
|
||||
</KeyboardProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user