feat(mobile): proper deployment

This commit is contained in:
daniel31x13
2025-10-13 10:56:29 -04:00
parent 96472243db
commit f7e7fda779
8 changed files with 373 additions and 213 deletions

View File

@@ -10,7 +10,16 @@
"newArchEnabled": true,
"ios": {
"supportsTablet": true,
"bundleIdentifier": "app.linkwarden"
"bundleIdentifier": "app.linkwarden",
"entitlements": {
"com.apple.security.application-groups": ["group.app.linkwarden"]
},
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false,
"NSAppTransportSecurity": {
"NSAllowsArbitraryLoads": true
}
}
},
"android": {
"adaptiveIcon": {
@@ -41,8 +50,26 @@
}
],
"expo-secure-store",
"expo-share-intent",
"./plugins/with-daynight-transparent-nav"
[
"expo-share-intent",
{
"iosAppGroupIdentifier": "group.app.linkwarden"
}
],
"./plugins/with-daynight-transparent-nav",
[
"expo-build-properties",
{
"android": {
"enableProguardInReleaseBuilds": true,
"extraProguardRules": "-keep public class com.horcrux.svg.** {*;}",
"allowBackup": false,
"compileSdkVersion": 35,
"targetSdkVersion": 35,
"buildToolsVersion": "35.0.0"
}
}
]
],
"experiments": {
"typedRoutes": true
@@ -55,6 +82,15 @@
"androidNavigationBar": {
"backgroundColor": "#ffffff",
"barStyle": "dark-content"
}
},
"extra": {
"router": {
"origin": false
},
"eas": {
"projectId": "34f82639-7a25-4ebe-81c8-2db521b612cf"
}
},
"owner": "linkwarden"
}
}

View File

@@ -8,10 +8,10 @@ import {
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import React, { useEffect, useMemo, useState } from "react";
import { useDashboardData } from "@linkwarden/router/dashboardData";
import useAuthStore from "@/store/auth";
import React, { useEffect, useMemo, useState } from "react";
import { DashboardSection, DashboardSectionType } from "@prisma/client";
import { DashboardSection } from "@linkwarden/prisma/client";
import { useUser } from "@linkwarden/router/user";
import { useCollections } from "@linkwarden/router/collections";
import { useTags } from "@linkwarden/router/tags";
@@ -32,6 +32,13 @@ import {
} from "lucide-react-native";
import Spinner from "@/components/ui/Spinner";
// Don't remove this, spent a couple of days to figure out why the app crashes in production :|
type DashboardSectionType =
| "STATS"
| "RECENT_LINKS"
| "PINNED_LINKS"
| "COLLECTION";
export default function DashboardScreen() {
const { auth } = useAuthStore();
const {
@@ -76,6 +83,12 @@ export default function DashboardScreen() {
});
}, [dashboardSections]);
const RenderItem = React.memo(
({ item }: { item: LinkIncludingShortenedCollectionAndTags }) => {
return <LinkListing link={item} dashboard />;
}
);
interface SectionProps {
sectionData: { type: DashboardSectionType };
collection?: any;
@@ -100,7 +113,7 @@ export default function DashboardScreen() {
collectionLinks = [],
}) => {
switch (sectionData.type) {
case DashboardSectionType.STATS:
case "STATS":
return (
<View className="flex-col gap-4 max-w-full px-5">
<View className="flex-row gap-4">
@@ -134,7 +147,7 @@ export default function DashboardScreen() {
</View>
);
case DashboardSectionType.RECENT_LINKS:
case "RECENT_LINKS":
return (
<>
<View className="flex-row justify-between items-center px-5">
@@ -203,7 +216,7 @@ export default function DashboardScreen() {
</>
);
case DashboardSectionType.PINNED_LINKS:
case "PINNED_LINKS":
return (
<>
<View className="flex-row justify-between items-center px-5">
@@ -265,7 +278,7 @@ export default function DashboardScreen() {
</>
);
case DashboardSectionType.COLLECTION:
case "COLLECTION":
return collection?.id ? (
<>
<View className="flex-row justify-between items-center px-5">
@@ -331,12 +344,6 @@ export default function DashboardScreen() {
}
};
const RenderItem = React.memo(
({ item }: { item: LinkIncludingShortenedCollectionAndTags }) => {
return <LinkListing link={item} dashboard />;
}
);
return (
<SafeAreaView
style={styles.container}

View File

@@ -19,6 +19,7 @@ import useDataStore from "@/store/data";
import useAuthStore from "@/store/auth";
import { QueryClient } from "@tanstack/react-query";
import * as FileSystem from "expo-file-system";
import { KeyboardProvider } from "react-native-keyboard-controller";
const queryClient = new QueryClient({
defaultOptions: {
@@ -116,85 +117,87 @@ export default function RootLayout() {
<View
style={[{ flex: 1 }, colorScheme === "dark" ? darkTheme : lightTheme]}
>
<SheetProvider>
{!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:
<KeyboardProvider>
<SheetProvider>
{!isLoading && (
<Stack
screenOptions={{
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-200"],
headerShown: false,
contentStyle: {
backgroundColor:
rawTheme[colorScheme as ThemeName]["base-100"],
},
}),
}}
>
{/* <Stack.Screen name="(tabs)" /> */}
<Stack.Screen
name="links/[id]"
options={{
headerShown: true,
headerBackTitle: "Back",
headerTitle: "",
headerTintColor: colorScheme === "dark" ? "white" : "black",
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
headerStyle: {
backgroundColor:
colorScheme === "dark"
? rawTheme["dark"]["base-100"]
: "white",
},
}}
/>
<Stack.Screen
name="login"
options={{
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
...Platform.select({
android: {
statusBarStyle:
colorScheme === "light" ? "light" : "dark",
statusBarStyle: colorScheme === "dark" ? "light" : "dark",
statusBarBackgroundColor:
rawTheme[colorScheme as ThemeName]["primary"],
rawTheme[colorScheme as ThemeName]["base-100"],
},
}),
}}
/>
<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="(tabs)" /> */}
<Stack.Screen
name="links/[id]"
options={{
headerShown: true,
headerBackTitle: "Back",
headerTitle: "",
headerTintColor: colorScheme === "dark" ? "white" : "black",
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
headerStyle: {
backgroundColor:
colorScheme === "dark"
? rawTheme["dark"]["base-100"]
: "white",
},
}),
}}
/>
<Stack.Screen
name="incoming"
options={{
navigationBarColor:
rawTheme[colorScheme as ThemeName]["base-100"],
}}
/>
<Stack.Screen name="+not-found" />
</Stack>
)}
</SheetProvider>
}}
/>
<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="+not-found" />
</Stack>
)}
</SheetProvider>
</KeyboardProvider>
</View>
</PersistQueryClientProvider>
);

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState } from "react";
import {
View,
ActivityIndicator,
@@ -12,7 +12,6 @@ import NetInfo from "@react-native-community/netinfo";
import useAuthStore from "@/store/auth";
import { useLocalSearchParams, useRouter } from "expo-router";
import { useUser } from "@linkwarden/router/user";
import { generateLinkHref } from "@linkwarden/lib/generateLinkHref";
import { useWindowDimensions } from "react-native";
import RenderHtml from "@linkwarden/react-native-render-html";
import ElementNotSupported from "@/components/ElementNotSupported";
@@ -70,6 +69,7 @@ export default function LinkScreen() {
}, [user, link]);
async function fetchLinkData() {
// readable
if (link?.id && format === "3") {
const apiUrl = `${auth.instance}/api/v1/archives/${link.id}?format=${format}`;
setUrl(apiUrl);
@@ -87,11 +87,15 @@ export default function LinkScreen() {
} finally {
setIsLoading(false);
}
} else if (link?.id && !format && user) {
setUrl(
generateLinkHref(link, { ...user, password: "" }, auth.instance, true)
);
} else if (link?.id && format) {
}
// original
else if (link?.id && !format && user && link.url) {
setUrl(link.url);
}
// other formats
else if (link?.id && format) {
setUrl(`${auth.instance}/api/v1/archives/${link.id}?format=${format}`);
}
}

