feat: 轮播图

This commit is contained in:
jinyaqiu 2025-07-21 18:21:13 +08:00 committed by txcjh
parent 6aa98dae44
commit 06a579cf04
18 changed files with 369 additions and 118 deletions

View File

@ -3,26 +3,33 @@ import UploaderProgress from "@/components/file-upload/upload-progress/uploader-
import AskNavbar from "@/components/layout/ask";
import { useUploadManager } from "@/hooks/useUploadManager";
import { fetchApi } from "@/lib/server-api-util";
import { useAppDispatch, useAppSelector } from "@/store";
import { useAppSelector } from "@/store";
import { Chat } from "@/types/ask";
import { router } from "expo-router";
import React, { useEffect, useState } from 'react';
import { FlatList, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { router, useFocusEffect } from "expo-router";
import React from 'react';
import { FlatList, Platform, RefreshControl, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const MemoList = () => {
const insets = useSafeAreaInsets();
const dispatch = useAppDispatch();
const uploadSessionStartTime = useAppSelector((state) => state.appState.uploadSessionStartTime);
// 历史消息
const [historyList, setHistoryList] = React.useState<Chat[]>([]);
// 刷新状态
const [refreshing, setRefreshing] = React.useState(false);
// 获取历史消息
const getHistoryList = async () => {
await fetchApi<Chat[]>(`/chats`).then((res) => {
setHistoryList(res)
})
try {
setRefreshing(true);
const res = await fetchApi<Chat[]>(`/chats`);
setHistoryList(res);
} catch (error) {
console.error('Failed to fetch history:', error);
} finally {
setRefreshing(false);
}
}
// 获取对话历史消息
@ -41,9 +48,12 @@ const MemoList = () => {
getChatHistory(item.session_id)
}
useEffect(() => {
useFocusEffect(
React.useCallback(() => {
getHistoryList()
}, [])
);
const { progressInfo, uploadSessionStartTime: uploadSessionStartTimeFromHook } = useUploadManager();
@ -87,6 +97,15 @@ const MemoList = () => {
ListHeaderComponent={renderHeader}
data={historyList}
keyExtractor={(item) => item.session_id}
// 下拉刷新
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={getHistoryList}
colors={['#FFB645']} // Android
tintColor="#FFB645" // iOS
/>
}
ItemSeparatorComponent={() => (
<View style={styles.separator} />
)}

View File

@ -1,24 +1,21 @@
import ComeinSvg from '@/assets/icons/svg/comein.svg';
import ConversationsSvg from '@/assets/icons/svg/conversations.svg';
import PointsSvg from '@/assets/icons/svg/points.svg';
import StoriesSvg from '@/assets/icons/svg/stories.svg';
import UsedStorageSvg from '@/assets/icons/svg/usedStorage.svg';
import AskNavbar from '@/components/layout/ask';
import AlbumComponent from '@/components/owner/album';
import CategoryComponent from '@/components/owner/category';
import CountComponent from '@/components/owner/count';
import CarouselComponent from '@/components/owner/carousel';
import CreateCountComponent from '@/components/owner/createCount';
import Ranking from '@/components/owner/ranking';
import ResourceComponent from '@/components/owner/resource';
import SettingModal from '@/components/owner/setting';
import UserInfo from '@/components/owner/userName';
import { formatDuration } from '@/components/utils/time';
import { ThemedText } from '@/components/ThemedText';
import { checkAuthStatus } from '@/lib/auth';
import { fetchApi } from '@/lib/server-api-util';
import { CountData, UserInfoDetails } from '@/types/user';
import { useRouter } from 'expo-router';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, ScrollView, StyleSheet, View } from 'react-native';
import { FlatList, StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function OwnerPage() {
@ -88,31 +85,15 @@ export default function OwnerPage() {
{/* 资源数据 */}
<View style={styles.resourceContainer}>
<View style={{ gap: 16 }}>
<ResourceComponent title={t("generalSetting.usedStorage", { ns: "personal" })} data={{ all: userInfoDetails.total_bytes, used: countData.used_bytes }} icon={<UsedStorageSvg />} isFormatBytes={true} />
<ResourceComponent title={t("generalSetting.remainingPoints", { ns: "personal" })} data={{ all: userInfoDetails.total_points, used: userInfoDetails.remain_points }} icon={<PointsSvg />} />
<View style={{ gap: 4 }}>
<ThemedText style={styles.text}>{t("generalSetting.premium", { ns: "personal" })}</ThemedText>
<ThemedText style={styles.secondText}>{t("generalSetting.unlock", { ns: "personal" })}</ThemedText>
</View>
<ComeinSvg width={24} height={24} />
</View>
{/* 数据统计 */}
<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) }]}
/>
{/* 分类 */}
<View style={{ height: 145 }}>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 16 }} >
{countData?.counter?.category_count && Object.entries(countData?.counter?.category_count).map(([key, value], index) => {
return (
<CategoryComponent
key={index}
title={key}
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}
style={{ aspectRatio: 1, flex: 1 }}
/>
)
})}
</ScrollView>
<View style={{ marginHorizontal: -16, marginBottom: -16 }}>
<CarouselComponent data={userInfoDetails?.material_counter} />
</View>
{/* 作品数据 */}
@ -148,11 +129,20 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 16,
backgroundColor: "#FAF9F6",
padding: 16,
backgroundColor: "#4C320C",
paddingVertical: 8,
paddingHorizontal: 32,
borderRadius: 18,
paddingTop: 20
},
text: {
fontSize: 12,
fontWeight: '700',
color: '#FFB645',
},
secondText: {
fontSize: 16,
fontWeight: '700',
color: '#fff',
},
userInfo: {
flexDirection: 'row',

View File

@ -0,0 +1,3 @@
<svg width="11" height="20" viewBox="0 0 11 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.0002 1L9.77832 9.77812L1.0002 18.5562" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@ -0,0 +1,3 @@
<svg width="140" height="32" viewBox="0 0 140 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="140" height="32" rx="16" fill="#FFB645" fill-opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 179 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0C8 0 8.4414 5.71995 10.1607 7.43927C11.88 9.1586 16 9.6 16 9.6C16 9.6 11.88 10.0414 10.1607 11.7607C8.4414 13.4801 8 16 8 16C8 16 7.5586 13.4801 5.83927 11.7607C4.11995 10.0414 0 9.6 0 9.6C0 9.6 4.11995 9.1586 5.83927 7.43927C7.5586 5.71995 8 0 8 0Z" fill="#FFB645"/>
</svg>

After

Width:  |  Height:  |  Size: 384 B

View File

@ -0,0 +1,45 @@
<svg width="303" height="100" viewBox="0 0 403 135" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_2610_1468" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="403" height="135">
<rect width="403" height="135" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_2610_1468)">
<path d="M162.293 51.3874C165.003 47.9968 172.492 55.1223 175.897 59.1089L163.363 60.1427C160.856 59.0426 159.582 54.7779 162.293 51.3874Z" fill="#FFDBA3"/>
<path d="M164.084 52.7696C166.036 49.7687 170.582 55.3988 172.611 58.589L166.409 58.9204C164.82 58.1206 162.132 55.7706 164.084 52.7696Z" fill="#AC7E35"/>
<path d="M242.275 51.3874C239.565 47.9968 232.077 55.1223 228.671 59.1089L241.205 60.1427C243.712 59.0426 244.986 54.7779 242.275 51.3874Z" fill="#FFDBA3"/>
<path d="M240.487 52.7696C238.535 49.7687 233.989 55.3988 231.96 58.589L238.162 58.9204C239.75 58.1206 242.439 55.7706 240.487 52.7696Z" fill="#AC7E35"/>
<path d="M107.996 103.821C149.775 31.4558 254.225 31.4559 296.004 103.821L324.502 153.179C366.282 225.544 314.057 316 230.497 316H173.503C89.9431 316 37.7184 225.544 79.4981 153.179L107.996 103.821Z" fill="#FFD18A"/>
<path d="M196.064 69.1265C196.176 67.9873 195.281 67 194.137 67C192.992 67 192.097 67.9873 192.209 69.1265L192.437 71.4418C192.523 72.3163 193.258 72.9829 194.137 72.9829C195.015 72.9829 195.751 72.3163 195.837 71.4418L196.064 69.1265Z" fill="#4C320C"/>
<path d="M210.769 69.1265C210.881 67.9873 209.986 67 208.842 67C207.697 67 206.802 67.9873 206.914 69.1265L207.142 71.4418C207.228 72.3163 207.963 72.9829 208.842 72.9829C209.72 72.9829 210.456 72.3163 210.542 71.4418L210.769 69.1265Z" fill="#4C320C"/>
<path d="M140.736 142.883C170.285 99.9274 233.716 99.9274 263.265 142.883L296.918 191.806C330.855 241.142 295.535 308.307 235.654 308.307H168.347C108.466 308.307 73.1464 241.142 107.083 191.806L140.736 142.883Z" fill="#FFF8DE"/>
<g filter="url(#filter0_i_2610_1468)">
<ellipse cx="261.402" cy="108.682" rx="70.0855" ry="50" fill="#FFF8DE"/>
</g>
<g filter="url(#filter1_i_2610_1468)">
<ellipse cx="142.17" cy="108.682" rx="69.6581" ry="50" fill="#FFF8DE"/>
</g>
<ellipse cx="201.572" cy="83.8953" rx="5.12821" ry="3.84615" transform="rotate(180 201.572 83.8953)" fill="#FFB8B9"/>
<path d="M200.833 88.1688C201.162 87.599 201.984 87.599 202.313 88.1688L203.053 89.4508C203.382 90.0206 202.971 90.7329 202.313 90.7329H200.833C200.175 90.7329 199.764 90.0206 200.093 89.4508L200.833 88.1688Z" fill="#4C320C"/>
</g>
<defs>
<filter id="filter0_i_2610_1468" x="183.624" y="58.6816" width="147.863" height="102.564" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="-7.69231" dy="2.5641"/>
<feGaussianBlur stdDeviation="7.05128"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.713726 0 0 0 0 0.270588 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_2610_1468"/>
</filter>
<filter id="filter1_i_2610_1468" x="72.5122" y="58.6816" width="147.863" height="100" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="8.54701"/>
<feGaussianBlur stdDeviation="4.70086"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.713974 0 0 0 0 0.272498 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_2610_1468"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -11,10 +11,10 @@ const AlbumComponent = ({ setModalVisible, style }: CategoryProps) => {
const { t } = useTranslation();
return (
<View style={[styles.container, style]}>
<TouchableOpacity style={{ flex: 3, opacity: 0 }}>
<TouchableOpacity style={{ flex: 3 }}>
<ThemedText style={styles.text}>{t('generalSetting.album', { ns: 'personal' })}</ThemedText>
</TouchableOpacity>
<TouchableOpacity style={{ flex: 3, opacity: 0 }}>
<TouchableOpacity style={{ flex: 3 }}>
<ThemedText style={styles.text}>{t('generalSetting.shareProfile', { ns: 'personal' })}</ThemedText>
</TouchableOpacity>
<TouchableOpacity onPress={() => setModalVisible(true)} style={[styles.text, { flex: 1, alignItems: "center", paddingVertical: 6 }]}>

View File

@ -0,0 +1,153 @@
import HandersSvg from "@/assets/icons/svg/handers.svg";
import UserinfoTotalSvg from "@/assets/icons/svg/userinfoTotal.svg";
import { Counter, UserCountData } from "@/types/user";
import * as React from "react";
import { Dimensions, StyleSheet, View, ViewStyle } from "react-native";
import Carousel from "react-native-reanimated-carousel";
import { ThemedText } from "../ThemedText";
import { formatDuration } from "../utils/time";
import CategoryComponent from "./category";
interface Props {
data: Counter
}
interface CarouselData {
key: string,
value: UserCountData
}[]
const width = Dimensions.get("window").width;
function CarouselComponent(props: Props) {
const { data } = props;
const [carouselDataValue, setCarouselDataValue] = React.useState<CarouselData[]>([]);
const dataHandle = () => {
const carouselData = { ...data?.category_count, total_count: data?.total_count }
// 1. 转换为数组并过滤掉 'total'
const entries = Object?.entries(carouselData)
?.filter(([key]) => key !== 'total_count')
?.map(([key, value]) => ({ key, value }));
// 2. 找到 total 数据
const totalEntry = {
key: 'total_count',
value: carouselData?.total_count
};
// 3. 插入到中间位置
const middleIndex = Math.floor((entries || [])?.length / 2);
entries?.splice(middleIndex, 0, totalEntry);
setCarouselDataValue(entries)
return entries;
}
const totleItem = (data: UserCountData) => {
return <View style={styles.container}>
{Object?.entries(data)?.filter(([key]) => key !== 'cover_url')?.map((item, index) => (
<View style={index === Object?.entries(data)?.length - 2 ? { ...styles.item, borderBottomWidth: 0 } : styles.item} key={index}>
<ThemedText style={styles.title}>{item[0]}</ThemedText>
<ThemedText style={styles.number}>{item[1]}</ThemedText>
</View>
))}
<View style={styles.image}>
<UserinfoTotalSvg />
<View style={{ position: 'absolute', bottom: -5, right: 0, left: 0, justifyContent: 'center', alignItems: 'center' }}><HandersSvg /></View>
</View>
</View>
}
React.useEffect(() => {
if (data) {
dataHandle()
}
}, [data]);
return (
<View style={{ flex: 1 }}>
<Carousel
width={width}
height={width * 0.75}
data={carouselDataValue || []}
mode="parallax"
defaultIndex={carouselDataValue?.findIndex((item) => item?.key === 'total_count') - 1 || 0}
modeConfig={{
parallaxScrollingScale: 1,
parallaxScrollingOffset: 160,
parallaxAdjacentItemScale: 0.7
}}
renderItem={({ item, index }) => {
const style: ViewStyle = {
width: width,
height: width * 0.8,
alignItems: "center",
};
return (
<View key={index} style={style}>
{item?.key === 'total_count' ? (
totleItem(item.value)
) : (
<View style={{ flex: 1, width: width * 0.65 }}>
{CategoryComponent({
title: item?.key,
data: [
{ title: 'Video', number: item?.value?.video_count },
{ title: 'Photo', number: item?.value?.photo_count },
{ title: 'Length', number: formatDuration(item?.value?.video_length || 0) }
],
bgSvg: item?.value?.cover_url,
})}
</View>
)}
</View>
)
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: "#FFB645",
paddingVertical: 8,
paddingHorizontal: 16,
borderRadius: 16,
display: "flex",
flexDirection: "column",
position: 'relative',
width: width * 0.6,
height: '85%'
},
image: {
position: 'absolute',
bottom: 0,
right: 0,
left: 0,
alignItems: 'center',
justifyContent: 'flex-end',
},
item: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 8,
borderBottomColor: '#fff',
borderBottomWidth: 1,
},
title: {
color: "#4C320C",
fontWeight: "700",
fontSize: 14,
},
number: {
color: "#fff",
fontWeight: "700",
fontSize: 32,
textAlign: 'right',
flex: 1,
paddingTop: 8
}
})
export default CarouselComponent;

View File

@ -1,3 +1,4 @@
import PeopleSvg from "@/assets/icons/svg/people.svg";
import { Image, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
import { ThemedText } from "../ThemedText";
@ -13,20 +14,27 @@ const CategoryComponent = ({ title, data, bgSvg, style }: CategoryProps) => {
<View style={[styles.container, style]}>
<View style={styles.backgroundContainer}>
<Image
source={bgSvg !== "" && bgSvg !== null ? { uri: bgSvg } : require('@/assets/images/png/owner/animals.png')}
style={{ width: '100%', height: '100%' }}
resizeMode="cover"
source={bgSvg !== "" && bgSvg !== null ? { uri: bgSvg } : require('@/assets/images/png/owner/people.png')}
style={{
width: "100%",
height: "100%",
resizeMode: "cover"
}}
/>
<View style={styles.overlay} />
</View>
<View style={styles.content}>
<ThemedText style={styles.title}>{title}</ThemedText>
{data.map((item, index) => (
<View style={styles.item} key={index}>
<ThemedText style={styles.itemTitle}>{item.title}</ThemedText>
<ThemedText style={styles.itemNumber}>{item.number}</ThemedText>
</View>
))}
<View style={styles.titleContent}>
<ThemedText style={styles.title}>{title}</ThemedText>
<PeopleSvg />
</View>
</View>
</View>
);
@ -37,45 +45,57 @@ const styles = StyleSheet.create({
borderRadius: 32,
overflow: 'hidden',
position: 'relative',
aspectRatio: 1,
},
backgroundContainer: {
...StyleSheet.absoluteFillObject,
width: '100%',
height: '100%',
width: "100%",
height: "100%",
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.4)', // 0% 不透明度的黑色
backgroundColor: 'rgba(0, 0, 0, 0.2)', // 0% 不透明度的黑色
},
content: {
paddingHorizontal: 16,
paddingVertical: 8,
justifyContent: 'space-between',
paddingHorizontal: 40,
paddingVertical: 24,
justifyContent: 'flex-end',
flex: 1
},
title: {
titleContent: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
width: '100%',
textAlign: "center",
marginTop: 16
},
title: {
color: 'white',
fontSize: 16,
fontWeight: '700',
textShadowColor: 'rgba(0, 0, 0, 0.5)',
textShadowOffset: { width: 1, height: 1 },
textShadowRadius: 2,
position: 'absolute',
textAlign: 'center',
width: '100%',
zIndex: 1,
},
item: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
justifyContent: 'space-between',
},
itemTitle: {
color: 'white',
fontSize: 10,
fontSize: 14,
fontWeight: '700',
},
itemNumber: {
color: 'white',
fontSize: 10,
fontSize: 14,
}
});

