mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-03 03:47:02 +00:00
feat(mobile): proper deployment
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
30
apps/mobile/eas.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
77
yarn.lock
77
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user