View File

@@ -8,6 +8,10 @@ import { useEffect, useState } from "react";
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,
KeyboardToolbar,
} from "react-native-keyboard-controller";
export default function HomeScreen() {
const { auth, signIn } = useAuthStore();
@@ -50,117 +54,134 @@ export default function HomeScreen() {
}
return (
<View className="flex-col justify-end h-full bg-primary relative">
<View className="my-auto">
<Image
source={require("@/assets/images/linkwarden.png")}
className="w-[120px] h-[120px] mx-auto"
/>
</View>
<Text className="text-base-100 text-7xl font-bold ml-8">Login</Text>
<View>
<Text className="text-base-100 text-2xl mx-8 mt-3" numberOfLines={1}>
Login to{" "}
{form.instance === "https://cloud.linkwarden.app"
? "cloud.linkwarden.app"
: form.instance}
</Text>
<TouchableOpacity
onPress={() => {
if (showInstanceField) {
setForm({ ...form, instance: "https://cloud.linkwarden.app" });
}
setShowInstanceField(!showInstanceField);
}}
className="mx-8 mt-1 self-start"
>
<Text className="text-neutral-content text-sm">
{!showInstanceField ? "Change server" : "Use official server"}
</Text>
</TouchableOpacity>
</View>
<Svg
viewBox="0 0 1440 320"
width={Dimensions.get("screen").width}
height={100}
<>
<KeyboardAwareScrollView
bottomOffset={62}
contentContainerClassName="flex-col justify-end h-full bg-base-100 relative"
>
<Path
fill={rawTheme[colorScheme as ThemeName]["base-100"]}
fill-opacity="1"
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">
{showInstanceField && (
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Instance URL"
selectTextOnFocus={false}
value={form.instance}
onChangeText={(text) => setForm({ ...form, instance: text })}
/>
)}
{method === "password" ? (
<>
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Email or Username"
value={form.user}
onChangeText={(text) => setForm({ ...form, user: text })}
<View className="flex-col justify-end h-full bg-primary relative">
<View className="my-auto">
<Image
source={require("@/assets/images/linkwarden.png")}
className="w-[120px] h-[120px] mx-auto"
/>
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Password"
secureTextEntry
value={form.password}
onChangeText={(text) => setForm({ ...form, password: text })}
</View>
<Text className="text-base-100 text-7xl font-bold ml-8">Login</Text>
<View>
<Text
className="text-base-100 text-2xl mx-8 mt-3"
numberOfLines={1}
>
Login to{" "}
{form.instance === "https://cloud.linkwarden.app"
? "cloud.linkwarden.app"
: form.instance}
</Text>
<TouchableOpacity
onPress={() => {
if (showInstanceField) {
setForm({
...form,
instance: "https://cloud.linkwarden.app",
});
}
setShowInstanceField(!showInstanceField);
}}
className="mx-8 mt-1 self-start"
>
<Text className="text-neutral-content text-sm">
{!showInstanceField ? "Change server" : "Use official server"}
</Text>
</TouchableOpacity>
</View>
<Svg
viewBox="0 0 1440 320"
width={Dimensions.get("screen").width}
height={100}
>
<Path
fill={rawTheme[colorScheme as ThemeName]["base-100"]}
fill-opacity="1"
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"
/>
</>
) : (
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Access Token"
secureTextEntry
value={form.token}
onChangeText={(text) => setForm({ ...form, token: text })}
/>
)}
</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">
{showInstanceField && (
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Instance URL"
selectTextOnFocus={false}
value={form.instance}
onChangeText={(text) => setForm({ ...form, instance: text })}
/>
)}
{method === "password" ? (
<>
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Email or Username"
value={form.user}
onChangeText={(text) => setForm({ ...form, user: text })}
/>
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Password"
secureTextEntry
value={form.password}
onChangeText={(text) => setForm({ ...form, password: text })}
/>
</>
) : (
<Input
className="w-full text-xl p-3 leading-tight h-12"
textAlignVertical="center"
placeholder="Access Token"
secureTextEntry
value={form.token}
onChangeText={(text) => setForm({ ...form, token: text })}
/>
)}
<TouchableOpacity
onPress={() =>
setMethod(method === "password" ? "token" : "password")
}
className="w-fit mx-auto"
>
<Text className="text-primary w-fit text-center">
{method === "password"
? "Login with Access Token"
: "Login with Username/Password"}
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() =>
setMethod(method === "password" ? "token" : "password")
}
className="w-fit mx-auto"
>
<Text className="text-primary w-fit text-center">
{method === "password"
? "Login with Access Token"
: "Login with Username/Password"}
</Text>
</TouchableOpacity>
<Button
variant="accent"
size="lg"
onPress={() => {
if (((form.user && form.password) || form.token) && form.instance) {
signIn(form.user, form.password, form.instance, form.token);
}
}}
>
<Text className="text-white text-xl">Login</Text>
</Button>
<TouchableOpacity
className="w-fit mx-auto"
onPress={() => SheetManager.show("support-sheet")}
>
<Text className="text-neutral text-center w-fit">Need help?</Text>
</TouchableOpacity>
</View>
</View>
<Button
variant="accent"
size="lg"
onPress={() => {
if (
((form.user && form.password) || form.token) &&
form.instance
) {
signIn(form.user, form.password, form.instance, form.token);
}
}}
>
<Text className="text-white text-xl">Login</Text>
</Button>
<TouchableOpacity
className="w-fit mx-auto"
onPress={() => SheetManager.show("support-sheet")}
>
<Text className="text-neutral text-center w-fit">Need help?</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAwareScrollView>
<KeyboardToolbar />
</>
);
}

30
apps/mobile/eas.json Normal file
View File

@@ -0,0 +1,30 @@
{
"cli": {
"version": ">= 16.20.1",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"simulator": {
"extends": "development",
"ios": {
"simulator": true
}
},
"production": {
"distribution": "store",
"autoIncrement": true,
"channel": "production"
}
},
"submit": {
"production": {
"ios": {
"ascAppId": "6752550960"
}
}
}
}

View File

@@ -16,6 +16,7 @@
"dependencies": {
"@expo/vector-icons": "^14.0.2",
"@linkwarden/lib": "*",
"@linkwarden/prisma": "*",
"@linkwarden/react-native-render-html": "^6.3.4",
"@linkwarden/router": "*",
"@linkwarden/types": "*",
@@ -30,6 +31,7 @@
"expo": "~52.0.18",
"expo-application": "~6.0.2",
"expo-blur": "~14.0.1",
"expo-build-properties": "~0.13.3",
"expo-clipboard": "~7.0.1",
"expo-constants": "~17.0.3",
"expo-dev-client": "~5.0.6",
@@ -44,6 +46,7 @@
"expo-status-bar": "~2.0.0",
"expo-symbols": "~0.2.0",
"expo-system-ui": "~4.0.6",
"expo-updates": "~0.27.4",
"expo-web-browser": "~14.0.1",
"html-entities": "^2.6.0",
"lucide-react-native": "^0.536.0",
@@ -53,8 +56,9 @@
"react-native": "0.76.9",
"react-native-actions-sheet": "^0.9.7",
"react-native-gesture-handler": "~2.20.2",
"react-native-ios-context-menu": "3.1.0",
"react-native-ios-utilities": "5.1.2",
"react-native-ios-context-menu": "3.1.3",
"react-native-ios-utilities": "5.1.7",
"react-native-keyboard-controller": "^1.19.0",
"react-native-mmkv": "^3.2.0",
"react-native-reanimated": "3.16.2",
"react-native-safe-area-context": "4.12.0",

View File

@@ -2064,7 +2064,7 @@
wrap-ansi "^7.0.0"
ws "^8.12.1"
"@expo/code-signing-certificates@^0.0.5":
"@expo/code-signing-certificates@0.0.5", "@expo/code-signing-certificates@^0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz#a693ff684fb20c4725dade4b88a6a9f96b02496c"
integrity sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==
@@ -5148,7 +5148,7 @@ ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.9.0:
ajv@^8.0.0, ajv@^8.11.0, ajv@^8.9.0:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
@@ -7396,6 +7396,14 @@ expo-blur@~14.0.1:
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-14.0.3.tgz#656d6b2442bfbbfb2a6608c6bc1151b29bce6698"
integrity sha512-BL3xnqBJbYm3Hg9t/HjNjdeY7N/q8eK5tsLYxswWG1yElISWZmMvrXYekl7XaVCPfyFyz8vQeaxd7q74ZY3Wrw==
expo-build-properties@~0.13.3:
version "0.13.3"
resolved "https://registry.yarnpkg.com/expo-build-properties/-/expo-build-properties-0.13.3.tgz#6b96d0486148fca6e74e62c7c502c0a9990931aa"
integrity sha512-gw7AYP+YF50Gr912BedelRDTfR4GnUEn9p5s25g4nv0hTJGWpBZdCYR5/Oi2rmCHJXxBqhPjxzV7JRh72fntLg==
dependencies:
ajv "^8.11.0"
semver "^7.6.0"
expo-clipboard@~7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/expo-clipboard/-/expo-clipboard-7.0.1.tgz#31d61270e77a37d2a6b7ae9abf79e060497ef43b"
@@ -7450,6 +7458,11 @@ expo-dev-menu@6.0.25:
dependencies:
expo-dev-menu-interface "1.9.3"
expo-eas-client@~0.13.3:
version "0.13.3"
resolved "https://registry.yarnpkg.com/expo-eas-client/-/expo-eas-client-0.13.3.tgz#1535a99a224e360581c6253b0a1ea767e19815b8"
integrity sha512-t+1F1tiDocSot8iSnrn/CjTUMvVvPV2DpafSVcticpbSzMGybEN7wcamO1t18fK7WxGXpZE9gxtd80qwv/LLqQ==
expo-file-system@~18.0.12:
version "18.0.12"
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.12.tgz#6ceeeb0725f6c5faaf58112f18c073c2acfb3027"
@@ -7487,7 +7500,7 @@ expo-linking@~7.0.2, expo-linking@~7.0.5:
expo-constants "~17.0.5"
invariant "^2.2.4"
expo-manifests@~0.15.8:
expo-manifests@~0.15.7, expo-manifests@~0.15.8:
version "0.15.8"
resolved "https://registry.yarnpkg.com/expo-manifests/-/expo-manifests-0.15.8.tgz#15e7b7b99d764b40ca3e3f859a126c856e2d6206"
integrity sha512-VuIyaMfRfLZeETNsRohqhy1l7iZ7I+HKMPfZXVL2Yn17TT0WkOhZoq1DzYwPbOHPgp1Uk6phNa86EyaHrD2DLw==
@@ -7561,6 +7574,11 @@ expo-status-bar@~2.0.0:
resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.0.1.tgz#fc07726346dc30fbb68aadb0d7890b34fba42eee"
integrity sha512-AkIPX7jWHRPp83UBZ1iXtVvyr0g+DgBVvIXTtlmPtmUsm8Vq9Bb5IGj86PW8osuFlgoTVAg7HI/+Ok7yEYwiRg==
expo-structured-headers@~4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/expo-structured-headers/-/expo-structured-headers-4.0.0.tgz#85537ae6daec61ebfb214ede4107c8841c6e16d0"
integrity sha512-uPiwZjWq3AdFGgY52+I2nGPrNa6izxAglymPXHUZLekZW290GqIUOk7MBNDD4sg4JwUbSi3gdxEurpEvuq+FSg==
expo-symbols@~0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/expo-symbols/-/expo-symbols-0.2.2.tgz#753e55ab1fb8b8f3bdb66c0cfd954c390fa7f62b"
@@ -7581,6 +7599,26 @@ expo-updates-interface@~1.0.0:
resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-1.0.0.tgz#b98c66b800d29561c62409556948b2af3d5316e5"
integrity sha512-93oWtvULJOj+Pp+N/lpTcFfuREX1wNeHtp7Lwn8EbzYYmdn37MvZU3TPW2tYYCZuhzmKEXnUblYcruYoDu7IrQ==
expo-updates@~0.27.4:
version "0.27.4"
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.27.4.tgz#e1c017b285ae5eee1a82b38b10d05a19eef81aa4"
integrity sha512-0rg4L2fFPEjTR/qnZ9Te4Q4irVC8uvNcTZW1pWnWbadG1SLv2PKjS1MYX5BboKzC3ao0H7m++5TP3hWhNg9org==
dependencies:
"@expo/code-signing-certificates" "0.0.5"
"@expo/config" "~10.0.11"
"@expo/config-plugins" "~9.0.17"
"@expo/spawn-async" "^1.7.2"
arg "4.1.0"
chalk "^4.1.2"
expo-eas-client "~0.13.3"
expo-manifests "~0.15.7"
expo-structured-headers "~4.0.0"
expo-updates-interface "~1.0.0"
fast-glob "^3.3.2"
fbemitter "^3.0.0"
ignore "^5.3.1"
resolve-from "^5.0.0"
expo-web-browser@~14.0.1:
version "14.0.2"
resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.0.2.tgz#52d53947c42fdfb225e8c230418ffe508bcf98a7"
@@ -8528,6 +8566,11 @@ ignore@^5.2.0:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
ignore@^5.3.1:
version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
image-q@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/image-q/-/image-q-4.0.0.tgz#31e075be7bae3c1f42a85c469b4732c358981776"
@@ -11677,23 +11720,35 @@ react-native-helmet-async@2.0.4:
react-fast-compare "^3.2.2"
shallowequal "^1.1.0"
react-native-ios-context-menu@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-3.1.0.tgz#8e5b7054959e6d4629d9aa18e5fe51fc18ccc597"
integrity sha512-qdPSXMKUp5lDgmZeUPdv5sgBFhkFrIqma+zsnqJQYOvekb6Qs17yJy1Rqhrj0bJrwuduHzZX0aYbaA8whxqpDw==
react-native-ios-context-menu@3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-3.1.3.tgz#ac340cb4052ba7866bca19b365f8425801889139"
integrity sha512-p65JTOxL0D8TOgTgq3A7nVhr/hQuRTtlmsH/aQ7vaOgxY4Na/QVcEF9s4wHc7y+Rcmv84bi6V6DhqxGkFFLPmA==
dependencies:
"@dominicstop/ts-event-emitter" "^1.1.0"
react-native-ios-utilities@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/react-native-ios-utilities/-/react-native-ios-utilities-5.1.2.tgz#78ac76bc07d3464fe746194146c3dea6cff1080a"
integrity sha512-H566MdgC1x0vW6D8EKweno1yRj/gQHeWiK0cRmyRexnUSk7oIecy7l2uyxXLYWFs+yvB/YIdLm+s2c19lKGfaw==
react-native-ios-utilities@5.1.7:
version "5.1.7"
resolved "https://registry.yarnpkg.com/react-native-ios-utilities/-/react-native-ios-utilities-5.1.7.tgz#d9dfbeef45c8598a9fe32f0e2a2e3ff72deae8ec"
integrity sha512-tvJFBfjYHcYFN3PhJOPxU71J+4fBKNWt65FlqxHApVTPcX2xxnrqvSHj7i1qHkjNG7bLDSsTotr+d1gIrXBzQg==
react-native-is-edge-to-edge@^1.1.6:
version "1.1.7"
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz#28947688f9fafd584e73a4f935ea9603bd9b1939"
integrity sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==
react-native-is-edge-to-edge@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz#64e10851abd9d176cbf2b40562f751622bde3358"
integrity sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==
react-native-keyboard-controller@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.19.0.tgz#c74e8fe2ccc98e5ba15c1dcb659ed5fdb8d3190f"
integrity sha512-G/SPwip7XWObjHLLpMB83pQoki8wQh0dPL4c+uEqxmaDUznnHvkiGuSTz0FWY0UUCeClw/iJ31DirMp6jBC2Jw==
dependencies:
react-native-is-edge-to-edge "^1.2.1"
react-native-mmkv@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-3.2.0.tgz#460723eb23b9cc92c65b0416eae9874a6ddf5b82"