feat: userInfo

This commit is contained in:
jinyaqiu 2025-07-17 15:27:00 +08:00
parent 006db2af07
commit 521a4d0a51
9 changed files with 176 additions and 128 deletions

View File

@ -16,7 +16,7 @@ import { fetchApi } from '@/lib/server-api-util';
import { CountData, UserInfoDetails } from '@/types/user'; import { CountData, UserInfoDetails } from '@/types/user';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ScrollView, StyleSheet, View } from 'react-native'; import { FlatList, ScrollView, StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function OwnerPage() { export default function OwnerPage() {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
@ -59,54 +59,57 @@ export default function OwnerPage() {
return ( return (
<View style={[styles.container, { paddingTop: insets.top }]}> <View style={[styles.container, { paddingTop: insets.top }]}>
<ScrollView <FlatList
data={[]} // 空数据,因为我们只需要渲染一次
renderItem={null} // 不需要渲染项目
contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
contentContainerStyle={{ flexGrow: 1, gap: 16, marginHorizontal: 16 }} ListHeaderComponent={
> <View style={{ gap: 16 }}>
{/* 用户信息 */} {/* 用户信息 */}
<UserInfo userInfo={userInfoDetails} /> <UserInfo userInfo={userInfoDetails} />
{/* 设置栏 */} {/* 设置栏 */}
<AlbumComponent setModalVisible={setModalVisible} /> <AlbumComponent setModalVisible={setModalVisible} />
{/* 资源数据 */} {/* 资源数据 */}
<View style={styles.resourceContainer}> <View style={styles.resourceContainer}>
<ResourceComponent title={t("generalSetting.usedStorage", { ns: "personal" })} subtitle={`${countData?.counter?.total_count?.video_count || 0}videos/${countData?.counter?.total_count?.photo_count || 0}photos`} data={{ all: userInfoDetails.total_bytes, used: countData.used_bytes }} icon={<UsedStorageSvg />} style={{ flex: 1 }} isFormatBytes={true} /> <ResourceComponent title={t("generalSetting.usedStorage", { ns: "personal" })} subtitle={`${countData?.counter?.total_count?.video_count || 0}videos/${countData?.counter?.total_count?.photo_count || 0}photos`} data={{ all: userInfoDetails.total_bytes, used: countData.used_bytes }} icon={<UsedStorageSvg />} style={{ flex: 1 }} isFormatBytes={true} />
<ResourceComponent title={t("generalSetting.remainingPoints", { ns: "personal" })} data={{ all: userInfoDetails.total_points, used: userInfoDetails.remain_points }} icon={<PointsSvg />} style={{ flex: 1 }} /> <ResourceComponent title={t("generalSetting.remainingPoints", { ns: "personal" })} data={{ all: userInfoDetails.total_points, used: userInfoDetails.remain_points }} icon={<PointsSvg />} style={{ flex: 1 }} />
</View> </View>
{/* 数据统计 */} {/* 数据统计 */}
<CountComponent <CountComponent
data={[{ title: t("generalSetting.totalVideo", { ns: "personal" }), number: countData?.counter?.total_count?.video_count || 0 }, { title: t("generalSetting.totalPhoto", { ns: "personal" }), number: countData?.counter?.total_count?.photo_count || 0 }, { title: t("generalSetting.live", { ns: "personal" }), number: countData?.counter?.total_count?.live_count || 0 }, { title: t("generalSetting.videoLength", { ns: "personal" }), number: formatDuration(countData?.counter?.total_count?.video_length || 0) }]} data={[{ title: t("generalSetting.totalVideo", { ns: "personal" }), number: countData?.counter?.total_count?.video_count || 0 }, { title: t("generalSetting.totalPhoto", { ns: "personal" }), number: countData?.counter?.total_count?.photo_count || 0 }, { title: t("generalSetting.live", { ns: "personal" }), number: countData?.counter?.total_count?.live_count || 0 }, { title: t("generalSetting.videoLength", { ns: "personal" }), number: formatDuration(countData?.counter?.total_count?.video_length || 0) }]}
/> />
{/* 分类 */} {/* 分类 */}
<View style={{ height: 145 }}> <View style={{ height: 145 }}>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 16 }} > <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 16 }} >
{countData?.counter?.category_count && Object.entries(countData?.counter?.category_count).map(([key, value], index) => { {countData?.counter?.category_count && Object.entries(countData?.counter?.category_count).map(([key, value], index) => {
return ( return (
<CategoryComponent <CategoryComponent
key={index} key={index}
title={key} title={key}
data={[{ title: 'Video', number: value.video_count }, { title: 'Photo', number: value.photo_count }, { title: 'Length', number: formatDuration(value.video_length || 0) }]} data={[{ title: 'Video', number: value.video_count }, { title: 'Photo', number: value.photo_count }, { title: 'Length', number: formatDuration(value.video_length || 0) }]}
bgSvg={value.cover_url} bgSvg={value.cover_url}
style={{ aspectRatio: 1, flex: 1 }} style={{ aspectRatio: 1, flex: 1 }}
/> />
) )
})} })}
</ScrollView> </ScrollView>
</View> </View>
{/* 作品数据 */} {/* 作品数据 */}
<View className='flex flex-row justify-between gap-[1rem]'> <View className='flex flex-row justify-between gap-[1rem]'>
<CreateCountComponent title={t("generalSetting.storiesCreated", { ns: "personal" })} icon={<StoriesSvg />} number={userInfoDetails.stories_count} /> <CreateCountComponent title={t("generalSetting.storiesCreated", { ns: "personal" })} icon={<StoriesSvg />} number={userInfoDetails.stories_count} />
<CreateCountComponent title={t("generalSetting.conversationsWithMemo", { ns: "personal" })} icon={<ConversationsSvg />} number={userInfoDetails.conversations_count} /> <CreateCountComponent title={t("generalSetting.conversationsWithMemo", { ns: "personal" })} icon={<ConversationsSvg />} number={userInfoDetails.conversations_count} />
</View> </View>
{/* 排行榜 */}
<Ranking data={userInfoDetails.title_rankings} />
</ScrollView>
{/* 排行榜 */}
<Ranking data={userInfoDetails.title_rankings} />
</View>
}
/>
{/* 设置弹窗 */} {/* 设置弹窗 */}
<SettingModal modalVisible={modalVisible} setModalVisible={setModalVisible} userInfo={userInfoDetails.user_info} /> <SettingModal modalVisible={modalVisible} setModalVisible={setModalVisible} userInfo={userInfoDetails.user_info} />
@ -122,6 +125,9 @@ const styles = StyleSheet.create({
backgroundColor: 'white', backgroundColor: 'white',
paddingBottom: 86, paddingBottom: 86,
}, },
contentContainer: {
paddingHorizontal: 16,
},
resourceContainer: { resourceContainer: {
flexDirection: 'row', flexDirection: 'row',
gap: 16 gap: 16

38
components/copy.tsx Normal file
View File

@ -0,0 +1,38 @@
import Ionicons from '@expo/vector-icons/Ionicons';
import * as Clipboard from 'expo-clipboard';
import React, { useState } from 'react';
import { StyleSheet, TouchableOpacity } from 'react-native';
const CopyButton = ({ textToCopy }: { textToCopy: string }) => {
const [isCopied, setIsCopied] = useState(false);
const handleCopy = async () => {
await Clipboard.setStringAsync(textToCopy);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
};
return (
<TouchableOpacity onPress={handleCopy} style={styles.button}>
{isCopied ? (
<Ionicons name="checkmark-circle" size={12} color="#FFB645" />
) : (
<Ionicons name="copy-outline" size={12} color="#333" />
)}
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
flexDirection: 'row',
alignItems: 'center',
padding: 4,
},
text: {
marginLeft: 8,
fontSize: 16,
},
});
export default CopyButton;

View File

@ -7,6 +7,7 @@ import { useAuth } from "../../contexts/auth-context";
import { fetchApi } from "../../lib/server-api-util"; import { fetchApi } from "../../lib/server-api-util";
import { User } from "../../types/user"; import { User } from "../../types/user";
import { ThemedText } from "../ThemedText"; import { ThemedText } from "../ThemedText";
import PrivacyModal from "../owner/qualification/privacy";
interface LoginProps { interface LoginProps {
updateUrlParam: (status: string, value: string) => void; updateUrlParam: (status: string, value: string) => void;
@ -25,7 +26,9 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword }: Log
const [passwordsMatch, setPasswordsMatch] = useState(true); const [passwordsMatch, setPasswordsMatch] = useState(true);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [modalType, setModalType] = useState<'ai' | 'terms' | 'privacy' | 'user'>('ai');
// 协议弹窗
const [privacyModalVisible, setPrivacyModalVisible] = useState(false);
// 从 URL 参数中获取 task_id 和 steps // 从 URL 参数中获取 task_id 和 steps
const params = useLocalSearchParams<{ task_id?: string; steps?: string }>(); const params = useLocalSearchParams<{ task_id?: string; steps?: string }>();
const taskId = params.task_id; const taskId = params.task_id;
@ -263,10 +266,10 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword }: Log
<ThemedText className="text-sm !text-textPrimary"> <ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.agree", { ns: 'login' })} {t("auth.telLogin.agree", { ns: 'login' })}
</ThemedText> </ThemedText>
<TouchableOpacity onPress={() => router.push({ <TouchableOpacity onPress={() => {
pathname: '/agreement', setModalType('terms');
params: { type: 'service' } setPrivacyModalVisible(true);
} as any)}> }}>
<ThemedText className="text-sm !text-[#E2793F]"> <ThemedText className="text-sm !text-[#E2793F]">
{t("auth.telLogin.terms", { ns: 'login' })} {t("auth.telLogin.terms", { ns: 'login' })}
</ThemedText> </ThemedText>
@ -274,10 +277,10 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword }: Log
<ThemedText className="text-sm !text-textPrimary"> <ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.and", { ns: 'login' })} {t("auth.telLogin.and", { ns: 'login' })}
</ThemedText> </ThemedText>
<TouchableOpacity onPress={() => router.push({ <TouchableOpacity onPress={() => {
pathname: '/agreement', setModalType('privacy');
params: { type: 'privacy' } setPrivacyModalVisible(true);
} as any)}> }}>
<ThemedText className="text-sm !text-[#E2793F]"> <ThemedText className="text-sm !text-[#E2793F]">
{t("auth.telLogin.privacyPolicy", { ns: 'login' })} {t("auth.telLogin.privacyPolicy", { ns: 'login' })}
</ThemedText> </ThemedText>
@ -285,14 +288,25 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword }: Log
<ThemedText className="text-sm !text-textPrimary"> <ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.and", { ns: 'login' })} {t("auth.telLogin.and", { ns: 'login' })}
</ThemedText> </ThemedText>
<TouchableOpacity onPress={() => router.push({ <TouchableOpacity onPress={() => {
pathname: '/agreement', setModalType('user');
params: { type: 'user' } setPrivacyModalVisible(true);
} as any)}> }}>
<ThemedText className="text-sm !text-[#E2793F]"> <ThemedText className="text-sm !text-[#E2793F]">
{t("auth.telLogin.userAgreement", { ns: 'login' })} {t("auth.telLogin.userAgreement", { ns: 'login' })}
</ThemedText> </ThemedText>
</TouchableOpacity> </TouchableOpacity>
<ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.and", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => {
setModalType('ai');
setPrivacyModalVisible(true);
}}>
<ThemedText className="text-sm !text-[#E2793F]">
{t("auth.telLogin.aiAgreement", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText className="text-sm !text-textPrimary"> <ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.agreement", { ns: 'login' })} {t("auth.telLogin.agreement", { ns: 'login' })}
</ThemedText> </ThemedText>
@ -319,6 +333,9 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword }: Log
</ThemedText> </ThemedText>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{/* 协议弹窗 */}
<PrivacyModal modalVisible={privacyModalVisible} setModalVisible={setPrivacyModalVisible} type={modalType} />
</View> </View>
} }

