278 lines
6.2 KiB
TypeScript
278 lines
6.2 KiB
TypeScript
import { router } from "expo-router";
|
||
import { useEffect } from "react";
|
||
import {
|
||
ActivityIndicator,
|
||
Pressable,
|
||
ScrollView,
|
||
StyleSheet,
|
||
Text,
|
||
TextInput,
|
||
View,
|
||
} from "react-native";
|
||
import { useSocket } from "@/contexts/SocketContext";
|
||
import { colors, monoFont } from "@/lib/theme";
|
||
import type { SavedComputer } from "@/lib/types";
|
||
|
||
function SavedComputerRow({
|
||
computer,
|
||
connecting,
|
||
onConnect,
|
||
onRemove,
|
||
}: {
|
||
computer: SavedComputer;
|
||
connecting: boolean;
|
||
onConnect: () => void;
|
||
onRemove: () => void;
|
||
}) {
|
||
return (
|
||
<View style={styles.savedRow}>
|
||
<Pressable
|
||
style={({ pressed }) => [styles.savedRowMain, pressed && styles.savedRowPressed]}
|
||
disabled={connecting}
|
||
onPress={onConnect}
|
||
>
|
||
<Text style={styles.savedName} numberOfLines={1}>
|
||
{computer.name}
|
||
</Text>
|
||
<Text style={styles.savedAddress}>
|
||
{computer.host}:{computer.port}
|
||
</Text>
|
||
</Pressable>
|
||
<Pressable
|
||
style={({ pressed }) => [styles.removeButton, pressed && styles.removeButtonPressed]}
|
||
disabled={connecting}
|
||
onPress={onRemove}
|
||
hitSlop={8}
|
||
>
|
||
<Text style={styles.removeButtonText}>×</Text>
|
||
</Pressable>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
export default function ConnectScreen() {
|
||
const {
|
||
host,
|
||
port,
|
||
computerName,
|
||
connected,
|
||
connecting,
|
||
error,
|
||
savedComputers,
|
||
setHost,
|
||
setPort,
|
||
setComputerName,
|
||
connect,
|
||
connectToSavedComputer,
|
||
removeSavedComputerEntry,
|
||
} = useSocket();
|
||
|
||
useEffect(() => {
|
||
if (connected) {
|
||
router.replace("/sessions");
|
||
}
|
||
}, [connected]);
|
||
|
||
return (
|
||
<ScrollView
|
||
style={styles.scroll}
|
||
contentContainerStyle={styles.container}
|
||
keyboardShouldPersistTaps="handled"
|
||
>
|
||
<Text style={styles.brand}>Pi Mobile</Text>
|
||
<Text style={styles.subtitle}>Remote control for Pi agents on your computer.</Text>
|
||
|
||
<View style={styles.card}>
|
||
<Text style={styles.label}>Computer name</Text>
|
||
<TextInput
|
||
value={computerName}
|
||
onChangeText={setComputerName}
|
||
placeholder="My MacBook"
|
||
placeholderTextColor="#666"
|
||
autoCapitalize="words"
|
||
autoCorrect={false}
|
||
style={styles.input}
|
||
/>
|
||
|
||
<Text style={styles.label}>Computer IP</Text>
|
||
<TextInput
|
||
value={host}
|
||
onChangeText={setHost}
|
||
placeholder="192.168.1.10"
|
||
placeholderTextColor="#666"
|
||
autoCapitalize="none"
|
||
autoCorrect={false}
|
||
style={styles.input}
|
||
/>
|
||
|
||
<Text style={styles.label}>Port</Text>
|
||
<TextInput
|
||
value={port}
|
||
onChangeText={setPort}
|
||
placeholder="8787"
|
||
placeholderTextColor="#666"
|
||
keyboardType="number-pad"
|
||
style={styles.input}
|
||
/>
|
||
|
||
{error ? <Text style={styles.error}>{error}</Text> : null}
|
||
|
||
<Pressable
|
||
style={[styles.button, connecting && styles.buttonDisabled]}
|
||
disabled={connecting || !host.trim()}
|
||
onPress={() => void connect()}
|
||
>
|
||
{connecting ? (
|
||
<ActivityIndicator color="#0a0a0a" />
|
||
) : (
|
||
<Text style={styles.buttonText}>Connect</Text>
|
||
)}
|
||
</Pressable>
|
||
</View>
|
||
|
||
{savedComputers.length > 0 ? (
|
||
<View style={styles.savedSection}>
|
||
<Text style={styles.savedSectionTitle}>Saved computers</Text>
|
||
{savedComputers.map((computer) => (
|
||
<SavedComputerRow
|
||
key={computer.id}
|
||
computer={computer}
|
||
connecting={connecting}
|
||
onConnect={() => void connectToSavedComputer(computer)}
|
||
onRemove={() => void removeSavedComputerEntry(computer.id)}
|
||
/>
|
||
))}
|
||
</View>
|
||
) : (
|
||
<Text style={styles.savedHint}>
|
||
Successful connections are saved here so you can reconnect with one tap.
|
||
</Text>
|
||
)}
|
||
</ScrollView>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
scroll: {
|
||
flex: 1,
|
||
},
|
||
container: {
|
||
padding: 24,
|
||
gap: 16,
|
||
paddingBottom: 40,
|
||
},
|
||
brand: {
|
||
fontSize: 36,
|
||
fontWeight: "700",
|
||
color: "#f5f5f5",
|
||
marginTop: 24,
|
||
},
|
||
subtitle: {
|
||
color: "#a3a3a3",
|
||
fontSize: 16,
|
||
lineHeight: 22,
|
||
},
|
||
card: {
|
||
marginTop: 24,
|
||
gap: 12,
|
||
},
|
||
label: {
|
||
color: "#d4d4d4",
|
||
fontSize: 14,
|
||
fontWeight: "600",
|
||
},
|
||
input: {
|
||
backgroundColor: "#171717",
|
||
borderWidth: 1,
|
||
borderColor: "#262626",
|
||
borderRadius: 12,
|
||
color: "#f5f5f5",
|
||
paddingHorizontal: 14,
|
||
paddingVertical: 12,
|
||
fontSize: 16,
|
||
},
|
||
button: {
|
||
marginTop: 8,
|
||
backgroundColor: "#f5f5f5",
|
||
borderRadius: 12,
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
minHeight: 48,
|
||
},
|
||
buttonDisabled: {
|
||
opacity: 0.7,
|
||
},
|
||
buttonText: {
|
||
color: "#0a0a0a",
|
||
fontSize: 16,
|
||
fontWeight: "700",
|
||
},
|
||
error: {
|
||
color: "#f87171",
|
||
fontSize: 14,
|
||
},
|
||
savedSection: {
|
||
marginTop: 8,
|
||
gap: 10,
|
||
},
|
||
savedSectionTitle: {
|
||
color: colors.textSecondary,
|
||
fontSize: 13,
|
||
fontWeight: "700",
|
||
textTransform: "uppercase",
|
||
letterSpacing: 0.6,
|
||
fontFamily: monoFont,
|
||
},
|
||
savedRow: {
|
||
flexDirection: "row",
|
||
alignItems: "center",
|
||
backgroundColor: colors.surface,
|
||
borderRadius: 12,
|
||
borderWidth: 1,
|
||
borderColor: colors.border,
|
||
overflow: "hidden",
|
||
},
|
||
savedRowMain: {
|
||
flex: 1,
|
||
paddingHorizontal: 14,
|
||
paddingVertical: 12,
|
||
gap: 2,
|
||
},
|
||
savedRowPressed: {
|
||
backgroundColor: colors.surfaceDeep,
|
||
},
|
||
savedName: {
|
||
color: colors.text,
|
||
fontSize: 16,
|
||
fontWeight: "600",
|
||
},
|
||
savedAddress: {
|
||
color: colors.textSecondary,
|
||
fontSize: 13,
|
||
fontFamily: monoFont,
|
||
},
|
||
removeButton: {
|
||
paddingHorizontal: 16,
|
||
paddingVertical: 12,
|
||
alignSelf: "stretch",
|
||
justifyContent: "center",
|
||
borderLeftWidth: 1,
|
||
borderLeftColor: colors.border,
|
||
},
|
||
removeButtonPressed: {
|
||
backgroundColor: colors.surfaceDeep,
|
||
},
|
||
removeButtonText: {
|
||
color: colors.textMuted,
|
||
fontSize: 22,
|
||
fontWeight: "300",
|
||
lineHeight: 22,
|
||
},
|
||
savedHint: {
|
||
marginTop: 8,
|
||
color: colors.textMuted,
|
||
fontSize: 14,
|
||
lineHeight: 20,
|
||
},
|
||
});
|