fix: respect safe area insets and improve drag visual feedback
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { Alert, Platform, Pressable, Text, View } from "react-native";
|
import { Alert, Platform, Pressable, Text, View } from "react-native";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { CameraView, useCameraPermissions } from "expo-camera";
|
import { CameraView, useCameraPermissions } from "expo-camera";
|
||||||
import type { BarcodeScanningResult } from "expo-camera";
|
import type { BarcodeScanningResult } from "expo-camera";
|
||||||
@@ -13,7 +14,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
paddingHorizontal: theme.spacing[6],
|
paddingHorizontal: theme.spacing[6],
|
||||||
paddingTop: theme.spacing[6],
|
|
||||||
paddingBottom: theme.spacing[4],
|
paddingBottom: theme.spacing[4],
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
@@ -32,7 +32,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
body: {
|
body: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingHorizontal: theme.spacing[6],
|
paddingHorizontal: theme.spacing[6],
|
||||||
paddingBottom: theme.spacing[6],
|
|
||||||
},
|
},
|
||||||
cameraWrap: {
|
cameraWrap: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -132,6 +131,7 @@ function extractOfferUrlFromScan(result: BarcodeScanningResult): string | null {
|
|||||||
|
|
||||||
export default function PairScanScreen() {
|
export default function PairScanScreen() {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { upsertDaemonFromOfferUrl } = useDaemonRegistry();
|
const { upsertDaemonFromOfferUrl } = useDaemonRegistry();
|
||||||
|
|
||||||
@@ -172,17 +172,17 @@ export default function PairScanScreen() {
|
|||||||
if (Platform.OS === "web") {
|
if (Platform.OS === "web") {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.header}>
|
<View style={[styles.header, { paddingTop: insets.top + theme.spacing[2] }]}>
|
||||||
<Text style={styles.headerTitle}>Scan QR</Text>
|
<Text style={styles.headerTitle}>Scan QR</Text>
|
||||||
<Pressable onPress={() => router.back()}>
|
<Pressable onPress={() => router.back()}>
|
||||||
<Text style={styles.headerButtonText}>Close</Text>
|
<Text style={styles.headerButtonText}>Close</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.body}>
|
<View style={[styles.body, { paddingBottom: insets.bottom + theme.spacing[6] }]}>
|
||||||
<View style={styles.permissionCard}>
|
<View style={styles.permissionCard}>
|
||||||
<Text style={styles.permissionTitle}>Not available on web</Text>
|
<Text style={styles.permissionTitle}>Not available on web</Text>
|
||||||
<Text style={styles.permissionBody}>
|
<Text style={styles.permissionBody}>
|
||||||
QR scanning isn’t supported in the web build. Use “Paste link” instead.
|
QR scanning isn't supported in the web build. Use "Paste link" instead.
|
||||||
</Text>
|
</Text>
|
||||||
<Pressable style={styles.permissionButton} onPress={() => router.replace("/settings")}>
|
<Pressable style={styles.permissionButton} onPress={() => router.replace("/settings")}>
|
||||||
<Text style={styles.permissionButtonText}>Back to Settings</Text>
|
<Text style={styles.permissionButtonText}>Back to Settings</Text>
|
||||||
@@ -197,14 +197,14 @@ export default function PairScanScreen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.header}>
|
<View style={[styles.header, { paddingTop: insets.top + theme.spacing[2] }]}>
|
||||||
<Text style={styles.headerTitle}>Scan QR</Text>
|
<Text style={styles.headerTitle}>Scan QR</Text>
|
||||||
<Pressable onPress={() => router.back()}>
|
<Pressable onPress={() => router.back()}>
|
||||||
<Text style={styles.headerButtonText}>Close</Text>
|
<Text style={styles.headerButtonText}>Close</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.body}>
|
<View style={[styles.body, { paddingBottom: insets.bottom + theme.spacing[6] }]}>
|
||||||
{!granted ? (
|
{!granted ? (
|
||||||
<View style={styles.permissionCard}>
|
<View style={styles.permissionCard}>
|
||||||
<Text style={styles.permissionTitle}>Camera permission</Text>
|
<Text style={styles.permissionTitle}>Camera permission</Text>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { Sun, Moon, Monitor } from "lucide-react-native";
|
import { Sun, Moon, Monitor } from "lucide-react-native";
|
||||||
import { Fonts } from "@/constants/theme";
|
import { Fonts } from "@/constants/theme";
|
||||||
@@ -378,6 +379,8 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
|
|
||||||
|
|
||||||
export default function SettingsScreen() {
|
export default function SettingsScreen() {
|
||||||
|
const { theme } = useUnistyles();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
const { settings, isLoading: settingsLoading, updateSettings, resetSettings } = useAppSettings();
|
const { settings, isLoading: settingsLoading, updateSettings, resetSettings } = useAppSettings();
|
||||||
const {
|
const {
|
||||||
daemons,
|
daemons,
|
||||||
@@ -561,7 +564,7 @@ export default function SettingsScreen() {
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<MenuHeader title="Settings" />
|
<MenuHeader title="Settings" />
|
||||||
|
|
||||||
<ScrollView style={styles.scrollView}>
|
<ScrollView style={styles.scrollView} contentContainerStyle={{ paddingBottom: insets.bottom }}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{/* Host Management */}
|
{/* Host Management */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { ActivityIndicator, Pressable, Text, View } from "react-native";
|
import { ActivityIndicator, Pressable, Text, View } from "react-native";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { Check, X, XCircle } from "lucide-react-native";
|
import { Check, X, XCircle } from "lucide-react-native";
|
||||||
import { useDownloadStore, formatSpeed, formatEta } from "@/stores/download-store";
|
import { useDownloadStore, formatSpeed, formatEta } from "@/stores/download-store";
|
||||||
@@ -8,6 +9,7 @@ const AUTO_DISMISS_DELAY = 3000;
|
|||||||
|
|
||||||
export function DownloadToast() {
|
export function DownloadToast() {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
const downloads = useDownloadStore((state) => state.downloads);
|
const downloads = useDownloadStore((state) => state.downloads);
|
||||||
const activeDownloadId = useDownloadStore((state) => state.activeDownloadId);
|
const activeDownloadId = useDownloadStore((state) => state.activeDownloadId);
|
||||||
const dismissDownload = useDownloadStore((state) => state.dismissDownload);
|
const dismissDownload = useDownloadStore((state) => state.dismissDownload);
|
||||||
@@ -39,7 +41,7 @@ export function DownloadToast() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container} pointerEvents="box-none">
|
<View style={[styles.container, { bottom: theme.spacing[4] + insets.bottom }]} pointerEvents="box-none">
|
||||||
<View style={styles.toast}>
|
<View style={styles.toast}>
|
||||||
{activeDownload.status === "downloading" ? (
|
{activeDownload.status === "downloading" ? (
|
||||||
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||||
@@ -89,7 +91,6 @@ export function DownloadToast() {
|
|||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: theme.spacing[4],
|
|
||||||
left: theme.spacing[4],
|
left: theme.spacing[4],
|
||||||
right: theme.spacing[4],
|
right: theme.spacing[4],
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
|
|||||||
@@ -61,10 +61,14 @@ function SortableItem<T>({
|
|||||||
// Trigger drag - handled by dnd-kit's listeners
|
// Trigger drag - handled by dnd-kit's listeners
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const baseTransform = CSS.Transform.toString(transform);
|
||||||
|
const scaleTransform = isDragging ? "scale(1.02)" : "";
|
||||||
|
const combinedTransform = [baseTransform, scaleTransform].filter(Boolean).join(" ");
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: combinedTransform || undefined,
|
||||||
transition,
|
transition,
|
||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.9 : 1,
|
||||||
zIndex: isDragging ? 1000 : 1,
|
zIndex: isDragging ? 1000 : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -649,6 +649,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
sectionDragging: {
|
sectionDragging: {
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
|
transform: [{ scale: 1.02 }],
|
||||||
},
|
},
|
||||||
chevron: {
|
chevron: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
|
|||||||
@@ -617,14 +617,25 @@ function AgentScreenContent({
|
|||||||
<View style={styles.headerRightContent}>
|
<View style={styles.headerRightContent}>
|
||||||
<Pressable onPress={toggleFileExplorer} style={styles.menuButton}>
|
<Pressable onPress={toggleFileExplorer} style={styles.menuButton}>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<Folder
|
checkout?.isGit ? (
|
||||||
size={20}
|
<GitBranch
|
||||||
color={
|
size={20}
|
||||||
isExplorerOpen
|
color={
|
||||||
? theme.colors.foreground
|
isExplorerOpen
|
||||||
: theme.colors.foregroundMuted
|
? theme.colors.foreground
|
||||||
}
|
: theme.colors.foregroundMuted
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Folder
|
||||||
|
size={20}
|
||||||
|
color={
|
||||||
|
isExplorerOpen
|
||||||
|
? theme.colors.foreground
|
||||||
|
: theme.colors.foregroundMuted
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<PanelRight
|
<PanelRight
|
||||||
size={16}
|
size={16}
|
||||||
|
|||||||
Reference in New Issue
Block a user