View File

@ -1,7 +1,7 @@
import { fetchApi } from '@/lib/server-api-util'; import { fetchApi } from '@/lib/server-api-util';
import { Policy } from '@/types/personal-info'; import { Policy } from '@/types/personal-info';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Modal, Pressable, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import RenderHtml from 'react-native-render-html'; import RenderHtml from 'react-native-render-html';
const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, type: string }) => { const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, type: string }) => {
@ -62,12 +62,8 @@ const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible:
onRequestClose={() => { onRequestClose={() => {
setModalVisible(!modalVisible); setModalVisible(!modalVisible);
}}> }}>
<Pressable <View style={styles.centeredView}>
style={styles.centeredView} <View style={styles.modalView}>
onPress={() => setModalVisible(false)}>
<Pressable
style={styles.modalView}
onPress={(e) => e.stopPropagation()}>
<View style={styles.modalHeader}> <View style={styles.modalHeader}>
<Text style={{ opacity: 0 }}>Settings</Text> <Text style={{ opacity: 0 }}>Settings</Text>
<Text style={styles.modalTitle}>{type === 'ai' ? 'AI Policy' : type === 'terms' ? 'Terms of Service' : type === 'privacy' ? 'Privacy Policy' : 'User Agreement'}</Text> <Text style={styles.modalTitle}>{type === 'ai' ? 'AI Policy' : type === 'terms' ? 'Terms of Service' : type === 'privacy' ? 'Privacy Policy' : 'User Agreement'}</Text>
@ -85,9 +81,8 @@ const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible:
}} }}
/> />
</ScrollView> </ScrollView>
</Pressable> </View>
</Pressable> </View>
</Modal> </Modal>
); );
}; };

