add register and login screens

This commit is contained in:
Jorrin
2024-04-20 00:11:02 +02:00
parent fcfd0d99cc
commit bbeb729156
20 changed files with 555 additions and 103 deletions

View File

@@ -41,6 +41,7 @@
"expo-av": "~13.10.5", "expo-av": "~13.10.5",
"expo-brightness": "~11.8.0", "expo-brightness": "~11.8.0",
"expo-build-properties": "~0.11.1", "expo-build-properties": "~0.11.1",
"expo-clipboard": "^5.0.1",
"expo-constants": "~15.4.5", "expo-constants": "~15.4.5",
"expo-file-system": "~16.0.8", "expo-file-system": "~16.0.8",
"expo-haptics": "~12.8.1", "expo-haptics": "~12.8.1",

View File

@@ -55,7 +55,7 @@ const TestDownloadButton = (props: {
<MaterialCommunityIcons <MaterialCommunityIcons
name="download" name="download"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={async () => { onPress={async () => {

View File

@@ -1,53 +1,12 @@
import { Link } from "expo-router"; import { Link } from "expo-router";
import { MaterialCommunityIcons } from "@expo/vector-icons"; import { H3, H5, Paragraph, View } from "tamagui";
import { H2, H5, Paragraph, useTheme, View } from "tamagui";
import ScreenLayout from "~/components/layout/ScreenLayout"; import ScreenLayout from "~/components/layout/ScreenLayout";
import { MWButton } from "~/components/ui/Button"; import { MWButton } from "~/components/ui/Button";
import { MWCard } from "~/components/ui/Card"; import { MWCard } from "~/components/ui/Card";
import { MWInput } from "~/components/ui/Input"; import { MWInput } from "~/components/ui/Input";
import { useAuth } from "~/hooks/useAuth";
import { useAuthStore } from "~/stores/settings"; import { useAuthStore } from "~/stores/settings";
function TestButtons() {
const theme = useTheme();
const { login } = useAuth();
return (
<View>
<MWButton
type="secondary"
backgroundColor="$sheetItemBackground"
marginBottom="$4"
icon={
<MaterialCommunityIcons
name="login"
size={24}
color={theme.buttonSecondaryText.val}
/>
}
onPress={async () => {
const passhphrase = "";
if (!passhphrase) {
alert("Please configure your passphrase");
return;
}
const account = await login({
mnemonic: passhphrase,
userData: {
device: "phone",
},
});
console.log(account);
}}
>
test login
</MWButton>
</View>
);
}
export default function MovieWebScreen() { export default function MovieWebScreen() {
const { backendUrl, setBackendUrl } = useAuthStore(); const { backendUrl, setBackendUrl } = useAuthStore();
@@ -59,16 +18,15 @@ export default function MovieWebScreen() {
justifyContent: "center", justifyContent: "center",
}} }}
> >
<TestButtons /> <MWCard bordered padded>
<MWCard bordered> <MWCard.Header>
<MWCard.Header padded> <H3 fontWeight="$bold" paddingBottom="$1">
<H2 fontWeight="$bold" paddingBottom="$1">
Sync to the cloud Sync to the cloud
</H2> </H3>
<H5 color="$ash50" fontWeight="$semibold" paddingVertical="$3"> <H5 color="$shade200" fontWeight="$semibold" paddingVertical="$3">
Share your watch progress between devices and keep them synced. Share your watch progress between devices and keep them synced.
</H5> </H5>
<Paragraph color="$ash50"> <Paragraph color="$shade200">
First choose the backend you want to use. If you do not know what First choose the backend you want to use. If you do not know what
this does, use the default and click on &apos;Get started&apos;. this does, use the default and click on &apos;Get started&apos;.
</Paragraph> </Paragraph>
@@ -77,24 +35,24 @@ export default function MovieWebScreen() {
<View padding="$4"> <View padding="$4">
<MWInput <MWInput
placeholder={backendUrl} placeholder={backendUrl}
type="search" type="authentication"
value={backendUrl} value={backendUrl}
onChangeText={setBackendUrl} onChangeText={setBackendUrl}
/> />
</View> </View>
<MWCard.Footer padded justifyContent="center"> <MWCard.Footer padded justifyContent="center">
<MWButton type="purple"> <Link
<Link href={{
href={{ pathname: "/sync/trust/[url]",
pathname: "/sync/trust/[url]", params: { url: backendUrl },
params: { url: backendUrl }, }}
}} asChild
style={{ color: "white", fontWeight: "bold" }} >
> <MWButton type="purple" width="100%">
Get started Get started
</Link> </MWButton>
</MWButton> </Link>
</MWCard.Footer> </MWCard.Footer>
</MWCard> </MWCard>
</ScreenLayout> </ScreenLayout>

View File

@@ -206,7 +206,7 @@ export default function SettingsScreen() {
android: "android", android: "android",
})} })}
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
iconAfter={ iconAfter={
@@ -229,7 +229,7 @@ export default function SettingsScreen() {
<MaterialCommunityIcons <MaterialCommunityIcons
name="broom" name="broom"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => clearCacheDirectory()} onPress={() => clearCacheDirectory()}
@@ -304,7 +304,7 @@ export function UpdateSheet({
<MaterialCommunityIcons <MaterialCommunityIcons
name={Platform.select({ ios: "apple", android: "android" })} name={Platform.select({ ios: "apple", android: "android" })}
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => WebBrowser.openBrowserAsync(downloadUrl)} onPress={() => WebBrowser.openBrowserAsync(downloadUrl)}

View File

@@ -0,0 +1,79 @@
import { Stack } from "expo-router";
import { H4, Label, Paragraph, Text, YStack } from "tamagui";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { MWButton } from "~/components/ui/Button";
import { MWCard } from "~/components/ui/Card";
import { MWInput } from "~/components/ui/Input";
export default function Page() {
return (
<ScreenLayout
showHeader={false}
contentContainerStyle={{
flexGrow: 1,
alignItems: "center",
justifyContent: "center",
}}
>
<Stack.Screen
options={{
title: "",
}}
/>
<MWCard bordered padded>
<MWCard.Header>
<H4 fontWeight="$bold" textAlign="center">
Login to your account
</H4>
<Paragraph
color="$ash50"
textAlign="center"
fontWeight="$semibold"
paddingVertical="$4"
>
Please enter your passphrase to login to your account
</Paragraph>
</MWCard.Header>
<YStack paddingBottom="$5">
<YStack gap="$1">
<Label fontWeight="$bold">12-Word passphrase</Label>
<MWInput
type="authentication"
placeholder="Passphrase"
secureTextEntry
autoCorrect={false}
/>
</YStack>
<YStack gap="$1">
<Label fontWeight="$bold">Device name</Label>
<MWInput
type="authentication"
placeholder="Personal phone"
autoCorrect={false}
/>
</YStack>
</YStack>
<MWCard.Footer
padded
justifyContent="center"
flexDirection="column"
gap="$4"
>
<MWButton type="purple">Login</MWButton>
<Paragraph color="$ash50" textAlign="center" fontWeight="$semibold">
Don&apos;t have an account yet?{"\n"}
<Text color="$purple100" fontWeight="$bold">
Create an account.
</Text>
</Paragraph>
</MWCard.Footer>
</MWCard>
</ScreenLayout>
);
}

View File

@@ -0,0 +1,190 @@
import { useState } from "react";
import { Link, Stack } from "expo-router";
import { FontAwesome6, Ionicons } from "@expo/vector-icons";
import { Circle, H4, Label, Paragraph, View, XStack, YStack } from "tamagui";
import { LinearGradient } from "tamagui/linear-gradient";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { MWButton } from "~/components/ui/Button";
import { MWCard } from "~/components/ui/Card";
import { MWInput } from "~/components/ui/Input";
const colors = ["#0A54FF", "#CF2E68", "#F9DD7F", "#7652DD", "#2ECFA8"] as const;
function ColorPicker(props: {
value: (typeof colors)[number];
onInput: (v: (typeof colors)[number]) => void;
}) {
return (
<XStack gap="$2">
{colors.map((color) => {
return (
<View
onPress={() => props.onInput(color)}
flexGrow={1}
height="$4"
borderRadius="$4"
justifyContent="center"
alignItems="center"
backgroundColor={color}
key={color}
>
{props.value === color ? (
<Ionicons name="checkmark-circle" size={24} color="white" />
) : null}
</View>
);
})}
</XStack>
);
}
const icons = [
"user-group",
"couch",
"mobile-screen",
"ticket",
"handcuffs",
] as const;
function UserIconPicker(props: {
value: (typeof icons)[number];
onInput: (v: (typeof icons)[number]) => void;
}) {
return (
<XStack gap="$2">
{icons.map((icon) => {
return (
<View
flexGrow={1}
height="$4"
borderRadius="$4"
justifyContent="center"
alignItems="center"
backgroundColor={props.value === icon ? "$purple400" : "$shade400"}
borderColor={props.value === icon ? "$purple200" : "$shade400"}
borderWidth={1}
key={icon}
onPress={() => props.onInput(icon)}
>
<FontAwesome6 name={icon} size={24} color="white" />
</View>
);
})}
</XStack>
);
}
interface AvatarProps {
colorA: string;
colorB: string;
icon: (typeof icons)[number];
}
export function Avatar(props: AvatarProps) {
return (
<Circle
backgroundColor={props.colorA}
height="$6"
width="$6"
justifyContent="center"
alignItems="center"
>
<LinearGradient
colors={[props.colorA, props.colorB]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
borderRadius="$12"
width="100%"
height="100%"
justifyContent="center"
alignItems="center"
>
<FontAwesome6 name={props.icon} size={24} color="white" />
</LinearGradient>
</Circle>
);
}
export default function Page() {
const [color, setColor] = useState<(typeof colors)[number]>(colors[0]);
const [color2, setColor2] = useState<(typeof colors)[number]>(colors[0]);
const [icon, setIcon] = useState<(typeof icons)[number]>(icons[0]);
return (
<ScreenLayout
showHeader={false}
contentContainerStyle={{
flexGrow: 1,
alignItems: "center",
justifyContent: "center",
}}
>
<Stack.Screen
options={{
title: "",
}}
/>
<MWCard bordered padded>
<MWCard.Header>
<View alignItems="center" marginBottom="$3">
<Avatar colorA={color} colorB={color2} icon={icon} />
</View>
<H4 fontWeight="$bold" textAlign="center">
Account information
</H4>
<Paragraph
color="$shade200"
textAlign="center"
fontWeight="$normal"
paddingTop="$4"
>
Enter a name for your device and pick colours and a user icon of
your choosing
</Paragraph>
</MWCard.Header>
<YStack paddingBottom="$5">
<YStack gap="$1">
<Label fontWeight="$bold">Device name</Label>
<MWInput
type="authentication"
placeholder="Passphrase"
secureTextEntry
autoCorrect={false}
/>
</YStack>
<YStack gap="$1">
<Label fontWeight="$bold">Profile color one</Label>
<ColorPicker value={color} onInput={(color) => setColor(color)} />
</YStack>
<YStack gap="$1">
<Label fontWeight="$bold">Profile color two</Label>
<ColorPicker value={color2} onInput={(color) => setColor2(color)} />
</YStack>
<YStack gap="$1">
<Label fontWeight="$bold">User icon</Label>
<UserIconPicker value={icon} onInput={(icon) => setIcon(icon)} />
</YStack>
</YStack>
<MWCard.Footer justifyContent="center" flexDirection="column" gap="$4">
<Link
href={{
pathname: "/sync/register/confirm",
}}
replace
asChild
>
<MWButton type="purple" width="100%">
Next
</MWButton>
</Link>
</MWCard.Footer>
</MWCard>
</ScreenLayout>
);
}

View File

@@ -0,0 +1,68 @@
import { Link, Stack } from "expo-router";
import { H4, Label, Paragraph, YStack } from "tamagui";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { MWButton } from "~/components/ui/Button";
import { MWCard } from "~/components/ui/Card";
import { MWInput } from "~/components/ui/Input";
export default function Page() {
return (
<ScreenLayout
showHeader={false}
contentContainerStyle={{
flexGrow: 1,
alignItems: "center",
justifyContent: "center",
}}
>
<Stack.Screen
options={{
title: "",
}}
/>
<MWCard bordered padded>
<MWCard.Header>
<H4 fontWeight="$bold" textAlign="center">
Confirm your passphrase
</H4>
<Paragraph
color="$shade200"
textAlign="center"
fontWeight="$normal"
paddingTop="$4"
>
Please enter your passphrase from earlier to confirm you have saved
it and to create your account
</Paragraph>
</MWCard.Header>
<YStack paddingBottom="$5">
<YStack gap="$1">
<Label fontWeight="$bold">12-Word passphrase</Label>
<MWInput
type="authentication"
placeholder="Passphrase"
secureTextEntry
autoCorrect={false}
/>
</YStack>
</YStack>
<MWCard.Footer justifyContent="center" flexDirection="column" gap="$4">
<Link
href={{
pathname: "/(tabs)/movie-web",
}}
replace
asChild
>
<MWButton type="purple">Create account</MWButton>
</Link>
</MWCard.Footer>
</MWCard>
</ScreenLayout>
);
}

View File

@@ -0,0 +1,132 @@
import { TouchableOpacity } from "react-native-gesture-handler";
import * as Clipboard from "expo-clipboard";
import { Link, Stack } from "expo-router";
import { Feather } from "@expo/vector-icons";
import { H4, Paragraph, Text, useTheme, View, XStack, YStack } from "tamagui";
import { genMnemonic } from "@movie-web/api";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { MWButton } from "~/components/ui/Button";
import { MWCard } from "~/components/ui/Card";
function PassphraseWord({ word }: { word: string }) {
return (
<View
width="$10"
borderRadius="$4"
paddingHorizontal="$4"
paddingVertical="$3"
alignItems="center"
justifyContent="center"
backgroundColor="$shade400"
>
<Text fontWeight="$bold">{word}</Text>
</View>
);
}
export default function Page() {
const theme = useTheme();
const words = genMnemonic().split(" ");
return (
<ScreenLayout
showHeader={false}
contentContainerStyle={{
flexGrow: 1,
alignItems: "center",
justifyContent: "center",
}}
>
<Stack.Screen
options={{
title: "",
}}
/>
<MWCard bordered padded>
<MWCard.Header>
<H4 fontWeight="$bold" textAlign="center">
Your passphrase
</H4>
<Paragraph
color="$shade200"
textAlign="center"
fontWeight="$normal"
paddingTop="$4"
>
Your passphrase acts as your username and password. Make sure to
keep it safe as you will need to enter it to login to your account
</Paragraph>
</MWCard.Header>
<YStack
borderRadius="$4"
borderColor="$shade200"
borderWidth="$0.5"
marginBottom="$4"
>
<XStack
gap="$1"
borderBottomWidth="$0.5"
borderColor="$shade200"
paddingVertical="$2"
paddingHorizontal="$4"
>
<Text fontWeight="$bold" flexGrow={1}>
Passphrase
</Text>
<TouchableOpacity
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
}}
onPress={async () => {
await Clipboard.setStringAsync(words.join(""));
}}
>
<Feather name="copy" size={18} color={theme.shade200.val} />
<Text color="$shade200" fontWeight="$bold">
Copy
</Text>
</TouchableOpacity>
</XStack>
<View
flexWrap="wrap"
flexDirection="row"
gap="$4"
alignItems="center"
justifyContent="center"
padding="$3"
>
{words.map((word, index) => (
<PassphraseWord key={index} word={word} />
))}
</View>
</YStack>
<MWCard.Footer justifyContent="center" flexDirection="column" gap="$4">
<Link
href={{
pathname: "/sync/register/account",
}}
asChild
>
<MWButton type="purple">I have saved my passphrase</MWButton>
</Link>
<Paragraph color="$ash50" textAlign="center" fontWeight="$semibold">
Already have an account?{"\n"}
<Text color="$purple100" fontWeight="$bold">
<Link href="/sync/login">Login here</Link>
</Text>
</Paragraph>
</MWCard.Footer>
</MWCard>
</ScreenLayout>
);
}

View File

@@ -1,4 +1,4 @@
import { Stack, useLocalSearchParams } from "expo-router"; import { Link, Stack, useLocalSearchParams } from "expo-router";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { H4, Paragraph, Text, View } from "tamagui"; import { H4, Paragraph, Text, View } from "tamagui";
@@ -30,7 +30,7 @@ export default function Page() {
title: "", title: "",
}} }}
/> />
<MWCard bordered> <MWCard bordered padded>
<MWCard.Header padded> <MWCard.Header padded>
<H4 fontWeight="$bold" textAlign="center"> <H4 fontWeight="$bold" textAlign="center">
Do you trust this server? Do you trust this server?
@@ -90,15 +90,23 @@ export default function Page() {
flexDirection="column" flexDirection="column"
gap="$4" gap="$4"
> >
<MWButton type="purple">I trust this server</MWButton> <Link
<MWButton type="cancel">Go back</MWButton> href={{
pathname: "/sync/register",
<Paragraph color="$ash50" textAlign="center" fontWeight="$semibold"> }}
Already have an account?{" "} asChild
<Text color="$purple100" fontWeight="$bold"> >
Login here <MWButton type="purple">I trust this server</MWButton>
</Text> </Link>
</Paragraph> <Link
href={{
pathname: "/(tabs)/",
}}
replace
asChild
>
<MWButton type="cancel">Go back</MWButton>
</Link>
</MWCard.Footer> </MWCard.Footer>
</MWCard> </MWCard>
</ScreenLayout> </ScreenLayout>

View File

@@ -58,7 +58,7 @@ export const AudioTrackSelector = () => {
<MaterialCommunityIcons <MaterialCommunityIcons
name="volume-high" name="volume-high"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => setOpen(true)} onPress={() => setOpen(true)}

View File

@@ -65,7 +65,7 @@ export const CaptionsSelector = () => {
<MaterialCommunityIcons <MaterialCommunityIcons
name="subtitles" name="subtitles"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => setOpen(true)} onPress={() => setOpen(true)}

View File

@@ -38,7 +38,7 @@ export const DownloadButton = () => {
<MaterialCommunityIcons <MaterialCommunityIcons
name="download" name="download"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => onPress={() =>

View File

@@ -47,7 +47,7 @@ const EpisodeSelector = ({
<Ionicons <Ionicons
name="arrow-back" name="arrow-back"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
onPress={() => { onPress={() => {
setSelectedSeason(null); setSelectedSeason(null);
props.onOpenChange?.(false); props.onOpenChange?.(false);
@@ -119,7 +119,7 @@ export const SeasonSelector = () => {
<MaterialCommunityIcons <MaterialCommunityIcons
name="audio-video" name="audio-video"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => setOpen(true)} onPress={() => setOpen(true)}

View File

@@ -23,7 +23,7 @@ export const SettingsSelector = () => {
<MaterialIcons <MaterialIcons
name="display-settings" name="display-settings"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => setOpen(true)} onPress={() => setOpen(true)}

View File

@@ -102,7 +102,7 @@ const EmbedsPart = ({
<Ionicons <Ionicons
name="arrow-back" name="arrow-back"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
onPress={() => { onPress={() => {
props.onOpenChange?.(false); props.onOpenChange?.(false);
}} }}
@@ -160,7 +160,7 @@ export const SourceSelector = () => {
<MaterialCommunityIcons <MaterialCommunityIcons
name="video" name="video"
size={24} size={24}
color={theme.buttonSecondaryText.val} color={theme.silver300.val}
/> />
} }
onPress={() => setOpen(true)} onPress={() => setOpen(true)}

View File

@@ -1,7 +1,7 @@
import { Card, styled, withStaticProperties } from "tamagui"; import { Card, styled, withStaticProperties } from "tamagui";
export const MWCardFrame = styled(Card, { export const MWCardFrame = styled(Card, {
backgroundColor: "$shade400", backgroundColor: "$shade600",
borderColor: "$shade400", borderColor: "$shade400",
variants: { variants: {

View File

@@ -6,21 +6,37 @@ export const MWInput = styled(Input, {
variants: { variants: {
type: { type: {
default: { default: {
backgroundColor: "$inputBackground", backgroundColor: "$ash600",
color: "$inputText", color: "$ash100",
placeholderTextColor: "$placeHolderText", placeholderTextColor: "$ash200",
borderColor: "$inputBorder", borderColor: "$ash500",
outlineStyle: "none", outlineStyle: "none",
focusStyle: {
borderColor: "$ash300",
},
}, },
search: { search: {
backgroundColor: "$searchBackground", backgroundColor: "$shade500",
color: "$shade100",
borderColor: "$colorTransparent", borderColor: "$colorTransparent",
placeholderTextColor: "$searchPlaceholder", placeholderTextColor: "$shade100",
outlineStyle: "none", outlineStyle: "none",
focusStyle: { focusStyle: {
borderColor: "$colorTransparent", borderColor: "$colorTransparent",
}, },
}, },
authentication: {
backgroundColor: "$shade500",
color: "$shade100",
placeholderTextColor: "$shade400",
outlineStyle: "none",
focusStyle: {
borderColor: "$shade300",
},
pressStyle: {
backgroundColor: "$shade500",
},
},
}, },
}, },
}); });

View File

@@ -71,7 +71,7 @@ export function useAuth() {
backendUrl, backendUrl,
publicKeyBase64Url, publicKeyBase64Url,
); );
const signature = await signChallenge(keys, challenge); const signature = signChallenge(keys, challenge);
const loginResult = await loginAccount(backendUrl, { const loginResult = await loginAccount(backendUrl, {
challenge: { challenge: {
code: challenge, code: challenge,
@@ -110,7 +110,7 @@ export function useAuth() {
registerData.recaptchaToken, registerData.recaptchaToken,
); );
const keys = await keysFromMnemonic(registerData.mnemonic); const keys = await keysFromMnemonic(registerData.mnemonic);
const signature = await signChallenge(keys, challenge); const signature = signChallenge(keys, challenge);
const registerResult = await registerAccount(backendUrl, { const registerResult = await registerAccount(backendUrl, {
challenge: { challenge: {
code: challenge, code: challenge,

View File

@@ -57,17 +57,6 @@ const createThemeConfig = (tokens: Tokens) => ({
loadingIndicator: tokens.purple.c200, loadingIndicator: tokens.purple.c200,
buttonSecondaryBackground: tokens.ash.c700,
buttonSecondaryText: tokens.semantic.silver.c300,
buttonSecondaryBackgroundHover: tokens.ash.c700,
buttonPrimaryBackground: tokens.white,
buttonPrimaryText: tokens.black,
buttonPrimaryBackgroundHover: tokens.semantic.silver.c100,
buttonPurpleBackground: tokens.purple.c500,
buttonPurpleBackgroundHover: tokens.purple.c400,
buttonCancelBackground: tokens.ash.c500,
buttonCancelBackgroundHover: tokens.ash.c300,
switchActiveTrackColor: tokens.purple.c300, switchActiveTrackColor: tokens.purple.c300,
switchInactiveTrackColor: tokens.ash.c500, switchInactiveTrackColor: tokens.ash.c500,
switchThumbColor: tokens.white, switchThumbColor: tokens.white,

11
pnpm-lock.yaml generated
View File

@@ -95,6 +95,9 @@ importers:
expo-build-properties: expo-build-properties:
specifier: ~0.11.1 specifier: ~0.11.1
version: 0.11.1(expo@50.0.14) version: 0.11.1(expo@50.0.14)
expo-clipboard:
specifier: ^5.0.1
version: 5.0.1(expo@50.0.14)
expo-constants: expo-constants:
specifier: ~15.4.5 specifier: ~15.4.5
version: 15.4.5(expo@50.0.14) version: 15.4.5(expo@50.0.14)
@@ -7860,6 +7863,14 @@ packages:
semver: 7.5.4 semver: 7.5.4
dev: false dev: false
/expo-clipboard@5.0.1(expo@50.0.14):
resolution: {integrity: sha512-JH853QJPr5W3h87If3aDTnMK+ESSIrwzU2TdfZrqZttVDY2pMIf/w37mVHHNYodXM4ATHXadtOkjKbAa0DWwUg==}
peerDependencies:
expo: '*'
dependencies:
expo: 50.0.14(@babel/core@7.23.9)(@react-native/babel-preset@0.73.21)
dev: false
/expo-constants@15.4.5(expo@50.0.14): /expo-constants@15.4.5(expo@50.0.14):
resolution: {integrity: sha512-1pVVjwk733hbbIjtQcvUFCme540v4gFemdNlaxM2UXKbfRCOh2hzgKN5joHMOysoXQe736TTUrRj7UaZI5Yyhg==} resolution: {integrity: sha512-1pVVjwk733hbbIjtQcvUFCme540v4gFemdNlaxM2UXKbfRCOh2hzgKN5joHMOysoXQe736TTUrRj7UaZI5Yyhg==}
peerDependencies: peerDependencies: