fix(mobile): integrate react-native-edge-to-edge for improved UI handling and add SafeAreaView for better layout management

This commit is contained in:
daniel31x13
2025-12-06 22:14:33 -05:00
parent 7610f844f7
commit 24206c0953
12 changed files with 71 additions and 70 deletions

View File

@@ -65,7 +65,17 @@
"allowBackup": false,
"compileSdkVersion": 35,
"targetSdkVersion": 35,
"buildToolsVersion": "35.0.0"
"buildToolsVersion": "35.0.0",
"usesCleartextTraffic": true
}
}
],
[
"react-native-edge-to-edge",
{
"android": {
"parentTheme": "Default",
"enforceNavigationBarContrast": false
}
}
],

View File

@@ -38,6 +38,7 @@ import { useDeleteLink, useUpdateLink } from "@linkwarden/router/links";
import { deleteLinkCache } from "@/lib/cache";
import { queryClient } from "@/lib/queryClient";
import getOriginalFormat from "@linkwarden/lib/getOriginalFormat";
import { StatusBar } from "expo-status-bar";
export default function RootLayout() {
const [isLoading, setIsLoading] = useState(true);
@@ -130,23 +131,18 @@ const RootComponent = ({
>
<KeyboardProvider>
<SheetProvider>
<StatusBar
style={colorScheme === "dark" ? "light" : "dark"}
backgroundColor={rawTheme[colorScheme as ThemeName]["base-100"]}
/>
{!isLoading && (
<Stack
screenOptions={{
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-200"],
headerShown: false,
contentStyle: {
backgroundColor:
rawTheme[colorScheme as ThemeName]["base-100"],
},
...Platform.select({
android: {
statusBarStyle: colorScheme === "dark" ? "light" : "dark",
statusBarBackgroundColor:
rawTheme[colorScheme as ThemeName]["base-100"],
},
}),
}}
>
{/* <Stack.Screen name="(tabs)" /> */}
@@ -157,8 +153,6 @@ const RootComponent = ({
headerBackTitle: "Back",
headerTitle: "",
headerTintColor: colorScheme === "dark" ? "white" : "black",
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
headerStyle: {
backgroundColor:
colorScheme === "dark"
@@ -318,43 +312,9 @@ const RootComponent = ({
),
}}
/>
<Stack.Screen
name="login"
options={{
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
...Platform.select({
android: {
statusBarStyle:
colorScheme === "light" ? "light" : "dark",
statusBarBackgroundColor:
rawTheme[colorScheme as ThemeName]["primary"],
},
}),
}}
/>
<Stack.Screen
name="index"
options={{
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
...Platform.select({
android: {
statusBarStyle:
colorScheme === "light" ? "light" : "dark",
statusBarBackgroundColor:
rawTheme[colorScheme as ThemeName]["primary"],
},
}),
}}
/>
<Stack.Screen
name="incoming"
options={{
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
}}
/>
<Stack.Screen name="login" />
<Stack.Screen name="index" />
<Stack.Screen name="incoming" />
<Stack.Screen name="+not-found" />
</Stack>
)}

View File

@@ -7,6 +7,7 @@ import { View, Text, Dimensions, TouchableOpacity, Image } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import Svg, { Path } from "react-native-svg";
import Animated, { SlideInDown } from "react-native-reanimated";
import { SafeAreaView } from "react-native-safe-area-context";
export default function HomeScreen() {
const { auth } = useAuthStore();
@@ -51,7 +52,10 @@ export default function HomeScreen() {
d="M0,256L48,234.7C96,213,192,171,288,176C384,181,480,235,576,266.7C672,299,768,309,864,277.3C960,245,1056,171,1152,122.7C1248,75,1344,53,1392,42.7L1440,32L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
/>
</Svg>
<View className="flex-col justify-end h-auto duration-100 pt-10 bg-base-100 -mt-2 pb-10 gap-4 w-full px-4">
<SafeAreaView
edges={["bottom"]}
className="flex-col justify-end h-auto duration-100 pt-10 bg-base-100 -mt-2 pb-10 gap-4 w-full px-4"
>
<Button
variant="accent"
size="lg"
@@ -65,7 +69,7 @@ export default function HomeScreen() {
>
<Text className="text-neutral text-center w-fit">Need help?</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
</Animated.View>
);
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { View, ActivityIndicator, Text } from "react-native";
import { View, ActivityIndicator, Text, Platform } from "react-native";
import { WebView } from "react-native-webview";
import useAuthStore from "@/store/auth";
import { useLocalSearchParams } from "expo-router";
@@ -11,6 +11,7 @@ import ReadableFormat from "@/components/Formats/ReadableFormat";
import ImageFormat from "@/components/Formats/ImageFormat";
import PdfFormat from "@/components/Formats/PdfFormat";
import WebpageFormat from "@/components/Formats/WebpageFormat";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function LinkScreen() {
const { auth } = useAuthStore();
@@ -48,8 +49,13 @@ export default function LinkScreen() {
}
}, [user, link]);
const insets = useSafeAreaInsets();
return (
<>
<View
className="flex-1"
style={{ paddingBottom: Platform.OS === "android" ? insets.bottom : 0 }}
>
{link?.id && Number(format) === ArchivedFormat.readability ? (
<ReadableFormat
link={link as any}
@@ -100,6 +106,6 @@ export default function LinkScreen() {
<Text className="text-base mt-2.5 text-neutral">Loading...</Text>
</View>
)}
</>
</View>
);
}

View File

@@ -9,9 +9,10 @@ import { View, Text, Dimensions, TouchableOpacity, Image } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import Svg, { Path } from "react-native-svg";
import {
KeyboardAwareScrollView,
KeyboardStickyView,
KeyboardToolbar,
} from "react-native-keyboard-controller";
import { SafeAreaView } from "react-native-safe-area-context";
export default function HomeScreen() {
const { auth, signIn } = useAuthStore();
@@ -56,10 +57,7 @@ export default function HomeScreen() {
return (
<>
<KeyboardAwareScrollView
bottomOffset={62}
contentContainerClassName="flex-col justify-end h-full bg-base-100 relative"
>
<KeyboardStickyView className="flex-col justify-end h-full bg-base-100 relative">
<View className="flex-col justify-end h-full bg-primary relative">
<View className="my-auto">
<Image
@@ -106,7 +104,10 @@ export default function HomeScreen() {
d="M0,256L48,234.7C96,213,192,171,288,176C384,181,480,235,576,266.7C672,299,768,309,864,277.3C960,245,1056,171,1152,122.7C1248,75,1344,53,1392,42.7L1440,32L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
/>
</Svg>
<View className="flex-col justify-end h-auto duration-100 pt-10 bg-base-100 -mt-2 pb-10 gap-4 w-full px-4">
<SafeAreaView
edges={["bottom"]}
className="flex-col justify-end h-auto duration-100 pt-10 bg-base-100 -mt-2 pb-10 gap-4 w-full px-4"
>
{showInstanceField && (
<Input
className="w-full text-xl p-3 leading-tight h-12"
@@ -187,9 +188,9 @@ export default function HomeScreen() {
>
<Text className="text-neutral text-center w-fit">Need help?</Text>
</TouchableOpacity>
</SafeAreaView>
</View>
</View>
</KeyboardAwareScrollView>
</KeyboardStickyView>
<KeyboardToolbar />
</>
);

View File

@@ -1,4 +1,4 @@
import { Alert, Text, View } from "react-native";
import { Alert, Platform, Text, View } from "react-native";
import { useRef, useState } from "react";
import ActionSheet, { ActionSheetRef } from "react-native-actions-sheet";
import Input from "@/components/ui/Input";
@@ -7,6 +7,7 @@ import { useAddLink } from "@linkwarden/router/links";
import useAuthStore from "@/store/auth";
import { rawTheme, ThemeName } from "@/lib/colors";
import { useColorScheme } from "nativewind";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function AddLinkSheet() {
const actionSheetRef = useRef<ActionSheetRef>(null);
@@ -15,6 +16,8 @@ export default function AddLinkSheet() {
const [link, setLink] = useState("");
const { colorScheme } = useColorScheme();
const insets = useSafeAreaInsets();
return (
<ActionSheet
ref={actionSheetRef}
@@ -25,6 +28,7 @@ export default function AddLinkSheet() {
containerStyle={{
backgroundColor: rawTheme[colorScheme as ThemeName]["base-200"],
}}
safeAreaInsets={insets}
>
<View className="px-8 py-5">
<Input

View File

@@ -1,4 +1,4 @@
import { View, Text, Alert } from "react-native";
import { View, Text, Alert, Platform } from "react-native";
import { useCallback, useEffect, useMemo, useState } from "react";
import ActionSheet, {
FlatList,
@@ -21,6 +21,7 @@ import { rawTheme, ThemeName } from "@/lib/colors";
import { useColorScheme } from "nativewind";
import { Folder, ChevronRight, Check } from "lucide-react-native";
import useTmpStore from "@/store/tmp";
import { useSafeAreaInsets } from "react-native-safe-area-context";
const Main = (props: SheetProps<"edit-link-sheet">) => {
const { auth } = useAuthStore();
@@ -255,6 +256,8 @@ const routes: Route[] = [
export default function EditLinkSheet() {
const { colorScheme } = useColorScheme();
const insets = useSafeAreaInsets();
return (
<ActionSheet
gestureEnabled
@@ -267,6 +270,7 @@ export default function EditLinkSheet() {
containerStyle={{
backgroundColor: rawTheme[colorScheme as ThemeName]["base-200"],
}}
safeAreaInsets={insets}
/>
);
}

View File

@@ -1,4 +1,4 @@
import { Alert, Text, View } from "react-native";
import { Alert, Platform, Text, View } from "react-native";
import { useRef, useState } from "react";
import ActionSheet, { ActionSheetRef } from "react-native-actions-sheet";
import Input from "@/components/ui/Input";
@@ -7,6 +7,7 @@ import useAuthStore from "@/store/auth";
import { rawTheme, ThemeName } from "@/lib/colors";
import { useColorScheme } from "nativewind";
import { useCreateCollection } from "@linkwarden/router/collections";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function NewCollectionSheet() {
const actionSheetRef = useRef<ActionSheetRef>(null);
@@ -18,6 +19,8 @@ export default function NewCollectionSheet() {
});
const { colorScheme } = useColorScheme();
const insets = useSafeAreaInsets();
return (
<ActionSheet
ref={actionSheetRef}
@@ -28,6 +31,7 @@ export default function NewCollectionSheet() {
containerStyle={{
backgroundColor: rawTheme[colorScheme as ThemeName]["base-200"],
}}
safeAreaInsets={insets}
>
<View className="px-8 py-5">
<Input

View File

@@ -5,6 +5,7 @@ import { rawTheme, ThemeName } from "@/lib/colors";
import { useColorScheme } from "nativewind";
import * as Clipboard from "expo-clipboard";
import { Button } from "../ui/Button";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function SupportSheet() {
const { colorScheme } = useColorScheme();
@@ -13,11 +14,11 @@ export default function SupportSheet() {
async function handleEmailPress() {
await Clipboard.setStringAsync("support@linkwarden.app");
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
setTimeout(() => setCopied(false), 2000);
}
const insets = useSafeAreaInsets();
return (
<ActionSheet
gestureEnabled
@@ -27,6 +28,7 @@ export default function SupportSheet() {
containerStyle={{
backgroundColor: rawTheme[colorScheme as ThemeName]["base-100"],
}}
safeAreaInsets={insets}
>
<View className="px-8 py-5 flex-col gap-4">
<Text className="text-2xl font-bold text-base-content">Need help?</Text>

View File

@@ -56,6 +56,7 @@
"react-native": "0.76.9",
"react-native-actions-sheet": "^0.9.7",
"react-native-blob-util": "^0.23.2",
"react-native-edge-to-edge": "^1.7.0",
"react-native-gesture-handler": "~2.20.2",
"react-native-ios-context-menu": "3.1.3",
"react-native-ios-utilities": "5.1.7",

View File

@@ -15,13 +15,13 @@ const useDataStore = create<DataStore>((set, get) => ({
hasShareIntent: false,
url: "",
},
theme: "system",
theme: "light",
preferredBrowser: "app",
},
setData: async () => {
const dataString = JSON.parse((await AsyncStorage.getItem("data")) || "{}");
colorScheme.set(dataString.theme || "system");
colorScheme.set(dataString.theme || "light");
if (dataString)
set((state) => ({ data: { ...state.data, ...dataString } }));

View File

@@ -11733,6 +11733,11 @@ react-native-css-interop@0.1.22:
lightningcss "^1.27.0"
semver "^7.6.3"
react-native-edge-to-edge@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/react-native-edge-to-edge/-/react-native-edge-to-edge-1.7.0.tgz#6999ee86febe920b9b4e730ec1f0eaf0c28f0003"
integrity sha512-ERegbsq28yoMndn/Uq49i4h6aAhMvTEjOfkFh50yX9H/dMjjCr/Tix/es/9JcPRvC+q7VzCMWfxWDUb6Jrq1OQ==
react-native-gesture-handler@~2.20.2:
version "2.20.2"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz#73844c8e9c417459c2f2981bc4d8f66ba8a5ee66"