Improve server URL input UX on native app launch (#9)

This commit is contained in:
2026-03-13 10:32:38 -04:00
committed by GitHub
parent 83839a0cd7
commit 3a57de2ed6
2 changed files with 58 additions and 41 deletions
+23 -11
View File
@@ -63,8 +63,6 @@ export default function ServerUrlScreen() {
const destructiveColor = useCSSVariable("--color-destructive") as string;
const mutedFgColor = useCSSVariable("--color-muted-foreground") as string;
const [isFirstLaunch] = useState(() => !hasStoredServerUrl());
// Icon pulse animation
const iconOpacity = useSharedValue(1);
const iconAnimatedStyle = useAnimatedStyle(() => ({
@@ -94,14 +92,6 @@ export default function ServerUrlScreen() {
};
}, []);
// Auto-focus on first launch
useEffect(() => {
if (isFirstLaunch) {
const timer = setTimeout(() => inputRef.current?.focus(), 500);
return () => clearTimeout(timer);
}
}, [isFirstLaunch]);
const handleChangeText = (text: string) => {
setUrl(text);
if (connection.phase === "error") {
@@ -114,6 +104,14 @@ export default function ServerUrlScreen() {
if (!trimmed) return;
const fullUrl = normalizeUrl(trimmed);
try {
new URL(fullUrl);
} catch {
setConnection({ phase: "error", error: "invalid_url" });
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
return;
}
setConnection({ phase: "connecting" });
const result = await validateServerUrl(fullUrl);
@@ -133,6 +131,16 @@ export default function ServerUrlScreen() {
const isConnecting = connection.phase === "connecting";
const isSuccess = connection.phase === "success";
const trimmedUrl = url.trim().replace(/\/+$/, "");
const isValidUrl = (() => {
if (!trimmedUrl) return false;
try {
new URL(normalizeUrl(trimmedUrl));
return true;
} catch {
return false;
}
})();
const isDisabled = isConnecting || isSuccess;
return (
@@ -187,7 +195,11 @@ export default function ServerUrlScreen() {
</Text>
</View>
) : (
<Button onPress={handleConnect} className="bg-primary">
<Button
onPress={handleConnect}
disabled={!isValidUrl}
className="bg-primary"
>
<ButtonLabel>Connect</ButtonLabel>
</Button>
)}
+7 -2
View File
@@ -1,6 +1,7 @@
import type { ReactNode } from "react";
import type { StyleProp, ViewStyle } from "react-native";
import { ScrollView } from "react-native";
import { KeyboardAvoidingView } from "react-native-keyboard-controller";
import Animated, { FadeIn } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { SofaLogo } from "@/components/ui/sofa-logo";
@@ -22,12 +23,13 @@ export function AuthScreen({
const insets = useSafeAreaInsets();
return (
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
<ScrollView
contentContainerStyle={{
flexGrow: 1,
justifyContent: "center",
paddingHorizontal: 24,
paddingTop: insets.top,
paddingTop: insets.top + 24,
paddingBottom: insets.bottom,
}}
keyboardShouldPersistTaps="handled"
@@ -49,10 +51,13 @@ export function AuthScreen({
{title}
</Text>
{subtitle && (
<Text className="mt-2 text-muted-foreground text-sm">{subtitle}</Text>
<Text className="mt-2 text-muted-foreground text-sm">
{subtitle}
</Text>
)}
</Animated.View>
{children}
</ScrollView>
</KeyboardAvoidingView>
);
}