View File

@ -27,7 +27,8 @@ const Ranking = ({ data }: { data: TitleRankings[] }) => {
renderItem={({ item }) => (
<View style={styles.item}>
<ThemedText style={styles.rank}>No.{item.ranking}</ThemedText>
<ThemedText style={styles.title}>{item.region}{item.display_name}</ThemedText>
<ThemedText style={[styles.title]}>{item.display_name}</ThemedText>
<ThemedText style={styles.title}>{item.region}</ThemedText>
</View>
)}
/>

View File

@ -1,15 +1,31 @@
import UserSvg from '@/assets/icons/svg/ataver.svg';
import StarSvg from '@/assets/icons/svg/star.svg';
import { ThemedText } from '@/components/ThemedText';
import { UserInfoDetails } from '@/types/user';
import { useState } from 'react';
import { Image, ScrollView, View } from 'react-native';
import CopyButton from '../copy';
export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) {
// 添加状态来跟踪图片加载状态
const [imageError, setImageError] = useState(false);
return (
<View className='flex flex-row justify-between items-center mt-[1rem] gap-[1rem] w-full'>
{/* 头像 */}
<View className='w-auto'>
{userInfo?.user_info?.avatar_file_url && !imageError ? (
<Image
source={{ uri: userInfo.user_info.avatar_file_url }}
style={{ width: 70, height: 70, borderRadius: 40 }}
onError={() => {
setImageError(true);
}}
/>
) : (
<UserSvg width={70} height={70} />
)}
</View>
{/* 用户名 */}
<View className='flex flex-col gap-4 w-[75%]'>
<View className='flex flex-col w-[75%] gap-1'>
<View className='flex flex-row items-center justify-between w-full'>
<View className='flex flex-row items-center gap-2 w-full'>
<ThemedText
@ -39,39 +55,19 @@ export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) {
))
}
</ScrollView>
<View className='flex flex-row items-center gap-2 border border-bgPrimary px-2 py-1 rounded-full'>
<StarSvg />
<ThemedText style={{ color: 'bgPrimary', fontSize: 14, fontWeight: '700' }}>{userInfo?.remain_points}</ThemedText>
</View>
</View>
<View>
<ScrollView
className='max-w-[85%]'
horizontal // 水平滚动
showsHorizontalScrollIndicator={false} // 隐藏滚动条
contentContainerStyle={{
flexDirection: 'row',
gap: 8 // 间距
}}
>
</View>
<View style={{ display: "flex", flexDirection: "row", gap: 2, alignItems: "center" }}>
<ThemedText style={{ color: '#AC7E35', fontSize: 12, fontWeight: '600' }}>User ID{userInfo?.user_info?.user_id}</ThemedText>
</ScrollView>
{/* <CopyButton textToCopy={userInfo?.user_info?.user_id || ""} /> */}
<CopyButton textToCopy={userInfo?.user_info?.user_id || ""} />
</View>
</View>
</View >
{/* 头像 */}
<View className='w-auto'>
{userInfo?.user_info?.avatar_file_url && !imageError ? (
<Image
source={{ uri: userInfo.user_info.avatar_file_url }}
style={{ width: 80, height: 80, borderRadius: 40 }}
onError={() => {
setImageError(true);
}}
/>
) : (
<UserSvg width={80} height={80} />
)}
</View>
</View >
);
}