View File

@ -1,25 +1,26 @@
import UserSvg from '@/assets/icons/svg/ataver.svg'; import UserSvg from '@/assets/icons/svg/ataver.svg';
import { ThemedText } from '@/components/ThemedText'; import { ThemedText } from '@/components/ThemedText';
import { UserInfoDetails } from '@/types/user'; import { UserInfoDetails } from '@/types/user';
// import { Image } from 'expo-image'; import { useState } from 'react';
import { Image, ScrollView, View } from 'react-native'; import { Image, ScrollView, View } from 'react-native';
export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) { export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) {
// 添加状态来跟踪图片加载状态
const [imageError, setImageError] = useState(false);
return ( return (
<View className='flex flex-row items-center mt-[1rem] gap-[1rem]'> <View className='flex flex-row justify-between items-center mt-[1rem] gap-[1rem] w-full'>
{/* 用户名 */} {/* 用户名 */}
<View className='flex flex-col gap-4 w-[68vw]'> <View className='flex flex-col gap-4 w-[75%]'>
<View className='flex flex-row items-center justify-between w-full'> <View className='flex flex-row items-center justify-between w-full'>
<View className='flex flex-row items-center gap-2'> <View className='flex flex-row items-center gap-2 w-full'>
<ThemedText <ThemedText
className='max-w-[36vw] !text-textSecondary !font-semibold !text-2xl' className='max-w-[80%] !text-textSecondary !font-semibold !text-2xl'
numberOfLines={1} // 限制为1行 numberOfLines={1} // 限制为1行
ellipsizeMode="tail" ellipsizeMode="tail"
> >
{userInfo?.user_info?.nickname} {userInfo?.user_info?.nickname}
</ThemedText> </ThemedText>
<ScrollView <ScrollView
className='max-w-[26vw] ' className='max-w-[20%]'
horizontal // 水平滚动 horizontal // 水平滚动
showsHorizontalScrollIndicator={false} // 隐藏滚动条 showsHorizontalScrollIndicator={false} // 隐藏滚动条
contentContainerStyle={{ contentContainerStyle={{
@ -40,29 +41,39 @@ export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) {
</ScrollView> </ScrollView>
</View> </View>
</View> </View>
<ScrollView <View>
className='max-w-[68vw]' <ScrollView
horizontal // 水平滚动 className='max-w-[85%]'
showsHorizontalScrollIndicator={false} // 隐藏滚动条 horizontal // 水平滚动
contentContainerStyle={{ showsHorizontalScrollIndicator={false} // 隐藏滚动条
flexDirection: 'row', contentContainerStyle={{
gap: 8 // 间距 flexDirection: 'row',
}} gap: 8 // 间距
> }}
<ThemedText style={{ color: '#AC7E35', fontSize: 12, fontWeight: '600' }}>User ID{userInfo?.user_info?.user_id}</ThemedText> >
</ScrollView> <ThemedText style={{ color: '#AC7E35', fontSize: 12, fontWeight: '600' }}>User ID {userInfo?.user_info?.user_id}</ThemedText>
</ScrollView>
{/* <CopyButton textToCopy={userInfo?.user_info?.user_id || ""} /> */}
</View>
</View> </View>
{/* 头像 */} {/* 头像 */}
<View> <View className='w-auto'>
{userInfo?.user_info?.avatar_file_url {userInfo?.user_info?.avatar_file_url && !imageError ? (
?
<Image <Image
source={{ uri: userInfo?.user_info?.avatar_file_url }} source={{ uri: userInfo.user_info.avatar_file_url }}
style={{ width: 80, height: 80, borderRadius: 40 }} style={{ width: 80, height: 80, borderRadius: 40 }}
onError={() => {
console.log('图片加载失败:', userInfo.user_info.avatar_file_url);
setImageError(true);
}}
onLoad={() => {
console.log('图片加载成功');
}}
/> />
: ) : (
<UserSvg width={80} height={80} /> <UserSvg width={80} height={80} />
} )}
</View> </View>
</View > </View >
); );

View File

@ -48,7 +48,8 @@
"codeVaild": "The code you entered is invalid", "codeVaild": "The code you entered is invalid",
"sendAgain": "Didnt receive a code?", "sendAgain": "Didnt receive a code?",
"resend": "Resend", "resend": "Resend",
"goBack": "Go Back" "goBack": "Go Back",
"aiAgreement": "AI Function Usage Norms"
}, },
"login": { "login": {
"title": "Log in", "title": "Log in",

View File

@ -48,7 +48,8 @@
"codeValid": "您输入的验证码无效", "codeValid": "您输入的验证码无效",
"sendAgain": "没有收到验证码?", "sendAgain": "没有收到验证码?",
"resend": "重新发送", "resend": "重新发送",
"goBack": "返回" "goBack": "返回",
"aiAgreement": "《AI功能使用规范》"
}, },
"login": { "login": {
"title": "登录", "title": "登录",

45
package-lock.json generated
View File

@ -18,6 +18,7 @@
"expo-audio": "~0.4.7", "expo-audio": "~0.4.7",
"expo-background-fetch": "^13.1.6", "expo-background-fetch": "^13.1.6",
"expo-blur": "~14.1.5", "expo-blur": "~14.1.5",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.6", "expo-constants": "~17.1.6",
"expo-dev-client": "~5.2.1", "expo-dev-client": "~5.2.1",
"expo-device": "~7.1.4", "expo-device": "~7.1.4",
@ -3360,18 +3361,6 @@
} }
} }
}, },
"node_modules/@react-native-async-storage/async-storage": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
"integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
"license": "MIT",
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native-picker/picker": { "node_modules/@react-native-picker/picker": {
"version": "2.11.1", "version": "2.11.1",
"resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz", "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz",
@ -7835,6 +7824,17 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/expo-clipboard": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-7.1.5.tgz",
"integrity": "sha512-TCANUGOxouoJXxKBW5ASJl2WlmQLGpuZGemDCL2fO5ZMl57DGTypUmagb0CVUFxDl0yAtFIcESd78UsF9o64aw==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-constants": { "node_modules/expo-constants": {
"version": "17.1.7", "version": "17.1.7",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.7.tgz", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.7.tgz",
@ -9743,15 +9743,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-regex": { "node_modules/is-regex": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@ -10819,18 +10810,6 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/merge-options": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
"license": "MIT",
"dependencies": {
"is-plain-obj": "^2.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",

View File

@ -29,7 +29,6 @@
"expo-file-system": "~18.1.10", "expo-file-system": "~18.1.10",
"expo-font": "~13.3.1", "expo-font": "~13.3.1",
"expo-haptics": "~14.1.4", "expo-haptics": "~14.1.4",
"expo-image": "~2.3.2",
"expo-image-manipulator": "~13.1.7", "expo-image-manipulator": "~13.1.7",
"expo-image-picker": "~16.1.4", "expo-image-picker": "~16.1.4",
"expo-linking": "~7.1.5", "expo-linking": "~7.1.5",
@ -69,7 +68,8 @@
"react-native-uuid": "^2.0.3", "react-native-uuid": "^2.0.3",
"react-native-web": "~0.20.0", "react-native-web": "~0.20.0",
"react-native-webview": "13.13.5", "react-native-webview": "13.13.5",
"react-redux": "^9.2.0" "react-redux": "^9.2.0",
"expo-clipboard": "~7.1.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",