Files
pi-mobile/apps/mobile/app/index.tsx
T
2026-07-09 12:07:57 -05:00

278 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
},
});