View File

@ -15,7 +15,7 @@
"homepage": "HomePage",
"signup": "Sign up",
"login": "Login",
"trade":"copyright 2025 MemoWake - All rights reserved",
"trade": "沪ICP备2025133004号-2A",
"logout": "Logout",
"self": "Personal Center"
},

View File

@ -83,6 +83,8 @@
"videoLength": "Video Duration",
"storiesCreated": "Stories Created",
"conversationsWithMemo": "Conversations with Memo",
"setting": "Settings"
"setting": "Settings",
"premium": "Upgrade to Premium",
"unlock": "Unlock more memory magic"
}
}

View File

@ -15,7 +15,7 @@
"homepage": "首页",
"signup": "注册",
"login": "登录",
"trade": "沪ICP备2023032876号-4",
"trade": "沪ICP备2025133004号-2A",
"logout": "退出登录",
"self": "个人中心"
},

View File

@ -83,6 +83,8 @@
"videoLength": "视频时长",
"storiesCreated": "创作视频",
"conversationsWithMemo": "Memo对话",
"setting": "设置"
"setting": "设置",
"premium": "升级至会员",
"unlock": "解锁更多记忆魔法"
}
}

13
package-lock.json generated
View File

@ -60,6 +60,7 @@
"react-native-picker-select": "^9.3.1",
"react-native-progress": "^5.0.1",
"react-native-reanimated": "~3.17.4",
"react-native-reanimated-carousel": "^4.0.2",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1",
@ -14826,6 +14827,18 @@
"react-native": "*"
}
},
"node_modules/react-native-reanimated-carousel": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/react-native-reanimated-carousel/-/react-native-reanimated-carousel-4.0.2.tgz",
"integrity": "sha512-vNpCfPlFoOVKHd+oB7B0luoJswp+nyz0NdJD8+LCrf25JiNQXfM22RSJhLaksBHqk3fm8R4fKWPNcfy5w7wL1Q==",
"license": "MIT",
"peerDependencies": {
"react": ">=18.0.0",
"react-native": ">=0.70.3",
"react-native-gesture-handler": ">=2.9.0",
"react-native-reanimated": ">=3.0.0"
}
},
"node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz",

