mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-16 06:25:42 -04:00
Improve server URL input UX on native app launch (#9)
This commit is contained in:
2 files changed
+58
-41
No files matched your search
@@ -63,8 +63,6 @@ export default function ServerUrlScreen() {
|
|||||||
const destructiveColor = useCSSVariable("--color-destructive") as string;
|
const destructiveColor = useCSSVariable("--color-destructive") as string;
|
||||||
const mutedFgColor = useCSSVariable("--color-muted-foreground") as string;
|
const mutedFgColor = useCSSVariable("--color-muted-foreground") as string;
|
||||||
|
|
||||||
const [isFirstLaunch] = useState(() => !hasStoredServerUrl());
|
|
||||||
|
|
||||||
// Icon pulse animation
|
// Icon pulse animation
|
||||||
const iconOpacity = useSharedValue(1);
|
const iconOpacity = useSharedValue(1);
|
||||||
const iconAnimatedStyle = useAnimatedStyle(() => ({
|
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) => {
|
const handleChangeText = (text: string) => {
|
||||||
setUrl(text);
|
setUrl(text);
|
||||||
if (connection.phase === "error") {
|
if (connection.phase === "error") {
|
||||||
@@ -114,6 +104,14 @@ export default function ServerUrlScreen() {
|
|||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
|
|
||||||
const fullUrl = normalizeUrl(trimmed);
|
const fullUrl = normalizeUrl(trimmed);
|
||||||
|
|
||||||
|
try {
|
||||||
|
new URL(fullUrl);
|
||||||
|
} catch {
|
||||||
|
setConnection({ phase: "error", error: "invalid_url" });
|
||||||
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setConnection({ phase: "connecting" });
|
setConnection({ phase: "connecting" });
|
||||||
|
|
||||||
const result = await validateServerUrl(fullUrl);
|
const result = await validateServerUrl(fullUrl);
|
||||||
@@ -133,6 +131,16 @@ export default function ServerUrlScreen() {
|
|||||||
|
|
||||||
const isConnecting = connection.phase === "connecting";
|
const isConnecting = connection.phase === "connecting";
|
||||||
const isSuccess = connection.phase === "success";
|
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;
|
const isDisabled = isConnecting || isSuccess;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -187,7 +195,11 @@ export default function ServerUrlScreen() {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Button onPress={handleConnect} className="bg-primary">
|
<Button
|
||||||
|
onPress={handleConnect}
|
||||||
|
disabled={!isValidUrl}
|
||||||
|
className="bg-primary"
|
||||||
|
>
|
||||||
<ButtonLabel>Connect</ButtonLabel>
|
<ButtonLabel>Connect</ButtonLabel>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import type { StyleProp, ViewStyle } from "react-native";
|
import type { StyleProp, ViewStyle } from "react-native";
|
||||||
import { ScrollView } from "react-native";
|
import { ScrollView } from "react-native";
|
||||||
|
import { KeyboardAvoidingView } from "react-native-keyboard-controller";
|
||||||
import Animated, { FadeIn } from "react-native-reanimated";
|
import Animated, { FadeIn } from "react-native-reanimated";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { SofaLogo } from "@/components/ui/sofa-logo";
|
import { SofaLogo } from "@/components/ui/sofa-logo";
|
||||||
@@ -22,37 +23,41 @@ export function AuthScreen({
|
|||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
|
||||||
contentContainerStyle={{
|
<ScrollView
|
||||||
flexGrow: 1,
|
contentContainerStyle={{
|
||||||
justifyContent: "center",
|
flexGrow: 1,
|
||||||
paddingHorizontal: 24,
|
justifyContent: "center",
|
||||||
paddingTop: insets.top,
|
paddingHorizontal: 24,
|
||||||
paddingBottom: insets.bottom,
|
paddingTop: insets.top + 24,
|
||||||
}}
|
paddingBottom: insets.bottom,
|
||||||
keyboardShouldPersistTaps="handled"
|
}}
|
||||||
bounces={false}
|
keyboardShouldPersistTaps="handled"
|
||||||
className="bg-background"
|
bounces={false}
|
||||||
>
|
className="bg-background"
|
||||||
<Animated.View
|
|
||||||
entering={FadeIn.duration(400)}
|
|
||||||
className="mb-4 items-center"
|
|
||||||
>
|
>
|
||||||
{logoStyle ? (
|
<Animated.View
|
||||||
<Animated.View style={logoStyle}>
|
entering={FadeIn.duration(400)}
|
||||||
|
className="mb-4 items-center"
|
||||||
|
>
|
||||||
|
{logoStyle ? (
|
||||||
|
<Animated.View style={logoStyle}>
|
||||||
|
<SofaLogo size={48} />
|
||||||
|
</Animated.View>
|
||||||
|
) : (
|
||||||
<SofaLogo size={48} />
|
<SofaLogo size={48} />
|
||||||
</Animated.View>
|
)}
|
||||||
) : (
|
<Text className="mt-1 font-display text-[32px] text-foreground">
|
||||||
<SofaLogo size={48} />
|
{title}
|
||||||
)}
|
</Text>
|
||||||
<Text className="mt-1 font-display text-[32px] text-foreground">
|
{subtitle && (
|
||||||
{title}
|
<Text className="mt-2 text-muted-foreground text-sm">
|
||||||
</Text>
|
{subtitle}
|
||||||
{subtitle && (
|
</Text>
|
||||||
<Text className="mt-2 text-muted-foreground text-sm">{subtitle}</Text>
|
)}
|
||||||
)}
|
</Animated.View>
|
||||||
</Animated.View>
|
{children}
|
||||||
{children}
|
</ScrollView>
|
||||||
</ScrollView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user