View File

@ -25,6 +25,7 @@
"expo-audio": "~0.4.8",
"expo-background-task": "^0.2.8",
"expo-blur": "~14.1.5",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.6",
"expo-dev-client": "~5.2.4",
"expo-device": "~7.1.4",
@ -33,6 +34,7 @@
"expo-haptics": "~14.1.4",
"expo-image-manipulator": "~13.1.7",
"expo-image-picker": "~16.1.4",
"expo-linear-gradient": "~14.1.5",
"expo-linking": "~7.1.7",
"expo-localization": "^16.1.5",
"expo-location": "~18.1.5",
@ -64,6 +66,7 @@
"react-native-picker-select": "^9.3.1",
"react-native-progress": "^5.0.1",
"react-native-reanimated": "~3.17.4",
"react-native-reanimated-carousel": "^4.0.2",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1",
@ -72,9 +75,7 @@
"react-native-uuid": "^2.0.3",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5",
"react-redux": "^9.2.0",
"expo-clipboard": "~7.1.5",
"expo-linear-gradient": "~14.1.5"
"react-redux": "^9.2.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",

View File

@ -12,7 +12,7 @@ export interface User {
avatar_file_url?: string
}
interface UserCountData {
export interface UserCountData {
video_count: number,
photo_count: number,
live_count: number,
@ -31,7 +31,7 @@ export interface CountData {
}
}
interface Counter {
export interface Counter {
user_id: number,
total_count: UserCountData,
category_count: {