@ -5,7 +5,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v0.4.0_front
|
||||
- v0.5.0
|
||||
|
||||
jobs:
|
||||
Explore-Gitea-Actions:
|
||||
|
||||
16
app.json
@ -43,6 +43,22 @@
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
"expo-secure-store",
|
||||
[
|
||||
"expo-location",
|
||||
{
|
||||
"locationAlwaysAndWhenInUsePermission": "允许 $(PRODUCT_NAME) 访问您的位置",
|
||||
"locationAlwaysPermission": "允许 $(PRODUCT_NAME) 访问您的位置",
|
||||
"locationWhenInUsePermission": "允许 $(PRODUCT_NAME) 访问您的位置"
|
||||
}
|
||||
],
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
"color": "#ffffff",
|
||||
"defaultChannel": "default",
|
||||
"enableBackgroundRemoteNotifications": false
|
||||
}
|
||||
],
|
||||
[
|
||||
"expo-audio",
|
||||
{
|
||||
|
||||
@ -100,6 +100,26 @@ export default function TabLayout() {
|
||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
||||
}}
|
||||
/>
|
||||
{/* owner */}
|
||||
<Tabs.Screen
|
||||
name="owner"
|
||||
options={{
|
||||
title: 'owner',
|
||||
tabBarButton: () => null, // 隐藏底部标签栏
|
||||
headerShown: false, // 隐藏导航栏
|
||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
||||
}}
|
||||
/>
|
||||
{/* 排行榜 */}
|
||||
<Tabs.Screen
|
||||
name="top"
|
||||
options={{
|
||||
title: 'top',
|
||||
tabBarButton: () => null, // 隐藏底部标签栏
|
||||
headerShown: false, // 隐藏导航栏
|
||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,9 +46,9 @@ export default function HomeScreen() {
|
||||
} else {
|
||||
token = await SecureStore.getItemAsync('token') || "";
|
||||
}
|
||||
|
||||
console.log("token111111111", token);
|
||||
if (token) {
|
||||
router.push('/user-message')
|
||||
router.push('/ask')
|
||||
} else {
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import ChatSvg from "@/assets/icons/svg/chat.svg";
|
||||
import IPSvg from "@/assets/icons/svg/ip.svg";
|
||||
import AskNavbar from "@/components/layout/ask";
|
||||
import { fetchApi } from "@/lib/server-api-util";
|
||||
import { Chat } from "@/types/ask";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import React, { useEffect } from 'react';
|
||||
import { FlatList, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
@ -59,7 +58,13 @@ const MemoList = () => {
|
||||
style={styles.memoItem}
|
||||
onPress={() => handleMemoPress(item)}
|
||||
>
|
||||
<ChatSvg />
|
||||
<View className="w-[3rem] h-[3rem] z-1">
|
||||
<ChatSvg
|
||||
width="100%"
|
||||
height="100%"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.memoContent}>
|
||||
<Text
|
||||
style={styles.memoTitle}
|
||||
@ -80,31 +85,7 @@ const MemoList = () => {
|
||||
)}
|
||||
/>
|
||||
{/* 底部导航栏 */}
|
||||
<View className="flex-row justify-between items-center px-8 shadow-lg shadow-black/20 pb-10">
|
||||
<TouchableOpacity >
|
||||
<Ionicons name="chatbubbles-outline" size={24} color="#4C320C" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
className="bg-white rounded-full shadow-lg shadow-black/20 items-center justify-center -mt-8"
|
||||
onPress={() => {
|
||||
router.push({
|
||||
pathname: '/ask',
|
||||
params: {
|
||||
newSession: "true",
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IPSvg width={96} height={96} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity>
|
||||
<View className="">
|
||||
<Ionicons name="person-outline" size={24} color="#4C320C" />
|
||||
<View className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full" />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<AskNavbar />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
211
app/(tabs)/owner.tsx
Normal file
@ -0,0 +1,211 @@
|
||||
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 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 { fetchApi } from '@/lib/server-api-util';
|
||||
import { CountData, UserInfoDetails } from '@/types/user';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
export default function OwnerPage() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 设置弹窗
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
// 数据统计
|
||||
const [countData, setCountData] = useState<CountData>({} as CountData);
|
||||
|
||||
// 获取数量统计 --- 需要轮询
|
||||
const getCountData = () => {
|
||||
fetchApi("/material/statistics").then((res) => {
|
||||
setCountData(res as CountData);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const [userInfoDetails, setUserInfoDetails] = useState<UserInfoDetails>({} as UserInfoDetails);
|
||||
const getUserInfo = () => {
|
||||
fetchApi("/membership/personal-center-info").then((res) => {
|
||||
setUserInfoDetails(res as UserInfoDetails);
|
||||
})
|
||||
}
|
||||
// 设计轮询获取数量统计
|
||||
// useEffect(() => {
|
||||
// const interval = setInterval(() => {
|
||||
// getCountData();
|
||||
// }, 1000);
|
||||
// return () => clearInterval(interval);
|
||||
// }, []);
|
||||
|
||||
// 初始化获取用户信息
|
||||
useEffect(() => {
|
||||
getUserInfo();
|
||||
getCountData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: insets.top }]}>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ flexGrow: 1, gap: 16, marginHorizontal: 16 }}
|
||||
>
|
||||
{/* 用户信息 */}
|
||||
<UserInfo userInfo={userInfoDetails} />
|
||||
|
||||
{/* 设置栏 */}
|
||||
<AlbumComponent setModalVisible={setModalVisible} />
|
||||
|
||||
{/* 资源数据 */}
|
||||
<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: countData.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 }} />
|
||||
</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>
|
||||
|
||||
{/* 作品数据 */}
|
||||
<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.conversationsWithMemo", { ns: "personal" })} icon={<ConversationsSvg />} number={userInfoDetails.conversations_count} />
|
||||
</View>
|
||||
|
||||
{/* 排行榜 */}
|
||||
<Ranking data={userInfoDetails.title_rankings} />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
{/* 设置弹窗 */}
|
||||
<SettingModal modalVisible={modalVisible} setModalVisible={setModalVisible} userInfo={userInfoDetails.user_info} />
|
||||
|
||||
{/* 导航栏 */}
|
||||
<AskNavbar />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
paddingBottom: 86,
|
||||
},
|
||||
resourceContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 16
|
||||
},
|
||||
userInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
},
|
||||
medal: {
|
||||
marginLeft: 16,
|
||||
},
|
||||
backButton: {
|
||||
padding: 4,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
profileSection: {
|
||||
backgroundColor: '#fff',
|
||||
padding: 20,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
avatarContainer: {
|
||||
marginRight: 16,
|
||||
},
|
||||
avatar: {
|
||||
width: 70,
|
||||
height: 70,
|
||||
borderRadius: 35,
|
||||
borderWidth: 2,
|
||||
borderColor: '#FFD38D',
|
||||
},
|
||||
profileInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
userName: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
marginBottom: 4,
|
||||
},
|
||||
userPhone: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
},
|
||||
editButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: '#eee',
|
||||
borderRadius: 15,
|
||||
},
|
||||
editButtonText: {
|
||||
marginLeft: 4,
|
||||
color: '#666',
|
||||
fontSize: 14,
|
||||
},
|
||||
menuContainer: {
|
||||
backgroundColor: '#fff',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
menuItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f5f5f5',
|
||||
},
|
||||
menuItemText: {
|
||||
flex: 1,
|
||||
marginLeft: 12,
|
||||
fontSize: 16,
|
||||
color: '#333',
|
||||
},
|
||||
arrowIcon: {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
});
|
||||
182
app/(tabs)/top.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
import ArrowSvg from '@/assets/icons/svg/arrow.svg';
|
||||
import ReturnArrowSvg from '@/assets/icons/svg/returnArrow.svg';
|
||||
import { CascaderItem } from '@/components/cascader';
|
||||
import ClassifyModal from '@/components/owner/classify';
|
||||
import LocationModal from '@/components/owner/location';
|
||||
import PodiumComponent from '@/components/owner/podium';
|
||||
import RankList from '@/components/owner/rankList';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { transformData } from '@/components/utils/objectToCascader';
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import { GroupedData, RankingItem, TargetItem } from '@/types/user';
|
||||
import { useRouter } from "expo-router";
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { LayoutChangeEvent, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
export default function OwnerPage() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const router = useRouter();
|
||||
// 位置弹窗
|
||||
const [locationModalVisible, setLocationModalVisible] = useState(false);
|
||||
// 分类弹窗
|
||||
const [classifyModalVisible, setClassifyModalVisible] = useState(false);
|
||||
|
||||
// 在组件内部添加:
|
||||
const podiumRef = useRef<View>(null);
|
||||
const [podiumPosition, setPodiumPosition] = useState({ x: 0, y: 0, width: 0, height: 0 });
|
||||
// 获取分类
|
||||
const [classify, setClassify] = useState<TargetItem[]>([]);
|
||||
const getClassify = () => {
|
||||
fetchApi<GroupedData>("/title-tags").then((res: GroupedData) => {
|
||||
setClassify(transformData(res));
|
||||
});
|
||||
}
|
||||
|
||||
// 选择地区
|
||||
const [selectedLocation, setSelectedLocation] = useState<any>();
|
||||
// 选择分类
|
||||
const [selectedClassify, setSelectedClassify] = useState<any>();
|
||||
|
||||
const onPodiumLayout = (event: LayoutChangeEvent) => {
|
||||
if (podiumRef.current) {
|
||||
podiumRef.current.measure((x, y, width, height, pageX, pageY) => {
|
||||
setPodiumPosition({
|
||||
x: pageX,
|
||||
y: pageY,
|
||||
width,
|
||||
height
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
// 地区选择
|
||||
const handleLocationChange = useCallback((selectedItems: CascaderItem[]) => {
|
||||
console.log('SelectedLocation:', selectedItems);
|
||||
if (selectedItems.length > 0) {
|
||||
const lastItem = selectedItems[selectedItems.length - 1];
|
||||
// 只有当选择完成时才更新状态
|
||||
if (!lastItem.children || lastItem.children.length === 0) {
|
||||
setSelectedLocation(selectedItems);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 分类选择
|
||||
const handleClassifyChange = useCallback((selectedItems: CascaderItem[]) => {
|
||||
console.log('SelectedClassify:', selectedItems);
|
||||
if (selectedItems.length > 0) {
|
||||
const lastItem = selectedItems[selectedItems.length - 1];
|
||||
// 只有当选择完成时才更新状态
|
||||
if (!lastItem.children || lastItem.children.length === 0) {
|
||||
setSelectedClassify(selectedItems);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 获取排名信息
|
||||
const [ranking, setRanking] = useState<RankingItem[]>([]);
|
||||
const getRanking = () => {
|
||||
fetchApi<RankingItem[]>("/title-rank", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
"title_tag_id": selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].value : 3,
|
||||
"area_id": 1
|
||||
})
|
||||
}).then((res) => {
|
||||
setRanking(res);
|
||||
});
|
||||
}
|
||||
|
||||
// 当用户选择发生变化时,重新获取排名
|
||||
useEffect(() => {
|
||||
getRanking();
|
||||
}, [selectedLocation, selectedClassify])
|
||||
|
||||
// 初始化获取分类
|
||||
useEffect(() => {
|
||||
getClassify();
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: insets.top }]}>
|
||||
{/* 导航栏 */}
|
||||
<View
|
||||
style={styles.header}
|
||||
ref={podiumRef}
|
||||
onLayout={onPodiumLayout}
|
||||
>
|
||||
<TouchableOpacity onPress={() => { router.push('/owner') }} style={{ padding: 16 }}>
|
||||
<ReturnArrowSvg />
|
||||
</TouchableOpacity>
|
||||
<ThemedText style={styles.headerTitle}>
|
||||
Top Memory Makers
|
||||
</ThemedText>
|
||||
<View className='opacity-0'>123</View>
|
||||
</View>
|
||||
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 16, marginHorizontal: 16 }}>
|
||||
<TouchableOpacity onPress={() => { setLocationModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<ThemedText style={{ color: selectedLocation?.length > 0 ? '#FFB645' : '#4C320C' }}>
|
||||
{selectedLocation?.length > 0 ? selectedLocation[selectedLocation?.length - 1].name : "地区"}
|
||||
</ThemedText>
|
||||
{
|
||||
selectedLocation?.length > 0
|
||||
?
|
||||
<ArrowSvg style={{ transform: [{ rotate: '90deg' }], width: 12, height: 12 }} />
|
||||
:
|
||||
<ReturnArrowSvg style={{ transform: [{ rotate: '270deg' }], width: 12, height: 12 }} />
|
||||
}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => { setClassifyModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<ThemedText style={{ color: selectedClassify?.length > 0 ? '#FFB645' : '#4C320C' }}>
|
||||
{selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].name : "分类"}
|
||||
</ThemedText>
|
||||
{selectedClassify?.length > 0
|
||||
?
|
||||
<ArrowSvg style={{ transform: [{ rotate: '90deg' }], width: 12, height: 12 }} />
|
||||
:
|
||||
<ReturnArrowSvg style={{ transform: [{ rotate: '270deg' }], width: 12, height: 12 }} />
|
||||
}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{/* 颁奖台 */}
|
||||
<PodiumComponent data={ranking} />
|
||||
{/* 排名区域 */}
|
||||
<RankList data={ranking} />
|
||||
|
||||
{/* 地区选择弹窗 */}
|
||||
<LocationModal
|
||||
modalVisible={locationModalVisible}
|
||||
setModalVisible={setLocationModalVisible}
|
||||
podiumPosition={podiumPosition}
|
||||
handleChange={handleLocationChange}
|
||||
/>
|
||||
{/* 分类选择弹窗 */}
|
||||
<ClassifyModal
|
||||
data={classify}
|
||||
modalVisible={classifyModalVisible}
|
||||
setModalVisible={setClassifyModalVisible}
|
||||
podiumPosition={podiumPosition}
|
||||
handleChange={handleClassifyChange}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
header: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginVertical: 16,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
color: '#4C320C',
|
||||
}
|
||||
});
|
||||
3
assets/icons/svg/arrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="9" height="14" viewBox="0 0 9 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.38965 1L7.38965 7L1.38965 13" stroke="#AC7E35" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 226 B |
46
assets/icons/svg/askIP.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<svg width="85" height="85" viewBox="0 0 85 85" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_1464_1669" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="85" height="85">
|
||||
<circle cx="42.5" cy="42.5" r="42.5" fill="#FFC959"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1464_1669)">
|
||||
<circle cx="42.5" cy="42.5" r="42.5" fill="#FFD38D"/>
|
||||
<path d="M20.2018 14.6411C21.8694 12.5551 26.4765 16.939 28.5716 19.3917L20.8604 20.0277C19.3178 19.3509 18.5342 16.7271 20.2018 14.6411Z" fill="#FFDBA3"/>
|
||||
<path d="M21.3021 15.4913C22.503 13.6451 25.3001 17.1089 26.5485 19.0716L22.7323 19.2755C21.7552 18.7834 20.1012 17.3376 21.3021 15.4913Z" fill="#AC7E35"/>
|
||||
<path d="M65.1253 14.6411C63.4577 12.5551 58.8506 16.939 56.7556 19.3917L64.4667 20.0277C66.0093 19.3509 66.7929 16.7271 65.1253 14.6411Z" fill="#FFDBA3"/>
|
||||
<path d="M64.0255 15.4913C62.8246 13.6451 60.0276 17.1089 58.7792 19.0716L62.5953 19.2755C63.5724 18.7834 65.2264 17.3376 64.0255 15.4913Z" fill="#AC7E35"/>
|
||||
<path d="M-15.3352 49.1734C10.3693 4.65192 74.6306 4.65187 100.335 49.1734L117.868 79.5409C143.572 124.062 111.442 179.714 60.0327 179.714H24.9673C-26.4417 179.714 -58.5724 124.062 -32.8679 79.5409L-15.3352 49.1734Z" fill="#FFD18A"/>
|
||||
<rect x="38.5571" y="46.2812" width="2.62922" height="3.68091" rx="1.31461" transform="rotate(-180 38.5571 46.2812)" fill="#4C320C"/>
|
||||
<rect x="48.0205" y="46.2812" width="2.62922" height="3.68091" rx="1.31461" transform="rotate(-180 48.0205 46.2812)" fill="#4C320C"/>
|
||||
<path d="M4.8084 73.2062C22.9876 46.7781 62.0132 46.7782 80.1924 73.2062L100.897 103.306C121.776 133.659 100.046 174.982 63.2051 174.982H21.7957C-15.0453 174.982 -36.7756 133.659 -15.8963 103.306L4.8084 73.2062Z" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter0_i_1464_1669)">
|
||||
<ellipse cx="79.047" cy="68.6298" rx="43.1193" ry="30.7619" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_i_1464_1669)">
|
||||
<ellipse cx="5.69032" cy="68.6298" rx="42.8563" ry="30.7619" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<ellipse cx="42.2365" cy="53.3803" rx="3.15507" ry="2.3663" transform="rotate(180 42.2365 53.3803)" fill="#FFB8B9"/>
|
||||
<path d="M41.7813 56.0095C41.9837 55.6589 42.4897 55.6589 42.6921 56.0095L43.1475 56.7982C43.3499 57.1488 43.0969 57.587 42.6921 57.587H41.7813C41.3765 57.587 41.1235 57.1488 41.3259 56.7982L41.7813 56.0095Z" fill="#4C320C"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_1464_1669" x="31.1951" y="37.8679" width="90.9709" height="63.1015" 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="-4.7326" dy="1.57753"/>
|
||||
<feGaussianBlur stdDeviation="4.33822"/>
|
||||
<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_1464_1669"/>
|
||||
</filter>
|
||||
<filter id="filter1_i_1464_1669" x="-37.166" y="37.8679" width="90.9713" height="61.5239" 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="5.25844"/>
|
||||
<feGaussianBlur stdDeviation="2.89214"/>
|
||||
<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_1464_1669"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@ -1,41 +1,44 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_106_1356" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="30" height="30">
|
||||
<circle cx="15" cy="15" r="15" fill="#D9D9D9"/>
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="30" cy="30" r="30" fill="white"/>
|
||||
<mask id="chat_mask0_555_1115" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="60" height="60">
|
||||
<circle cx="30" cy="30" r="30" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_106_1356)">
|
||||
<path d="M-40.9027 16.1923C-15.8348 -27.2265 46.8348 -27.2265 71.9027 16.1923L89.0011 45.8077C114.069 89.2265 82.7342 143.5 32.5984 143.5H-1.59845C-51.7342 143.5 -83.069 89.2265 -58.0011 45.8077L-40.9027 16.1923Z" fill="#FFD18A"/>
|
||||
<rect x="11.6543" y="13.3718" width="2.5641" height="3.58974" rx="1.28205" transform="rotate(-180 11.6543 13.3718)" fill="#4C320C"/>
|
||||
<rect x="20.8848" y="13.3718" width="2.5641" height="3.58974" rx="1.28205" transform="rotate(-180 20.8848 13.3718)" fill="#4C320C"/>
|
||||
<path d="M-21.2587 39.6301C-3.52975 13.8566 34.5294 13.8565 52.2583 39.63L72.4502 68.9839C92.8123 98.5854 71.6203 138.885 35.6917 138.885H-4.69213C-40.6207 138.885 -61.8127 98.5854 -41.4506 68.984L-21.2587 39.6301Z" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter0_i_106_1356)">
|
||||
<ellipse cx="51.1411" cy="35.1667" rx="42.0513" ry="30" fill="#FFF8DE"/>
|
||||
<g mask="url(#chat_mask0_555_1115)">
|
||||
<path d="M-12.302 16.2692C6.49892 -16.2949 53.5011 -16.2949 72.302 16.2692L85.1259 38.4808C103.927 71.0449 80.4256 111.75 42.8238 111.75H17.1762C-20.4256 111.75 -43.9267 71.0449 -25.1258 38.4808L-12.302 16.2692Z" fill="#FFD18A"/>
|
||||
<rect x="27.116" y="14.1538" width="1.92308" height="2.69231" rx="0.961539" transform="rotate(-180 27.116 14.1538)" fill="#4C320C"/>
|
||||
<rect x="34.0383" y="14.1538" width="1.92308" height="2.69231" rx="0.961539" transform="rotate(-180 34.0383 14.1538)" fill="#4C320C"/>
|
||||
<path d="M2.43174 33.8473C15.7285 14.5172 44.2728 14.5172 57.5695 33.8473L72.7134 55.8628C87.985 78.0639 72.0909 108.288 45.1445 108.288H14.8567C-12.0897 108.288 -27.9838 78.0639 -12.7122 55.8628L2.43174 33.8473Z" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter0_i_555_1115)">
|
||||
<ellipse cx="56.7313" cy="30.5" rx="31.5385" ry="22.5" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_i_106_1356)">
|
||||
<ellipse cx="-20.3975" cy="35.1667" rx="41.7949" ry="30" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter1_i_555_1115)">
|
||||
<ellipse cx="3.07711" cy="30.5" rx="31.3462" ry="22.5" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<ellipse cx="15.2434" cy="20.2949" rx="3.07692" ry="2.30769" transform="rotate(180 15.2434 20.2949)" fill="#FFB8B9"/>
|
||||
<path d="M14.7994 22.859C14.9968 22.5171 15.4903 22.5171 15.6877 22.859L16.1318 23.6282C16.3292 23.9701 16.0824 24.3974 15.6877 24.3974H14.7994C14.4047 24.3974 14.1579 23.9701 14.3553 23.6282L14.7994 22.859Z" fill="#4C320C"/>
|
||||
<ellipse cx="29.8075" cy="19.3464" rx="2.30769" ry="1.73077" transform="rotate(180 29.8075 19.3464)" fill="#FFB8B9"/>
|
||||
<ellipse cx="5.49476" cy="2.19199" rx="5.49476" ry="2.19199" transform="matrix(0.912659 0.408721 0.408721 -0.912659 41.571 57.001)" fill="#FFD38D"/>
|
||||
<ellipse cx="12.2567" cy="57.2463" rx="5.49476" ry="2.19199" transform="rotate(155.875 12.2567 57.2463)" fill="#FFD38D"/>
|
||||
<path d="M29.4743 21.2693C29.6224 21.0129 29.9925 21.0129 30.1405 21.2693L30.4736 21.8462C30.6216 22.1026 30.4366 22.4232 30.1405 22.4232H29.4743C29.1782 22.4232 28.9932 22.1026 29.1412 21.8462L29.4743 21.2693Z" fill="#4C320C"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_106_1356" x="0.628305" y="5.16666" width="92.5641" height="61.5385" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<filter id="chat_filter0_i_555_1115" x="21.7313" y="8" width="66.5384" height="46.1538" 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="-10" dy="1.53846"/>
|
||||
<feGaussianBlur stdDeviation="4.23077"/>
|
||||
<feOffset dx="-3.46154" dy="1.15385"/>
|
||||
<feGaussianBlur stdDeviation="3.17308"/>
|
||||
<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_106_1356"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_555_1115"/>
|
||||
</filter>
|
||||
<filter id="filter1_i_106_1356" x="-62.1924" y="5.16666" width="88.718" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<filter id="chat_filter1_i_555_1115" x="-28.269" y="8" width="66.5385" height="45" 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="5.12821"/>
|
||||
<feGaussianBlur stdDeviation="2.82051"/>
|
||||
<feOffset dx="3.84615"/>
|
||||
<feGaussianBlur stdDeviation="2.11538"/>
|
||||
<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_106_1356"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_555_1115"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.3 KiB |
3
assets/icons/svg/conversations.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.73307 11.6136L3.74905 10.8008L3.75652 10.795C3.96833 10.6256 4.07521 10.5401 4.19445 10.4792C4.30144 10.4245 4.41552 10.3847 4.5332 10.3606C4.66585 10.3333 4.80402 10.3333 5.08138 10.3333H10.8687C11.614 10.3333 11.9871 10.3333 12.272 10.1882C12.5229 10.0603 12.727 9.85614 12.8548 9.60526C13 9.32032 13 8.94766 13 8.20239V3.13127C13 2.386 13 2.0128 12.8548 1.72786C12.727 1.47698 12.5225 1.27316 12.2716 1.14532C11.9864 1 11.6135 1 10.8668 1H3.13346C2.38673 1 2.01308 1 1.72786 1.14532C1.47698 1.27316 1.27316 1.47698 1.14532 1.72786C1 2.01308 1 2.38673 1 3.13346V10.7808C1 11.4913 1 11.8464 1.14564 12.0289C1.2723 12.1876 1.46429 12.2799 1.66732 12.2796C1.90077 12.2794 2.17829 12.0574 2.73307 11.6136Z" fill="#FFF8DE" stroke="#4C320C" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 901 B |
3
assets/icons/svg/delete.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 8V15M7 8V15M3 4V15.8C3 16.9201 3 17.4798 3.21799 17.9076C3.40973 18.2839 3.71547 18.5905 4.0918 18.7822C4.5192 19 5.07899 19 6.19691 19H11.8031C12.921 19 13.48 19 13.9074 18.7822C14.2837 18.5905 14.5905 18.2839 14.7822 17.9076C15 17.4802 15 16.921 15 15.8031V4M3 4H5M3 4H1M5 4H13M5 4C5 3.06812 5 2.60241 5.15224 2.23486C5.35523 1.74481 5.74432 1.35523 6.23438 1.15224C6.60192 1 7.06812 1 8 1H10C10.9319 1 11.3978 1 11.7654 1.15224C12.2554 1.35523 12.6447 1.74481 12.8477 2.23486C12.9999 2.6024 13 3.06812 13 4M13 4H15M15 4H17" stroke="#E2793F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 726 B |
4
assets/icons/svg/edit.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 14.0002V10.6638L7.67272 3.99109L10.0654 1.59838L10.0669 1.59696C10.3962 1.26759 10.5612 1.10261 10.7514 1.04082C10.9189 0.986392 11.0994 0.986392 11.2669 1.04082C11.4569 1.10257 11.6217 1.26736 11.9506 1.59626L13.4018 3.04741C13.7321 3.37772 13.8973 3.54295 13.9592 3.7334C14.0136 3.90092 14.0136 4.08137 13.9592 4.24889C13.8973 4.4392 13.7323 4.60419 13.4025 4.93403L13.4018 4.93474L11.0091 7.32744L4.33636 14.0002L1 14.0002Z" fill="#AC7E35"/>
|
||||
<path d="M7.67272 3.99109L1 10.6638V14.0002L4.33636 14.0002L11.0091 7.32744M7.67272 3.99109L10.0654 1.59838L10.0669 1.59696C10.3962 1.26759 10.5612 1.10261 10.7514 1.04082C10.9189 0.986392 11.0994 0.986392 11.2669 1.04082C11.4569 1.10257 11.6217 1.26736 11.9506 1.59626L13.4018 3.04741C13.7321 3.37772 13.8973 3.54295 13.9592 3.7334C14.0136 3.90092 14.0136 4.08137 13.9592 4.24889C13.8973 4.4392 13.7323 4.60419 13.4025 4.93403L13.4018 4.93474L11.0091 7.32744M7.67272 3.99109L11.0091 7.32744" stroke="white" stroke-width="1.66818" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
44
assets/icons/svg/first.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<svg width="118" height="118" viewBox="0 0 118 118" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.9858 25.0493C50.6222 24.2532 52.3803 25.9262 53.1799 26.8622L50.2372 27.1049C49.6485 26.8466 49.3495 25.8454 49.9858 25.0493Z" fill="#FFDBA3"/>
|
||||
<path d="M50.4057 25.374C50.864 24.6694 51.9314 25.9913 52.4078 26.7403L50.9515 26.8181C50.5786 26.6303 49.9475 26.0786 50.4057 25.374Z" fill="#AC7E35"/>
|
||||
<path d="M67.1294 25.0493C66.493 24.2532 64.7349 25.9262 63.9354 26.8622L66.8781 27.1049C67.4667 26.8466 67.7658 25.8454 67.1294 25.0493Z" fill="#FFDBA3"/>
|
||||
<path d="M66.7095 25.374C66.2512 24.6694 65.1838 25.9913 64.7074 26.7403L66.1637 26.8181C66.5366 26.6303 67.1678 26.0786 66.7095 25.374Z" fill="#AC7E35"/>
|
||||
<ellipse cx="43.1438" cy="87.3043" rx="3.01003" ry="1.30435" fill="#AC7E35"/>
|
||||
<ellipse cx="75.01" cy="87.3043" rx="3.01003" ry="1.30435" fill="#AC7E35"/>
|
||||
<path d="M36.625 38.2274C46.4342 21.2375 70.9571 21.2375 80.7663 38.2274L87.457 49.816C97.2661 66.806 85.0047 88.0435 65.3863 88.0435H52.005C32.3866 88.0435 20.1252 66.806 29.9343 49.8161L36.625 38.2274Z" fill="#FFD18A"/>
|
||||
<rect x="57.1904" y="37.1235" width="1.00334" height="1.40468" rx="0.501672" transform="rotate(-180 57.1904 37.1235)" fill="#4C320C"/>
|
||||
<rect x="60.8027" y="37.1235" width="1.00334" height="1.40468" rx="0.501672" transform="rotate(-180 60.8027 37.1235)" fill="#4C320C"/>
|
||||
<path d="M44.3117 47.3987C51.2492 37.3134 66.1418 37.3134 73.0793 47.3987L80.9804 58.885C88.9482 70.4682 80.6557 86.2374 66.5967 86.2374H50.7944C36.7354 86.2374 28.4428 70.4682 36.4106 58.885L44.3117 47.3987Z" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter0_i_1464_207)">
|
||||
<ellipse cx="80.067" cy="51.4714" rx="24.8829" ry="20.9699" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_i_1464_207)">
|
||||
<path d="M62.2073 51.4714C62.2073 63.0527 51.0668 72.4413 37.3243 72.4413C23.5819 72.4413 12.4414 63.0527 12.4414 51.4714C12.4414 39.89 23.5819 30.5015 37.3243 30.5015C51.0668 30.5015 62.2073 39.89 62.2073 51.4714Z" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<ellipse cx="58.5953" cy="39.8326" rx="1.20401" ry="0.90301" transform="rotate(180 58.5953 39.8326)" fill="#FFB8B9"/>
|
||||
<ellipse cx="2.86683" cy="1.14365" rx="2.86683" ry="1.14365" transform="matrix(0.912659 0.408721 0.408721 -0.912659 64.3618 71.9202)" fill="#FFD38D"/>
|
||||
<ellipse cx="50.2413" cy="72.0481" rx="2.86683" ry="1.14365" transform="rotate(155.875 50.2413 72.0481)" fill="#FFD38D"/>
|
||||
<path d="M58.4214 40.8359C58.4986 40.7021 58.6917 40.7021 58.769 40.8359L58.9427 41.1369C59.02 41.2707 58.9234 41.4379 58.769 41.4379H58.4214C58.2669 41.4379 58.1704 41.2707 58.2476 41.1369L58.4214 40.8359Z" fill="#4C320C"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_1464_207" x="53.3781" y="30.5015" width="51.5721" height="42.5417" 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="-1.80602" dy="0.602007"/>
|
||||
<feGaussianBlur stdDeviation="1.65552"/>
|
||||
<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_1464_207"/>
|
||||
</filter>
|
||||
<filter id="filter1_i_1464_207" x="12.4414" y="30.5015" width="51.7728" height="41.9397" 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="2.00669"/>
|
||||
<feGaussianBlur stdDeviation="1.10368"/>
|
||||
<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_1464_207"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
4
assets/icons/svg/location.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 5.68124C1 8.96204 4.03179 11.6751 5.37373 12.7153C5.56579 12.8642 5.66296 12.9395 5.80625 12.9777C5.91782 13.0074 6.08203 13.0074 6.1936 12.9777C6.33715 12.9394 6.43365 12.8648 6.62643 12.7154C7.96837 11.6752 11 8.96234 11 5.68154C11 4.43995 10.4732 3.24908 9.53555 2.37115C8.59787 1.49322 7.32618 1 6.00009 1C4.674 1 3.40216 1.49329 2.46447 2.37122C1.52679 3.24915 1 4.43965 1 5.68124Z" stroke="#FFB645" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.57145 5.0572C4.57145 5.80411 5.21105 6.4096 6.00003 6.4096C6.78901 6.4096 7.42861 5.80411 7.42861 5.0572C7.42861 4.31029 6.78901 3.7048 6.00003 3.7048C5.21105 3.7048 4.57145 4.31029 4.57145 5.0572Z" stroke="#FFB645" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 846 B |
3
assets/icons/svg/logout.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 12L12 9M12 9L9 6M12 9H1M6 4.24859V4.2002C6 3.08009 6 2.51962 6.21799 2.0918C6.40973 1.71547 6.71547 1.40973 7.0918 1.21799C7.51962 1 8.08009 1 9.2002 1H13.8002C14.9203 1 15.4796 1 15.9074 1.21799C16.2837 1.40973 16.5905 1.71547 16.7822 2.0918C17 2.5192 17 3.07899 17 4.19691V13.8036C17 14.9215 17 15.4805 16.7822 15.9079C16.5905 16.2842 16.2837 16.5905 15.9074 16.7822C15.48 17 14.921 17 13.8031 17H9.19691C8.07899 17 7.5192 17 7.0918 16.7822C6.71547 16.5905 6.40973 16.2839 6.21799 15.9076C6 15.4798 6 14.9201 6 13.8V13.75" stroke="#E2793F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 723 B |
9
assets/icons/svg/medal.svg
Normal file
|
After Width: | Height: | Size: 302 KiB |
@ -1,17 +1,3 @@
|
||||
<svg width="402" height="97" viewBox="0 0 402 97" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_456_147)">
|
||||
<path d="M434.5 58C434.5 79.5391 417.039 97 395.5 97H9C-12.5391 97 -30 79.5391 -30 58V51C-30 29.4609 -12.5391 12 9 12H128.878C135.302 12 140 18.0761 140 24.5C140 53.4949 163.505 77 192.5 77C221.495 77 245 53.4949 245 24.5C245 18.0761 249.698 12 256.122 12H395.5C417.039 12 434.5 29.4609 434.5 51V58Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_456_147" x="-39.1" y="0.9" width="482.7" height="103.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<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 dy="-2"/>
|
||||
<feGaussianBlur stdDeviation="4.55"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_456_147"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_456_147" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<svg width="402" height="85" viewBox="0 0 402 85" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M412 73C412 79.6274 406.627 85 400 85H1.99999C-4.62742 85 -10 79.6274 -10 73V12C-10 5.37258 -4.62742 0 2 0H143.791C149.693 0 154 5.5976 154 11.5V11.5C154 37.7335 175.266 59 201.5 59C227.734 59 249 37.7335 249 11.5V11.5C249 5.5976 253.307 0 259.209 0H400C406.627 0 412 5.37258 412 12V73Z" fill="#FFF8DE"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 419 B |
43
assets/icons/svg/owner.svg
Normal file
@ -0,0 +1,43 @@
|
||||
<svg width="402" height="71" viewBox="0 0 402 71" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_611_1180" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="402" height="71">
|
||||
<rect width="402" height="71" fill="#FAF9F6"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_611_1180)">
|
||||
<path d="M210.544 -8.16855C211.587 -15.0351 227.663 -11.1522 235.571 -8.35252L219.031 3.10688C214.677 3.58837 209.5 -1.30203 210.544 -8.16855Z" fill="#FFDBA3"/>
|
||||
<path d="M214.128 -7.68358C214.432 -13.4033 225.235 -9.23902 230.599 -6.4419L222.27 -1.0205C219.429 -0.858303 213.824 -1.96384 214.128 -7.68358Z" fill="#AC7E35"/>
|
||||
<path d="M175.41 115.814C175.41 -17.8809 320.139 -101.44 435.923 -34.5929L514.897 11.003C630.681 77.8506 630.681 244.97 514.897 311.817L435.923 357.413C320.14 424.261 175.41 340.701 175.41 207.006L175.41 115.814Z" fill="#FFD18A"/>
|
||||
<rect x="293.023" y="39.2251" width="6.83761" height="9.57265" rx="3.4188" transform="rotate(150 293.023 39.2251)" fill="#4C320C"/>
|
||||
<rect x="314.341" y="26.9175" width="6.83761" height="9.57265" rx="3.4188" transform="rotate(150 314.341 26.9175)" fill="#4C320C"/>
|
||||
<path d="M252.027 143.749C258.605 60.5893 346.499 9.84374 421.807 45.7264L507.576 86.5938C594.069 127.806 598.861 249.129 515.887 297.034L422.625 350.879C339.652 398.784 236.978 333.973 244.534 238.462L252.027 143.749Z" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter0_i_611_1180)">
|
||||
<ellipse cx="413.275" cy="36.909" rx="112.137" ry="80" transform="rotate(-30 413.275 36.909)" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_i_611_1180)">
|
||||
<ellipse cx="248.064" cy="132.293" rx="111.453" ry="80" transform="rotate(-30 248.064 132.293)" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<ellipse cx="310.544" cy="50.427" rx="8.20513" ry="6.15385" transform="rotate(150 310.544 50.427)" fill="#FFB8B9"/>
|
||||
<path d="M312.938 56.9409C312.938 55.8882 314.077 55.2302 314.989 55.7566L317.04 56.9409C317.952 57.4672 317.952 58.7831 317.04 59.3095L314.989 60.4938C314.077 61.0202 312.938 60.3622 312.938 59.3095L312.938 56.9409Z" fill="#4C320C"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_611_1180" x="295.912" y="-52.2212" width="222.42" height="182.363" 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="-12.3077" dy="4.10256"/>
|
||||
<feGaussianBlur stdDeviation="11.2821"/>
|
||||
<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_611_1180"/>
|
||||
</filter>
|
||||
<filter id="filter1_i_611_1180" x="143.556" y="43.3774" width="222.692" height="177.832" 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="13.6752"/>
|
||||
<feGaussianBlur stdDeviation="7.52137"/>
|
||||
<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_611_1180"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
4
assets/icons/svg/points.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="14" height="18" viewBox="0 0 14 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 8.91577V13.9158C13 15.5726 10.3137 16.9158 7 16.9158C3.68629 16.9158 1 15.5726 1 13.9158V8.91577V3.91577C1 2.25892 3.68629 0.915771 7 0.915771C10.3137 0.915771 13 2.25892 13 3.91577V8.91577Z" fill="#FFF8DE"/>
|
||||
<path d="M13 8.91577V13.9158C13 15.5726 10.3137 16.9158 7 16.9158C3.68629 16.9158 1 15.5726 1 13.9158V8.91577M13 8.91577V3.91577M13 8.91577C13 10.5726 10.3137 11.9158 7 11.9158C3.68629 11.9158 1 10.5726 1 8.91577M13 3.91577C13 2.25892 10.3137 0.915771 7 0.915771C3.68629 0.915771 1 2.25892 1 3.91577M13 3.91577C13 5.57263 10.3137 6.91577 7 6.91577C3.68629 6.91577 1 5.57263 1 3.91577M1 8.91577V3.91577" stroke="#4C320C" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 794 B |
1
assets/icons/svg/refresh.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw-icon lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
||||
|
After Width: | Height: | Size: 324 B |
3
assets/icons/svg/rightArrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.88965 1L6.88965 6L1.88965 11" stroke="#4C320C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 226 B |
44
assets/icons/svg/second.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<svg width="118" height="118" viewBox="0 0 118 118" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.9858 25.0493C50.6222 24.2532 52.3803 25.9262 53.1799 26.8622L50.2372 27.1049C49.6485 26.8466 49.3495 25.8454 49.9858 25.0493Z" fill="#FFDBA3"/>
|
||||
<path d="M50.4057 25.374C50.864 24.6694 51.9314 25.9913 52.4078 26.7403L50.9515 26.8181C50.5786 26.6303 49.9475 26.0786 50.4057 25.374Z" fill="#AC7E35"/>
|
||||
<path d="M67.1294 25.0493C66.493 24.2532 64.7349 25.9262 63.9354 26.8622L66.8781 27.1049C67.4667 26.8466 67.7658 25.8454 67.1294 25.0493Z" fill="#FFDBA3"/>
|
||||
<path d="M66.71 25.374C66.2517 24.6694 65.1843 25.9913 64.7079 26.7403L66.1642 26.8181C66.5371 26.6303 67.1683 26.0786 66.71 25.374Z" fill="#AC7E35"/>
|
||||
<ellipse cx="43.1438" cy="87.3043" rx="3.01003" ry="1.30435" fill="#AC7E35"/>
|
||||
<ellipse cx="74.6526" cy="87.3043" rx="3.01003" ry="1.30435" fill="#AC7E35"/>
|
||||
<path d="M36.625 38.2274C46.4342 21.2375 70.9571 21.2375 80.7663 38.2274L87.457 49.816C97.2661 66.806 85.0047 88.0435 65.3863 88.0435H52.005C32.3866 88.0435 20.1252 66.806 29.9343 49.8161L36.625 38.2274Z" fill="#FFD18A"/>
|
||||
<rect x="57.1909" y="37.1235" width="1.00334" height="1.40468" rx="0.501672" transform="rotate(-180 57.1909 37.1235)" fill="#4C320C"/>
|
||||
<rect x="60.8027" y="37.1235" width="1.00334" height="1.40468" rx="0.501672" transform="rotate(-180 60.8027 37.1235)" fill="#4C320C"/>
|
||||
<path d="M44.3122 47.3987C51.2496 37.3134 66.1423 37.3134 73.0798 47.3987L80.9809 58.885C88.9487 70.4682 80.6562 86.2374 66.5972 86.2374H50.7948C36.7358 86.2374 28.4433 70.4682 36.4111 58.885L44.3122 47.3987Z" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter0_i_1464_224)">
|
||||
<ellipse cx="75.552" cy="49.5652" rx="19.7659" ry="16.6555" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_i_1464_224)">
|
||||
<ellipse cx="41.6389" cy="49.5652" rx="19.7659" ry="16.6555" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<ellipse cx="58.5953" cy="39.8326" rx="1.20401" ry="0.90301" transform="rotate(180 58.5953 39.8326)" fill="#FFB8B9"/>
|
||||
<ellipse cx="2.86683" cy="1.14365" rx="2.86683" ry="1.14365" transform="matrix(0.912659 0.408721 0.408721 -0.912659 64.1611 66.9036)" fill="#FFD38D"/>
|
||||
<ellipse cx="50.0406" cy="67.0315" rx="2.86683" ry="1.14365" transform="rotate(155.875 50.0406 67.0315)" fill="#FFD38D"/>
|
||||
<path d="M58.4219 40.8359C58.4991 40.7021 58.6922 40.7021 58.7694 40.8359L58.9432 41.1369C59.0205 41.2707 58.9239 41.4379 58.7694 41.4379H58.4219C58.2674 41.4379 58.1709 41.2707 58.2481 41.1369L58.4219 40.8359Z" fill="#4C320C"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_1464_224" x="53.9801" y="32.9097" width="41.3378" height="33.913" 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="-1.80602" dy="0.602007"/>
|
||||
<feGaussianBlur stdDeviation="1.65552"/>
|
||||
<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_1464_224"/>
|
||||
</filter>
|
||||
<filter id="filter1_i_1464_224" x="21.873" y="32.9097" width="41.5384" height="33.311" 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="2.00669"/>
|
||||
<feGaussianBlur stdDeviation="1.10368"/>
|
||||
<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_1464_224"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
6
assets/icons/svg/setting.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.5179 6.65727L17.1881 6.47385C17.1369 6.44536 17.1118 6.43107 17.087 6.41626C16.8412 6.26897 16.6339 6.06548 16.4827 5.82202C16.4675 5.79752 16.4531 5.77183 16.4238 5.72106C16.3945 5.67035 16.3797 5.64466 16.366 5.61922C16.2304 5.36593 16.157 5.08366 16.1526 4.79637C16.1522 4.76749 16.1523 4.73798 16.1533 4.67931L16.1597 4.29642C16.17 3.68371 16.1752 3.3764 16.0891 3.10061C16.0126 2.85564 15.8846 2.63001 15.7138 2.43852C15.5206 2.22208 15.2533 2.06766 14.718 1.75922L14.2734 1.50302C13.7397 1.19544 13.4727 1.04159 13.1893 0.982942C12.9386 0.931055 12.6799 0.93346 12.4301 0.989536C12.1482 1.05283 11.8845 1.21068 11.3576 1.52621L11.3546 1.52764L11.036 1.71838C10.9857 1.74855 10.9602 1.76375 10.9349 1.77779C10.6844 1.91709 10.4047 1.99411 10.1181 2.00331C10.0893 2.00424 10.0599 2.00424 10.0012 2.00424C9.94279 2.00424 9.91216 2.00424 9.88334 2.00331C9.5962 1.99407 9.31592 1.91663 9.06498 1.77675C9.03969 1.76265 9.01467 1.74733 8.9642 1.71703L8.6436 1.52456C8.11305 1.20605 7.84738 1.04655 7.56388 0.982942C7.31307 0.926668 7.05347 0.925111 6.80186 0.977666C6.51775 1.03701 6.25072 1.19199 5.71664 1.50196L5.71427 1.50302L5.2752 1.75785L5.27034 1.76082C4.74108 2.068 4.47581 2.22196 4.28429 2.43752C4.11434 2.62881 3.98731 2.85409 3.91127 3.09841C3.82533 3.37458 3.82991 3.68255 3.84027 4.29815L3.8467 4.68049C3.84768 4.73839 3.84936 4.76716 3.84894 4.79564C3.84468 5.08352 3.77033 5.36636 3.63428 5.6201C3.62082 5.64519 3.60633 5.67029 3.57737 5.72041C3.5484 5.77057 3.53437 5.79551 3.51934 5.81972C3.36743 6.06448 3.15925 6.26916 2.91183 6.4167C2.88735 6.43129 2.86156 6.44532 2.81088 6.47341L2.48535 6.65381C1.94373 6.95396 1.67298 7.10416 1.47597 7.31792C1.30169 7.50702 1.17003 7.73132 1.08966 7.9756C0.998814 8.25174 0.99889 8.56138 1.0003 9.18061L1.00145 9.68672C1.00284 10.3018 1.00476 10.6092 1.09581 10.8834C1.17636 11.1261 1.30706 11.349 1.48037 11.537C1.67626 11.7495 1.94432 11.8987 2.48183 12.1978L2.80446 12.3772C2.85937 12.4078 2.887 12.4229 2.91348 12.4388C3.15866 12.5864 3.36521 12.7906 3.51583 13.0339C3.5321 13.0602 3.54772 13.0875 3.57895 13.1421C3.6098 13.196 3.62559 13.2229 3.63985 13.2499C3.77191 13.4999 3.84262 13.7777 3.84744 14.0604C3.84796 14.091 3.84751 14.1219 3.84646 14.184L3.84027 14.5509C3.82984 15.1686 3.8253 15.4778 3.91175 15.7547C3.98823 15.9997 4.11605 16.2253 4.28693 16.4168C4.48007 16.6333 4.74784 16.7876 5.28311 17.096L5.72762 17.3522C6.2614 17.6598 6.52819 17.8134 6.81156 17.8721C7.06226 17.924 7.32112 17.922 7.57092 17.8659C7.85325 17.8025 8.11779 17.6441 8.64624 17.3277L8.96481 17.1369C9.0152 17.1068 9.04072 17.0916 9.06597 17.0776C9.31652 16.9383 9.59594 16.8609 9.88246 16.8517C9.91134 16.8507 9.94071 16.8507 9.99945 16.8507C10.0583 16.8507 10.0877 16.8507 10.1166 16.8517C10.4037 16.8609 10.6849 16.9386 10.9358 17.0785C10.9579 17.0908 10.98 17.1041 11.0188 17.1274L11.3575 17.3307C11.8881 17.6493 12.1532 17.8083 12.4368 17.8719C12.6876 17.9282 12.9474 17.9305 13.199 17.8779C13.483 17.8186 13.7506 17.6633 14.2844 17.3535L14.73 17.0948C15.2596 16.7875 15.5252 16.6333 15.7168 16.4177C15.8867 16.2264 16.0139 16.0012 16.09 15.7569C16.1753 15.4828 16.1701 15.1772 16.1599 14.5704L16.1533 14.1747C16.1523 14.1168 16.1522 14.088 16.1526 14.0596C16.1569 13.7717 16.23 13.4886 16.366 13.2349C16.3795 13.2099 16.3941 13.1846 16.4229 13.1346C16.4519 13.0844 16.4669 13.0594 16.4819 13.0352C16.6338 12.7905 16.8422 12.5856 17.0897 12.4381C17.1138 12.4237 17.1387 12.4099 17.1882 12.3825L17.1899 12.3817L17.5154 12.2013C18.057 11.9011 18.3283 11.7508 18.5253 11.537C18.6996 11.3479 18.8311 11.1239 18.9115 10.8796C19.0018 10.6051 19.0011 10.2973 18.9997 9.68528L18.9985 9.1683C18.9971 8.55321 18.9964 8.24591 18.9053 7.97165C18.8248 7.729 18.6933 7.50602 18.52 7.31806C18.3243 7.10581 18.0559 6.95648 17.5194 6.65804L17.5179 6.65727Z" fill="#AC7E35"/>
|
||||
<path d="M6.39888 9.42768C6.39888 11.4167 8.01128 13.0291 10.0003 13.0291C11.9893 13.0291 13.6017 11.4167 13.6017 9.42768C13.6017 7.43868 11.9893 5.82628 10.0003 5.82628C8.01128 5.82628 6.39888 7.43868 6.39888 9.42768Z" fill="#AC7E35"/>
|
||||
<path d="M17.5179 6.65727L17.1881 6.47385C17.1369 6.44536 17.1118 6.43107 17.087 6.41626C16.8412 6.26897 16.6339 6.06548 16.4827 5.82202C16.4675 5.79752 16.4531 5.77183 16.4238 5.72106C16.3945 5.67035 16.3797 5.64466 16.366 5.61922C16.2304 5.36593 16.157 5.08366 16.1526 4.79637C16.1522 4.76749 16.1523 4.73798 16.1533 4.67931L16.1597 4.29642C16.17 3.68371 16.1752 3.3764 16.0891 3.10061C16.0126 2.85564 15.8846 2.63001 15.7138 2.43852C15.5206 2.22208 15.2533 2.06766 14.718 1.75922L14.2734 1.50302C13.7397 1.19544 13.4727 1.04159 13.1893 0.982942C12.9386 0.931055 12.6799 0.93346 12.4301 0.989536C12.1482 1.05283 11.8845 1.21068 11.3576 1.52621L11.3546 1.52764L11.036 1.71838C10.9857 1.74855 10.9602 1.76375 10.9349 1.77779C10.6844 1.91709 10.4047 1.99411 10.1181 2.00331C10.0893 2.00424 10.0599 2.00424 10.0012 2.00424C9.94279 2.00424 9.91216 2.00424 9.88334 2.00331C9.5962 1.99407 9.31592 1.91663 9.06498 1.77675C9.03969 1.76265 9.01467 1.74733 8.9642 1.71703L8.6436 1.52456C8.11305 1.20605 7.84738 1.04655 7.56388 0.982942C7.31307 0.926668 7.05347 0.925111 6.80186 0.977666C6.51775 1.03701 6.25072 1.19199 5.71664 1.50196L5.71427 1.50302L5.2752 1.75785L5.27034 1.76082C4.74108 2.068 4.47581 2.22196 4.28429 2.43752C4.11434 2.62881 3.98731 2.85409 3.91127 3.09841C3.82533 3.37458 3.82991 3.68255 3.84027 4.29815L3.8467 4.68049C3.84768 4.73839 3.84936 4.76716 3.84894 4.79564C3.84468 5.08352 3.77033 5.36636 3.63428 5.6201C3.62082 5.64519 3.60633 5.67029 3.57737 5.72041C3.5484 5.77057 3.53437 5.79551 3.51934 5.81972C3.36743 6.06448 3.15925 6.26916 2.91183 6.4167C2.88735 6.43129 2.86156 6.44532 2.81088 6.47341L2.48535 6.65381C1.94373 6.95396 1.67298 7.10416 1.47597 7.31792C1.30169 7.50702 1.17003 7.73132 1.08966 7.9756C0.998814 8.25174 0.99889 8.56138 1.0003 9.18061L1.00145 9.68672C1.00284 10.3018 1.00476 10.6092 1.09581 10.8834C1.17636 11.1261 1.30706 11.349 1.48037 11.537C1.67626 11.7495 1.94432 11.8987 2.48183 12.1978L2.80446 12.3772C2.85937 12.4078 2.887 12.4229 2.91348 12.4388C3.15866 12.5864 3.36521 12.7906 3.51583 13.0339C3.5321 13.0602 3.54772 13.0875 3.57895 13.1421C3.6098 13.196 3.62559 13.2229 3.63985 13.2499C3.77191 13.4999 3.84262 13.7777 3.84744 14.0604C3.84796 14.091 3.84751 14.1219 3.84646 14.184L3.84027 14.5509C3.82984 15.1686 3.8253 15.4778 3.91175 15.7547C3.98823 15.9997 4.11605 16.2253 4.28693 16.4168C4.48007 16.6333 4.74784 16.7876 5.28311 17.096L5.72762 17.3522C6.2614 17.6598 6.52819 17.8134 6.81156 17.8721C7.06226 17.924 7.32112 17.922 7.57092 17.8659C7.85325 17.8025 8.11779 17.6441 8.64624 17.3277L8.96481 17.1369C9.0152 17.1068 9.04072 17.0916 9.06597 17.0776C9.31652 16.9383 9.59594 16.8609 9.88246 16.8517C9.91134 16.8507 9.94071 16.8507 9.99945 16.8507C10.0583 16.8507 10.0877 16.8507 10.1166 16.8517C10.4037 16.8609 10.6849 16.9386 10.9358 17.0785C10.9579 17.0908 10.98 17.1041 11.0188 17.1274L11.3575 17.3307C11.8881 17.6493 12.1532 17.8083 12.4368 17.8719C12.6876 17.9282 12.9474 17.9305 13.199 17.8779C13.483 17.8186 13.7506 17.6633 14.2844 17.3535L14.73 17.0948C15.2596 16.7875 15.5252 16.6333 15.7168 16.4177C15.8867 16.2264 16.0139 16.0012 16.09 15.7569C16.1753 15.4828 16.1701 15.1772 16.1599 14.5704L16.1533 14.1747C16.1523 14.1168 16.1522 14.088 16.1526 14.0596C16.1569 13.7717 16.23 13.4886 16.366 13.2349C16.3795 13.2099 16.3941 13.1846 16.4229 13.1346C16.4519 13.0844 16.4669 13.0594 16.4819 13.0352C16.6338 12.7905 16.8422 12.5856 17.0897 12.4381C17.1138 12.4237 17.1387 12.4099 17.1882 12.3825L17.1899 12.3817L17.5154 12.2013C18.057 11.9011 18.3283 11.7508 18.5253 11.537C18.6996 11.3479 18.8311 11.1239 18.9115 10.8796C19.0018 10.6051 19.0011 10.2973 18.9997 9.68528L18.9985 9.1683C18.9971 8.55321 18.9964 8.24591 18.9053 7.97165C18.8248 7.729 18.6933 7.50602 18.52 7.31806C18.3243 7.10581 18.0559 6.95648 17.5194 6.65804L17.5179 6.65727Z" stroke="white" stroke-width="1.8007" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.39888 9.42768C6.39888 11.4167 8.01128 13.0291 10.0003 13.0291C11.9893 13.0291 13.6017 11.4167 13.6017 9.42768C13.6017 7.43868 11.9893 5.82628 10.0003 5.82628C8.01128 5.82628 6.39888 7.43868 6.39888 9.42768Z" stroke="white" stroke-width="1.8007" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
4
assets/icons/svg/stories.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.44725 11.9956L8 12.6665L7.54771 11.9882C7.14578 11.3853 6.94422 11.083 6.67763 10.8639C6.44065 10.6691 6.16682 10.5232 5.87331 10.434C5.54177 10.3333 5.17662 10.3333 4.44612 10.3333H2.24286C1.80812 10.3333 1.5908 10.3333 1.42459 10.2486C1.27824 10.1741 1.15934 10.0548 1.08477 9.90843C1 9.74206 1 9.52456 1 9.08896V2.24452C1 1.80892 1 1.59096 1.08477 1.42459C1.15934 1.27824 1.27824 1.15934 1.42459 1.08477C1.59096 1 1.80854 1 2.24414 1H4.26636C5.57315 1 6.2267 1 6.72583 1.25432C7.16487 1.47802 7.52185 1.8351 7.74555 2.27414C7.99987 2.77327 8 3.42639 8 4.73318C8 3.42639 8 2.77327 8.25432 2.27414C8.47802 1.8351 8.83472 1.47802 9.27376 1.25432C9.77289 1 10.4264 1 11.7332 1H13.7554C14.191 1 14.409 1 14.5753 1.08477C14.7217 1.15934 14.8404 1.27824 14.9149 1.42459C14.9997 1.59096 15 1.80892 15 2.24452V9.08896C15 9.52456 14.9997 9.74206 14.9149 9.90843C14.8404 10.0548 14.7219 10.1741 14.5756 10.2486C14.4094 10.3333 14.1919 10.3333 13.7571 10.3333H11.5539C10.8234 10.3333 10.4575 10.3333 10.126 10.434C9.83247 10.5232 9.55996 10.6691 9.32299 10.8639C9.05531 11.0839 8.85246 11.3878 8.44725 11.9956Z" fill="#FFF8DE"/>
|
||||
<path d="M8 4.73318V12.6665M8 4.73318C8 3.42639 8 2.77327 8.25432 2.27414C8.47802 1.8351 8.83472 1.47802 9.27376 1.25432C9.77289 1 10.4264 1 11.7332 1H13.7554C14.191 1 14.409 1 14.5753 1.08477C14.7217 1.15934 14.8404 1.27824 14.9149 1.42459C14.9997 1.59096 15 1.80892 15 2.24452V9.08896C15 9.52456 14.9997 9.74206 14.9149 9.90843C14.8404 10.0548 14.7219 10.1741 14.5756 10.2486C14.4094 10.3333 14.1919 10.3333 13.7571 10.3333H11.5539C10.8234 10.3333 10.4575 10.3333 10.126 10.434C9.83247 10.5232 9.55996 10.6691 9.32299 10.8639C9.05531 11.0839 8.85246 11.3878 8.44725 11.9956L8 12.6665M8 4.73318C8 3.42639 7.99987 2.77327 7.74555 2.27414C7.52185 1.8351 7.16487 1.47802 6.72583 1.25432C6.2267 1 5.57315 1 4.26636 1H2.24414C1.80854 1 1.59096 1 1.42459 1.08477C1.27824 1.15934 1.15934 1.27824 1.08477 1.42459C1 1.59096 1 1.80892 1 2.24452V9.08896C1 9.52456 1 9.74206 1.08477 9.90843C1.15934 10.0548 1.27824 10.1741 1.42459 10.2486C1.5908 10.3333 1.80812 10.3333 2.24286 10.3333H4.44612C5.17662 10.3333 5.54177 10.3333 5.87331 10.434C6.16682 10.5232 6.44065 10.6691 6.67763 10.8639C6.94422 11.083 7.14578 11.3853 7.54771 11.9882L8 12.6665" stroke="#4C320C" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
6
assets/icons/svg/tag.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.28668 3.45116L1.03918 6.1737C1.00252 6.57691 0.984036 6.78021 1.01759 6.9732C1.04757 7.14563 1.10745 7.31155 1.19481 7.46321C1.29295 7.6336 1.43801 7.77866 1.72648 8.06712L5.59383 11.9345C6.18194 12.5226 6.47615 12.8168 6.81631 12.9273C7.11647 13.0249 7.44032 13.0251 7.74048 12.9275C8.08171 12.8167 8.37823 12.5205 8.97007 11.9287L11.929 8.96974C12.5208 8.3779 12.8163 8.08211 12.9271 7.74087C13.0247 7.44072 13.0241 7.11723 12.9266 6.81707C12.8157 6.47584 12.5207 6.18011 11.9289 5.58827L8.07104 1.73046C7.77994 1.43936 7.63434 1.29379 7.46318 1.1952C7.31152 1.10785 7.14559 1.04772 6.97316 1.01774C6.77856 0.983897 6.57347 1.0025 6.16348 1.03977L3.45113 1.28635C2.74516 1.35053 2.39196 1.38276 2.11572 1.53637C1.87218 1.6718 1.6714 1.87258 1.53597 2.11612C1.38313 2.39098 1.35125 2.74168 1.28771 3.44062L1.28668 3.45116Z" fill="#FFF8DE"/>
|
||||
<path d="M4.9965 4.99642C5.28831 4.7046 5.28831 4.23147 4.9965 3.93966C4.70468 3.64784 4.23122 3.64784 3.9394 3.93966C3.64758 4.23147 3.6473 4.70449 3.93912 4.99631C4.23094 5.28812 4.70468 5.28823 4.9965 4.99642Z" fill="#FAF9F6"/>
|
||||
<path d="M1.28668 3.45116L1.03918 6.1737C1.00252 6.57691 0.984036 6.78021 1.01759 6.9732C1.04757 7.14563 1.10745 7.31155 1.19481 7.46321C1.29295 7.6336 1.43801 7.77866 1.72648 8.06712L5.59383 11.9345C6.18194 12.5226 6.47615 12.8168 6.81631 12.9273C7.11647 13.0249 7.44032 13.0251 7.74048 12.9275C8.08171 12.8167 8.37823 12.5205 8.97007 11.9287L11.929 8.96974C12.5208 8.3779 12.8163 8.08211 12.9271 7.74087C13.0247 7.44072 13.0241 7.11723 12.9266 6.81707C12.8157 6.47584 12.5207 6.18011 11.9289 5.58827L8.07104 1.73046C7.77994 1.43936 7.63434 1.29379 7.46318 1.1952C7.31152 1.10785 7.14559 1.04772 6.97316 1.01774C6.77856 0.983897 6.57347 1.0025 6.16348 1.03977L3.45113 1.28635C2.74516 1.35053 2.39196 1.38276 2.11572 1.53637C1.87218 1.6718 1.6714 1.87258 1.53597 2.11612C1.38313 2.39098 1.35125 2.74168 1.28771 3.44062L1.28668 3.45116Z" stroke="#4C320C" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.9965 4.99642C5.28831 4.7046 5.28831 4.23147 4.9965 3.93966C4.70468 3.64784 4.23122 3.64784 3.9394 3.93966C3.64758 4.23147 3.6473 4.70449 3.93912 4.99631C4.23094 5.28812 4.70468 5.28823 4.9965 4.99642Z" stroke="#4C320C" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
44
assets/icons/svg/third.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<svg width="118" height="118" viewBox="0 0 118 118" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M50.1865 25.0493C50.8229 24.2532 52.581 25.9262 53.3805 26.8622L50.4379 27.1049C49.8492 26.8466 49.5502 25.8454 50.1865 25.0493Z" fill="#FFDBA3"/>
|
||||
<path d="M50.6059 25.374C51.0642 24.6694 52.1316 25.9913 52.608 26.7403L51.1517 26.8181C50.7788 26.6303 50.1477 26.0786 50.6059 25.374Z" fill="#AC7E35"/>
|
||||
<path d="M67.3296 25.0493C66.6932 24.2532 64.9351 25.9262 64.1356 26.8622L67.0783 27.1049C67.6669 26.8466 67.966 25.8454 67.3296 25.0493Z" fill="#FFDBA3"/>
|
||||
<path d="M66.9102 25.374C66.4519 24.6694 65.3845 25.9913 64.9081 26.7403L66.3644 26.8181C66.7373 26.6303 67.3685 26.0786 66.9102 25.374Z" fill="#AC7E35"/>
|
||||
<ellipse cx="43.1433" cy="87.3043" rx="3.01003" ry="1.30435" fill="#AC7E35"/>
|
||||
<ellipse cx="74.6521" cy="87.3043" rx="3.01003" ry="1.30435" fill="#AC7E35"/>
|
||||
<path d="M36.625 38.2274C46.4342 21.2375 70.9571 21.2375 80.7663 38.2274L87.457 49.816C97.2661 66.806 85.0047 88.0435 65.3863 88.0435H52.005C32.3866 88.0435 20.1252 66.806 29.9343 49.8161L36.625 38.2274Z" fill="#FFD18A"/>
|
||||
<rect x="57.1904" y="37.1235" width="1.00334" height="1.40468" rx="0.501672" transform="rotate(-180 57.1904 37.1235)" fill="#4C320C"/>
|
||||
<rect x="60.8022" y="37.1235" width="1.00334" height="1.40468" rx="0.501672" transform="rotate(-180 60.8022 37.1235)" fill="#4C320C"/>
|
||||
<path d="M44.3117 47.3987C51.2492 37.3134 66.1418 37.3134 73.0793 47.3987L80.9804 58.885C88.9482 70.4682 80.6557 86.2374 66.5967 86.2374H50.7944C36.7354 86.2374 28.4428 70.4682 36.4106 58.885L44.3117 47.3987Z" fill="#FFF8DE"/>
|
||||
<g filter="url(#filter0_i_1464_241)">
|
||||
<ellipse cx="72.6419" cy="45.652" rx="16.4548" ry="11.7391" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_i_1464_241)">
|
||||
<ellipse cx="44.6485" cy="45.652" rx="16.3545" ry="11.7391" fill="#FFF8DE"/>
|
||||
</g>
|
||||
<ellipse cx="58.5953" cy="39.8326" rx="1.20401" ry="0.90301" transform="rotate(180 58.5953 39.8326)" fill="#FFB8B9"/>
|
||||
<ellipse cx="2.86683" cy="1.14365" rx="2.86683" ry="1.14365" transform="matrix(0.912659 0.408721 0.408721 -0.912659 63.5591 59.4788)" fill="#FFD38D"/>
|
||||
<ellipse cx="49.4381" cy="59.6067" rx="2.86683" ry="1.14365" transform="rotate(155.875 49.4381 59.6067)" fill="#FFD38D"/>
|
||||
<path d="M58.4214 40.8359C58.4986 40.7021 58.6917 40.7021 58.769 40.8359L58.9427 41.1369C59.02 41.2707 58.9234 41.4379 58.769 41.4379H58.4214C58.2669 41.4379 58.1704 41.2707 58.2476 41.1369L58.4214 40.8359Z" fill="#4C320C"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_1464_241" x="54.381" y="33.9128" width="34.7157" height="24.0803" 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="-1.80602" dy="0.602007"/>
|
||||
<feGaussianBlur stdDeviation="1.65552"/>
|
||||
<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_1464_241"/>
|
||||
</filter>
|
||||
<filter id="filter1_i_1464_241" x="28.2939" y="33.9128" width="34.7157" height="23.4783" 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="2.00669"/>
|
||||
<feGaussianBlur stdDeviation="1.10368"/>
|
||||
<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_1464_241"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
4
assets/icons/svg/usedStorage.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="21" height="14" viewBox="0 0 21 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.45455 6.0586C2.54665 6.0586 1 7.59362 1 9.48715C1 11.3807 2.54665 12.9157 4.45455 12.9157L15.6818 12.9158C18.0667 12.9158 20 10.9968 20 8.62989C20 6.35844 18.2191 4.49985 15.9677 4.3536C14.9994 2.32151 12.9153 0.915771 10.5 0.915771C7.45422 0.915771 4.9341 3.15121 4.51527 6.05913C4.49493 6.05878 4.47497 6.0586 4.45455 6.0586Z" fill="#FFF8DE"/>
|
||||
<path d="M13.0909 7.77288H7.90909M1 9.48715C1 7.59362 2.54665 6.0586 4.45455 6.0586C4.47497 6.0586 4.49493 6.05878 4.51527 6.05913C4.9341 3.15121 7.45422 0.915771 10.5 0.915771C12.9153 0.915771 14.9994 2.32151 15.9677 4.3536C18.2191 4.49985 20 6.35844 20 8.62989C20 10.9968 18.0667 12.9158 15.6818 12.9158L4.45455 12.9157C2.54665 12.9157 1 11.3807 1 9.48715Z" stroke="#4C320C" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 887 B |
BIN
assets/images/png/owner/abstract.png
Normal file
|
After Width: | Height: | Size: 862 KiB |
BIN
assets/images/png/owner/animals.png
Normal file
|
After Width: | Height: | Size: 960 KiB |
BIN
assets/images/png/owner/life.png
Normal file
|
After Width: | Height: | Size: 783 KiB |
BIN
assets/images/png/owner/people.png
Normal file
|
After Width: | Height: | Size: 466 KiB |
BIN
assets/images/png/owner/scenery.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
173
assets/json/classify.json
Normal file
@ -0,0 +1,173 @@
|
||||
[
|
||||
{
|
||||
"name": "摄影分类",
|
||||
"children": [
|
||||
{
|
||||
"name": "人物",
|
||||
"children": [
|
||||
{
|
||||
"name": "男性"
|
||||
},
|
||||
{
|
||||
"name": "女性"
|
||||
},
|
||||
{
|
||||
"name": "老人",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量/时长:"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "小孩",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:上海市第一人类幼崽萌主"
|
||||
},
|
||||
{
|
||||
"name": "时长:上海市第一成长记录仪MAX"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "风景",
|
||||
"children": [
|
||||
{
|
||||
"name": "自然风景",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:上海市多巴胺风景供应商TOP1"
|
||||
},
|
||||
{
|
||||
"name": "时长:上海市第一心灵SPA持久包"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "建筑物",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:上海市第一城市景观收藏家"
|
||||
},
|
||||
{
|
||||
"name": "时长:徐汇区第一光影驻留大师"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "静物",
|
||||
"children": [
|
||||
{
|
||||
"name": "美食",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:上海市第一人间美味种草机"
|
||||
},
|
||||
{
|
||||
"name": "时长:上海市第一恩格尔系数爆破手"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "艺术品(雕像)"
|
||||
},
|
||||
{
|
||||
"name": "花草",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:上海市第一莫奈花园造访者"
|
||||
},
|
||||
{
|
||||
"name": "时长:徐汇区第一春日魔法师"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "日常物品",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:上海市第一闲鱼の神预备役"
|
||||
},
|
||||
{
|
||||
"name": "时长:徐汇区第一人间杂货铺店长"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "动物",
|
||||
"children": [
|
||||
{
|
||||
"name": "猫",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:全国第一猫猫重度依赖症患者"
|
||||
},
|
||||
{
|
||||
"name": "时长:上海市第一最佳铲屎官"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "狗",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:徐汇区第一狗狗教教主"
|
||||
},
|
||||
{
|
||||
"name": "时长:上海市第一修狗快乐永动机"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "鸟",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:上海市第一天籁点唱机"
|
||||
},
|
||||
{
|
||||
"name": "时长:上海市第一天籁电台24h主播"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "抽象",
|
||||
"children": [
|
||||
{
|
||||
"name": "游戏动漫",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:徐汇区第一次元壁拆迁办"
|
||||
},
|
||||
{
|
||||
"name": "时长:全国第一最强爆肝王"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "艺术作品",
|
||||
"children": [
|
||||
{
|
||||
"name": "数量:徐汇区第一灵感批发商"
|
||||
},
|
||||
{
|
||||
"name": "时长:上海市第一浪漫创作官"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "其他"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
552
assets/json/location.json
Normal file
@ -0,0 +1,552 @@
|
||||
[
|
||||
{
|
||||
"name": "北京市",
|
||||
"shortCode": "BJ",
|
||||
"regions": [
|
||||
{
|
||||
"name": "东城区",
|
||||
"shortCode": "Dongcheng"
|
||||
},
|
||||
{
|
||||
"name": "西城区",
|
||||
"shortCode": "Xicheng"
|
||||
},
|
||||
{
|
||||
"name": "朝阳区",
|
||||
"shortCode": "Chaoyang"
|
||||
},
|
||||
{
|
||||
"name": "海淀区",
|
||||
"shortCode": "Haidian"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "广东省",
|
||||
"shortCode": "GD",
|
||||
"regions": [
|
||||
{
|
||||
"name": "广州市",
|
||||
"shortCode": "Guangzhou"
|
||||
},
|
||||
{
|
||||
"name": "深圳市",
|
||||
"shortCode": "Shenzhen"
|
||||
},
|
||||
{
|
||||
"name": "珠海市",
|
||||
"shortCode": "Zhuhai"
|
||||
},
|
||||
{
|
||||
"name": "佛山市",
|
||||
"shortCode": "Foshan"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "河南省",
|
||||
"shortCode": "Henan",
|
||||
"regions": [
|
||||
{
|
||||
"name": "郑州市",
|
||||
"shortCode": "Zhengzhou",
|
||||
"regions": [
|
||||
{
|
||||
"name": "中原区",
|
||||
"shortCode": "zhongyuan"
|
||||
},
|
||||
{
|
||||
"name": "二七区",
|
||||
"shortCode": "erqi"
|
||||
},
|
||||
{
|
||||
"name": "管城回族区",
|
||||
"shortCode": "guancheng"
|
||||
},
|
||||
{
|
||||
"name": "金水区",
|
||||
"shortCode": "jinsui"
|
||||
},
|
||||
{
|
||||
"name": "上街区",
|
||||
"shortCode": "shangjiao"
|
||||
},
|
||||
{
|
||||
"name": "惠济区",
|
||||
"shortCode": "huiji"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "开封市",
|
||||
"shortCode": "Kaifeng",
|
||||
"regions": [
|
||||
{
|
||||
"name": "龙亭区",
|
||||
"shortCode": "longting"
|
||||
},
|
||||
{
|
||||
"name": "顺河回族区",
|
||||
"shortCode": "shunhe"
|
||||
},
|
||||
{
|
||||
"name": "鼓楼区",
|
||||
"shortCode": "gulou"
|
||||
},
|
||||
{
|
||||
"name": "禹王台区",
|
||||
"shortCode": "yudang"
|
||||
},
|
||||
{
|
||||
"name": "祥符区",
|
||||
"shortCode": "xiangfu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "洛阳市",
|
||||
"shortCode": "Luoyang",
|
||||
"regions": [
|
||||
{
|
||||
"name": "老城区",
|
||||
"shortCode": "laocheng"
|
||||
},
|
||||
{
|
||||
"name": "西工区",
|
||||
"shortCode": "xigong"
|
||||
},
|
||||
{
|
||||
"name": "瀍河回族区",
|
||||
"shortCode": "chanhe"
|
||||
},
|
||||
{
|
||||
"name": "涧西区",
|
||||
"shortCode": "jianxi"
|
||||
},
|
||||
{
|
||||
"name": "吉利区",
|
||||
"shortCode": "jili"
|
||||
},
|
||||
{
|
||||
"name": "洛龙区",
|
||||
"shortCode": "luorong"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "平顶山市",
|
||||
"shortCode": "Pingdingshan",
|
||||
"regions": [
|
||||
{
|
||||
"name": "新华区",
|
||||
"shortCode": "xihu"
|
||||
},
|
||||
{
|
||||
"name": "卫东区",
|
||||
"shortCode": "weidong"
|
||||
},
|
||||
{
|
||||
"name": "石龙区",
|
||||
"shortCode": "shilong"
|
||||
},
|
||||
{
|
||||
"name": "湛河区",
|
||||
"shortCode": "zhanhe"
|
||||
},
|
||||
{
|
||||
"name": "汝州市",
|
||||
"shortCode": "ruzhou"
|
||||
},
|
||||
{
|
||||
"name": "舞钢市",
|
||||
"shortCode": "wugang"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "安阳市",
|
||||
"shortCode": "Anyang",
|
||||
"regions": [
|
||||
{
|
||||
"name": "文峰区",
|
||||
"shortCode": "wenfeng"
|
||||
},
|
||||
{
|
||||
"name": "北关区",
|
||||
"shortCode": "beiguan"
|
||||
},
|
||||
{
|
||||
"name": "殷都区",
|
||||
"shortCode": "yindu"
|
||||
},
|
||||
{
|
||||
"name": "龙安区",
|
||||
"shortCode": "longan"
|
||||
},
|
||||
{
|
||||
"name": "林州市",
|
||||
"shortCode": "linzhou"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "鹤壁市",
|
||||
"shortCode": "Hebi",
|
||||
"regions": [
|
||||
{
|
||||
"name": "鹤山区",
|
||||
"shortCode": "heshan"
|
||||
},
|
||||
{
|
||||
"name": "山城区",
|
||||
"shortCode": "shancheng"
|
||||
},
|
||||
{
|
||||
"name": "淇滨区",
|
||||
"shortCode": "qibin"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "新乡市",
|
||||
"shortCode": "Xinxiang",
|
||||
"regions": [
|
||||
{
|
||||
"name": "红旗区",
|
||||
"shortCode": "hongqi"
|
||||
},
|
||||
{
|
||||
"name": "卫滨区",
|
||||
"shortCode": "weibin"
|
||||
},
|
||||
{
|
||||
"name": "凤泉区",
|
||||
"shortCode": "fengquan"
|
||||
},
|
||||
{
|
||||
"name": "牧野区",
|
||||
"shortCode": "muye"
|
||||
},
|
||||
{
|
||||
"name": "卫辉市",
|
||||
"shortCode": "weihui"
|
||||
},
|
||||
{
|
||||
"name": "辉县市",
|
||||
"shortCode": "huixian"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "焦作市",
|
||||
"shortCode": "Jiaozuo",
|
||||
"regions": [
|
||||
{
|
||||
"name": "解放区",
|
||||
"shortCode": "jiefang"
|
||||
},
|
||||
{
|
||||
"name": "中站区",
|
||||
"shortCode": "zhongzhan"
|
||||
},
|
||||
{
|
||||
"name": "马村区",
|
||||
"shortCode": "macun"
|
||||
},
|
||||
{
|
||||
"name": "山阳区",
|
||||
"shortCode": "shanyang"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "濮阳市",
|
||||
"shortCode": "Puyang",
|
||||
"regions": [
|
||||
{
|
||||
"name": "华龙区",
|
||||
"shortCode": "hualong"
|
||||
},
|
||||
{
|
||||
"name": "清丰县",
|
||||
"shortCode": "qingfeng"
|
||||
},
|
||||
{
|
||||
"name": "南乐县",
|
||||
"shortCode": "nanle"
|
||||
},
|
||||
{
|
||||
"name": "范县",
|
||||
"shortCode": "fan"
|
||||
},
|
||||
{
|
||||
"name": "台前县",
|
||||
"shortCode": "taiqian"
|
||||
},
|
||||
{
|
||||
"name": "濮阳县",
|
||||
"shortCode": "puyang"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "许昌市",
|
||||
"shortCode": "Xuchang",
|
||||
"regions": [
|
||||
{
|
||||
"name": "魏都区",
|
||||
"shortCode": "weidu"
|
||||
},
|
||||
{
|
||||
"name": "建安区",
|
||||
"shortCode": "jianan"
|
||||
},
|
||||
{
|
||||
"name": "鄢陵县",
|
||||
"shortCode": "yanling"
|
||||
},
|
||||
{
|
||||
"name": "襄城县",
|
||||
"shortCode": "xiangcheng"
|
||||
},
|
||||
{
|
||||
"name": "禹州市",
|
||||
"shortCode": "yuzhoushi"
|
||||
},
|
||||
{
|
||||
"name": "长葛市",
|
||||
"shortCode": "changgeshi"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "漯河市",
|
||||
"shortCode": "Luohe",
|
||||
"regions": [
|
||||
{
|
||||
"name": "源汇区",
|
||||
"shortCode": "yuanhu"
|
||||
},
|
||||
{
|
||||
"name": "郾城区",
|
||||
"shortCode": "yancheng"
|
||||
},
|
||||
{
|
||||
"name": "召陵区",
|
||||
"shortCode": "zhaoqing"
|
||||
},
|
||||
{
|
||||
"name": "舞阳县",
|
||||
"shortCode": "wuyang"
|
||||
},
|
||||
{
|
||||
"name": "临颍县",
|
||||
"shortCode": "linyong"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "三门峡市",
|
||||
"shortCode": "Sanmenxia",
|
||||
"regions": [
|
||||
{
|
||||
"name": "湖滨区",
|
||||
"shortCode": "hubin"
|
||||
},
|
||||
{
|
||||
"name": "陕州区",
|
||||
"shortCode": "shanzhou"
|
||||
},
|
||||
{
|
||||
"name": "渑池县",
|
||||
"shortCode": "mianchi"
|
||||
},
|
||||
{
|
||||
"name": "卢氏县",
|
||||
"shortCode": "lushi"
|
||||
},
|
||||
{
|
||||
"name": "义马市",
|
||||
"shortCode": "yima"
|
||||
},
|
||||
{
|
||||
"name": "灵宝市",
|
||||
"shortCode": "lingbao"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "南阳市",
|
||||
"shortCode": "Nanyang",
|
||||
"regions": [
|
||||
{
|
||||
"name": "宛城区",
|
||||
"shortCode": "wancheng"
|
||||
},
|
||||
{
|
||||
"name": "卧龙区",
|
||||
"shortCode": "wolong"
|
||||
},
|
||||
{
|
||||
"name": "南召县",
|
||||
"shortCode": "nanzhao"
|
||||
},
|
||||
{
|
||||
"name": "方城县",
|
||||
"shortCode": "fangcheng"
|
||||
},
|
||||
{
|
||||
"name": "西峡县",
|
||||
"shortCode": "xixia"
|
||||
},
|
||||
{
|
||||
"name": "镇平县",
|
||||
"shortCode": "zhenping"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "商丘市",
|
||||
"shortCode": "Shangqiu",
|
||||
"regions": [
|
||||
{
|
||||
"name": "梁园区",
|
||||
"shortCode": "liangyu"
|
||||
},
|
||||
{
|
||||
"name": "睢阳区",
|
||||
"shortCode": "suiyang"
|
||||
},
|
||||
{
|
||||
"name": "民权县",
|
||||
"shortCode": "minquan"
|
||||
},
|
||||
{
|
||||
"name": "睢县",
|
||||
"shortCode": "sui"
|
||||
},
|
||||
{
|
||||
"name": "宁陵县",
|
||||
"shortCode": "ningling"
|
||||
},
|
||||
{
|
||||
"name": "柘城县",
|
||||
"shortCode": "zhecheng"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "信阳市",
|
||||
"shortCode": "Xinyang",
|
||||
"regions": [
|
||||
{
|
||||
"name": "浉河区",
|
||||
"shortCode": "buhe"
|
||||
},
|
||||
{
|
||||
"name": "平桥区",
|
||||
"shortCode": "pingqiao"
|
||||
},
|
||||
{
|
||||
"name": "罗山县",
|
||||
"shortCode": "luoshan"
|
||||
},
|
||||
{
|
||||
"name": "光山县",
|
||||
"shortCode": "guangshan"
|
||||
},
|
||||
{
|
||||
"name": "新县",
|
||||
"shortCode": "xin"
|
||||
},
|
||||
{
|
||||
"name": "商城县",
|
||||
"shortCode": "shangcheng"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "周口市",
|
||||
"shortCode": "zhoukou",
|
||||
"regions": [
|
||||
{
|
||||
"name": "川汇区",
|
||||
"shortCode": "chuhui"
|
||||
},
|
||||
{
|
||||
"name": "扶沟县",
|
||||
"shortCode": "fugou"
|
||||
},
|
||||
{
|
||||
"name": "西华县",
|
||||
"shortCode": "xihua"
|
||||
},
|
||||
{
|
||||
"name": "商水县",
|
||||
"shortCode": "shangshui"
|
||||
},
|
||||
{
|
||||
"name": "沈丘县",
|
||||
"shortCode": "shenqiu"
|
||||
},
|
||||
{
|
||||
"name": "郸城县",
|
||||
"shortCode": "dancheng"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "驻马店市",
|
||||
"shortCode": "zhumadian",
|
||||
"regions": [
|
||||
{
|
||||
"name": "驿城区",
|
||||
"shortCode": "yecheng"
|
||||
},
|
||||
{
|
||||
"name": "西平县",
|
||||
"shortCode": "xiping"
|
||||
},
|
||||
{
|
||||
"name": "上蔡县",
|
||||
"shortCode": "shangcai"
|
||||
},
|
||||
{
|
||||
"name": "平舆县",
|
||||
"shortCode": "pingyu"
|
||||
},
|
||||
{
|
||||
"name": "正阳县",
|
||||
"shortCode": "zhengyang"
|
||||
},
|
||||
{
|
||||
"name": "确山县",
|
||||
"shortCode": "qieshan"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "济源市",
|
||||
"shortCode": "Jiyuan",
|
||||
"regions": [
|
||||
{
|
||||
"name": "沁园街道",
|
||||
"shortCode": "qinyuan"
|
||||
},
|
||||
{
|
||||
"name": "北海街道",
|
||||
"shortCode": "beihai"
|
||||
},
|
||||
{
|
||||
"name": "玉泉街道",
|
||||
"shortCode": "yuquan"
|
||||
},
|
||||
{
|
||||
"name": "天坛街道",
|
||||
"shortCode": "tiantan"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
149
components/auth/index.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import Constants from 'expo-constants';
|
||||
import * as Device from 'expo-device';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Platform, Text, View } from 'react-native';
|
||||
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
shouldPlaySound: false,
|
||||
shouldSetBadge: false,
|
||||
shouldShowBanner: true,
|
||||
shouldShowList: true,
|
||||
}),
|
||||
});
|
||||
|
||||
export default function AuthNotifications({ setNotificationsEnabled, notificationsEnabled }: { setNotificationsEnabled: (value: boolean) => void, notificationsEnabled: boolean }) {
|
||||
const [expoPushToken, setExpoPushToken] = useState('');
|
||||
const [channels, setChannels] = useState<Notifications.NotificationChannel[]>([]);
|
||||
const [notification, setNotification] = useState<Notifications.Notification | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('notificationsEnabled', notificationsEnabled);
|
||||
registerForPushNotificationsAsync().then(token => {
|
||||
console.log('token', token);
|
||||
token && setExpoPushToken(token)
|
||||
});
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
Notifications.getNotificationChannelsAsync().then(value => setChannels(value ?? []));
|
||||
}
|
||||
const notificationListener = Notifications.addNotificationReceivedListener(notification => {
|
||||
setNotification(notification);
|
||||
});
|
||||
|
||||
const responseListener = Notifications.addNotificationResponseReceivedListener(response => {
|
||||
console.log(response);
|
||||
});
|
||||
|
||||
return () => {
|
||||
notificationListener.remove();
|
||||
responseListener.remove();
|
||||
};
|
||||
}, [notificationsEnabled]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-around',
|
||||
display: "none"
|
||||
}}>
|
||||
<Text>Your expo push token: {expoPushToken}</Text>
|
||||
<Text>{`Channels: ${JSON.stringify(
|
||||
channels.map(c => c.id),
|
||||
null,
|
||||
2
|
||||
)}`}</Text>
|
||||
<View style={{ alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text>Title: {notification && notification.request.content.title} </Text>
|
||||
<Text>Body: {notification && notification.request.content.body}</Text>
|
||||
<Text>Data: {notification && JSON.stringify(notification.request.content.data)}</Text>
|
||||
</View>
|
||||
<Button
|
||||
title="Press to schedule a notification"
|
||||
onPress={async () => {
|
||||
await schedulePushNotification();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
async function schedulePushNotification() {
|
||||
await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: "You've got mail! 📬",
|
||||
body: 'Here is the notification body',
|
||||
data: { data: 'goes here', test: { test1: 'more data' } },
|
||||
},
|
||||
trigger: {
|
||||
type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
|
||||
seconds: 2,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function registerForPushNotificationsAsync() {
|
||||
let token;
|
||||
|
||||
// 1. Android 特定配置
|
||||
if (Platform.OS === 'android') {
|
||||
await Notifications.setNotificationChannelAsync('myNotificationChannel', {
|
||||
name: 'A channel is needed for the permissions prompt to appear',
|
||||
importance: Notifications.AndroidImportance.MAX, // 最高优先级
|
||||
vibrationPattern: [0, 250, 250, 250], // 振动模式
|
||||
lightColor: '#FF231F7C', // 通知灯颜色
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 检查是否在真实设备上运行
|
||||
if (Device.isDevice) {
|
||||
// 3. 检查通知权限状态
|
||||
const { status: existingStatus } = await Notifications.getPermissionsAsync();
|
||||
let finalStatus = existingStatus;
|
||||
|
||||
// 4. 如果尚未授予权限,则请求权限
|
||||
if (existingStatus !== 'granted') {
|
||||
const { status } = await Notifications.requestPermissionsAsync();
|
||||
finalStatus = status;
|
||||
}
|
||||
|
||||
// 5. 如果权限被拒绝,显示警告并返回
|
||||
if (finalStatus !== 'granted') {
|
||||
alert('Failed to get push token for push notification!');
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. 获取推送令牌
|
||||
try {
|
||||
// 获取项目ID(用于Expo推送通知服务)
|
||||
const projectId =
|
||||
Constants?.expoConfig?.extra?.eas?.projectId ??
|
||||
Constants?.easConfig?.projectId;
|
||||
|
||||
if (!projectId) {
|
||||
throw new Error('Project ID not found');
|
||||
}
|
||||
|
||||
// 获取Expo推送令牌
|
||||
token = (
|
||||
await Notifications.getExpoPushTokenAsync({
|
||||
projectId,
|
||||
})
|
||||
).data;
|
||||
console.log(token); // 打印令牌,实际应用中应该发送到你的服务器
|
||||
|
||||
} catch (e) {
|
||||
token = `${e}`; // 错误处理
|
||||
}
|
||||
} else {
|
||||
// 7. 如果是在模拟器上运行,显示警告
|
||||
alert('Must use physical device for Push Notifications');
|
||||
}
|
||||
|
||||
return token; // 返回获取到的令牌
|
||||
}
|
||||
214
components/cascader.tsx
Normal file
@ -0,0 +1,214 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ScrollView, StyleProp, StyleSheet, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native';
|
||||
import { ThemedText } from './ThemedText';
|
||||
|
||||
export type CascaderItem = {
|
||||
name: string;
|
||||
[key: string]: any; // 允许其他自定义属性
|
||||
children?: CascaderItem[];
|
||||
};
|
||||
|
||||
type CascaderProps = {
|
||||
data: CascaderItem[]; // 级联数据
|
||||
value?: CascaderItem[]; // 选中的值
|
||||
onChange?: (value: CascaderItem[]) => void; // 选中项变化时的回调
|
||||
displayRender?: (selectedItems: CascaderItem[]) => React.ReactNode; // 自定义显示内容
|
||||
style?: StyleProp<ViewStyle>; // 容器样式
|
||||
itemStyle?: StyleProp<ViewStyle>; // 选项样式
|
||||
activeItemStyle?: StyleProp<ViewStyle>; // 选中项样式
|
||||
textStyle?: StyleProp<TextStyle>; // 文字样式
|
||||
activeTextStyle?: StyleProp<TextStyle>; // 选中文字样式
|
||||
columnWidth?: number; // 列宽
|
||||
showDivider?: boolean; // 是否显示分割线
|
||||
dividerColor?: string; // 分割线颜色
|
||||
showArrow?: boolean; // 是否显示箭头
|
||||
};
|
||||
|
||||
const CascaderComponent: React.FC<CascaderProps> = ({
|
||||
data,
|
||||
value = [],
|
||||
onChange,
|
||||
displayRender,
|
||||
style,
|
||||
activeItemStyle,
|
||||
textStyle,
|
||||
activeTextStyle,
|
||||
columnWidth = 120,
|
||||
showDivider = true,
|
||||
dividerColor = '#e0e0e0',
|
||||
showArrow = false,
|
||||
}) => {
|
||||
const [selectedItems, setSelectedItems] = useState<CascaderItem[]>(value);
|
||||
const [allLevelsData, setAllLevelsData] = useState<CascaderItem[][]>([]);
|
||||
|
||||
// 初始化数据
|
||||
useEffect(() => {
|
||||
setAllLevelsData([data]);
|
||||
}, [data]);
|
||||
|
||||
// 处理选择
|
||||
const handleSelect = (item: CascaderItem, level: number) => {
|
||||
const newSelectedItems = [...selectedItems.slice(0, level), item];
|
||||
setSelectedItems(newSelectedItems);
|
||||
|
||||
// 如果有子项,添加下一级数据
|
||||
if (item.children?.length) {
|
||||
setAllLevelsData(prev => {
|
||||
const newLevels = [...prev.slice(0, level + 1)];
|
||||
// 确保 children 存在且是数组
|
||||
if (item.children && Array.isArray(item.children)) {
|
||||
newLevels.push(item.children);
|
||||
}
|
||||
return newLevels;
|
||||
});
|
||||
} else {
|
||||
setAllLevelsData(prev => prev.slice(0, level + 1));
|
||||
}
|
||||
|
||||
// 触发onChange回调
|
||||
onChange?.(newSelectedItems);
|
||||
};
|
||||
|
||||
// 渲染某一级选项
|
||||
const renderLevel = (items: CascaderItem[], level: number) => {
|
||||
return (
|
||||
<View style={[styles.levelContainer, { width: columnWidth }]}>
|
||||
{items.map((item, index) => {
|
||||
const isActive = selectedItems[level]?.name === item.name;
|
||||
return (
|
||||
<View key={`${level}-${index}`} style={[
|
||||
styles.item,
|
||||
isActive && [styles.activeItem, activeItemStyle]
|
||||
]}>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.itemContent}
|
||||
onPress={() => handleSelect(item, level)}
|
||||
>
|
||||
<ThemedText
|
||||
style={[
|
||||
styles.text,
|
||||
textStyle,
|
||||
isActive && [styles.activeText, activeTextStyle]
|
||||
]}
|
||||
>
|
||||
{item.name}
|
||||
</ThemedText>
|
||||
{showArrow && item.children?.length ? (
|
||||
<ThemedText style={styles.arrow}>›</ThemedText>
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染所有级联列
|
||||
const renderColumns = () => {
|
||||
return allLevelsData.map((items, level) => (
|
||||
<View
|
||||
key={`column-${level}`}
|
||||
style={[
|
||||
styles.column,
|
||||
{ width: columnWidth },
|
||||
showDivider && level < allLevelsData.length - 1 && [
|
||||
styles.columnWithDivider,
|
||||
{ borderRightColor: dividerColor }
|
||||
]
|
||||
]}
|
||||
>
|
||||
{renderLevel(items, level)}
|
||||
</View>
|
||||
));
|
||||
};
|
||||
|
||||
// 自定义显示内容
|
||||
const renderDisplay = () => {
|
||||
if (displayRender) {
|
||||
return displayRender(selectedItems);
|
||||
}
|
||||
return selectedItems.map(item => item.name).join(' / ');
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, style]}>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
{renderColumns()}
|
||||
</ScrollView>
|
||||
{displayRender && (
|
||||
<View style={styles.displayContainer}>
|
||||
{renderDisplay()}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
column: {
|
||||
height: '100%',
|
||||
},
|
||||
columnWithDivider: {
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
levelContainer: {
|
||||
height: '100%',
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
minWidth: '100%', // 确保最小宽度填满容器
|
||||
overflow: 'hidden', // 隐藏超出部分
|
||||
},
|
||||
text: {
|
||||
fontSize: 15,
|
||||
color: '#333',
|
||||
flexShrink: 0, // 禁止收缩
|
||||
paddingRight: 4,
|
||||
},
|
||||
itemContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingRight: 16, // 确保有足够的右边距
|
||||
},
|
||||
activeItem: {
|
||||
backgroundColor: '#F6F6F6',
|
||||
},
|
||||
activeText: {
|
||||
color: '#AC7E35',
|
||||
fontWeight: '500',
|
||||
},
|
||||
arrow: {
|
||||
fontSize: 18,
|
||||
color: '#999',
|
||||
marginLeft: 8,
|
||||
},
|
||||
displayContainer: {
|
||||
padding: 12,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#f0f0f0',
|
||||
},
|
||||
});
|
||||
|
||||
export default CascaderComponent;
|
||||
@ -1,12 +1,11 @@
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import { defaultExifData, ExifData, ImagesuploaderProps } from '@/types/upload';
|
||||
import { defaultExifData, ExifData, ImagesuploaderProps, UploadUrlResponse } from '@/types/upload';
|
||||
import * as ImageManipulator from 'expo-image-manipulator';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import * as Location from 'expo-location';
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Platform, TouchableOpacity, View } from 'react-native';
|
||||
import { UploadUrlResponse } from './file-uploader';
|
||||
import UploadPreview from './preview';
|
||||
|
||||
// 在文件顶部添加这些类型
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import { ConfirmUpload, defaultExifData, ExifData, ImagesPickerProps, UploadResult } from '@/types/upload';
|
||||
import { ConfirmUpload, defaultExifData, ExifData, FileStatus, ImagesPickerProps, UploadResult, UploadUrlResponse } from '@/types/upload';
|
||||
import * as ImageManipulator from 'expo-image-manipulator';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import * as Location from 'expo-location';
|
||||
@ -7,7 +7,6 @@ import * as MediaLibrary from 'expo-media-library';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Platform, TouchableOpacity, View } from 'react-native';
|
||||
import * as Progress from 'react-native-progress';
|
||||
import { FileStatus, UploadUrlResponse } from './file-uploader';
|
||||
|
||||
export const ImagesPicker: React.FC<ImagesPickerProps> = ({
|
||||
children,
|
||||
|
||||
81
components/layout/ask.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import NavbarSvg from "@/assets/icons/svg/navbar.svg";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router } from "expo-router";
|
||||
import React from 'react';
|
||||
import { Platform, TouchableOpacity, View } from 'react-native';
|
||||
import { Circle, Ellipse, G, Mask, Path, Rect, Svg } from 'react-native-svg';
|
||||
|
||||
const AskNavbar = () => {
|
||||
return (
|
||||
<View className="absolute bottom-0 left-0 right-0 bg-white" style={{
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: -4 }, // Negative height for bottom shadow
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 10, // For Android
|
||||
}}>
|
||||
<NavbarSvg className="w-full h-full" />
|
||||
<View className="absolute bottom-0 top-0 left-0 right-0 flex flex-row justify-between items-center px-[2rem]">
|
||||
<TouchableOpacity onPress={() => router.push('/memo-list')} >
|
||||
<Ionicons name="chatbubbles-outline" size={24} color="#4C320C" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
router.push({
|
||||
pathname: '/ask',
|
||||
params: { newSession: "true" }
|
||||
});
|
||||
}}
|
||||
className={`${Platform.OS === 'web' ? '-mt-[4rem]' : '-mt-[5rem] ml-[0.8rem]'}`}
|
||||
>
|
||||
<View style={{
|
||||
shadowColor: '#FFB645',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 8,
|
||||
elevation: 8,
|
||||
borderRadius: 50,
|
||||
backgroundColor: 'transparent',
|
||||
zIndex: 10,
|
||||
width: 85,
|
||||
height: 85
|
||||
}}>
|
||||
<Svg width="100%" height="100%" viewBox="0 0 85 85" fill="none">
|
||||
<Mask id="mask0_1464_1669" maskUnits="userSpaceOnUse" x="0" y="0" width="85" height="85">
|
||||
<Circle cx="42.5" cy="42.5" r="42.5" fill="#FFC959" />
|
||||
</Mask>
|
||||
<G mask="url(#mask0_1464_1669)">
|
||||
<Circle cx="42.5" cy="42.5" r="42.5" fill="#FFD38D" />
|
||||
<Path d="M20.2018 14.6411C21.8694 12.5551 26.4765 16.939 28.5716 19.3917L20.8604 20.0277C19.3178 19.3509 18.5342 16.7271 20.2018 14.6411Z" fill="#FFDBA3" />
|
||||
<Path d="M21.3021 15.4913C22.503 13.6451 25.3001 17.1089 26.5485 19.0716L22.7323 19.2755C21.7552 18.7834 20.1012 17.3376 21.3021 15.4913Z" fill="#AC7E35" />
|
||||
<Path d="M65.1253 14.6411C63.4577 12.5551 58.8506 16.939 56.7556 19.3917L64.4667 20.0277C66.0093 19.3509 66.7929 16.7271 65.1253 14.6411Z" fill="#FFDBA3" />
|
||||
<Path d="M64.0255 15.4913C62.8246 13.6451 60.0276 17.1089 58.7792 19.0716L62.5953 19.2755C63.5724 18.7834 65.2264 17.3376 64.0255 15.4913Z" fill="#AC7E35" />
|
||||
<Path d="M-15.3352 49.1734C10.3693 4.65192 74.6306 4.65187 100.335 49.1734L117.868 79.5409C143.572 124.062 111.442 179.714 60.0327 179.714H24.9673C-26.4417 179.714 -58.5724 124.062 -32.8679 79.5409L-15.3352 49.1734Z" fill="#FFD18A" />
|
||||
<Rect x="38.5571" y="46.2812" width="2.62922" height="3.68091" rx="1.31461" transform="rotate(-180 38.5571 46.2812)" fill="#4C320C" />
|
||||
<Rect x="48.0205" y="46.2812" width="2.62922" height="3.68091" rx="1.31461" transform="rotate(-180 48.0205 46.2812)" fill="#4C320C" />
|
||||
<Path d="M4.8084 73.2062C22.9876 46.7781 62.0132 46.7782 80.1924 73.2062L100.897 103.306C121.776 133.659 100.046 174.982 63.2051 174.982H21.7957C-15.0453 174.982 -36.7756 133.659 -15.8963 103.306L4.8084 73.2062Z" fill="#FFF8DE" />
|
||||
<G>
|
||||
<Ellipse cx="79.047" cy="68.6298" rx="43.1193" ry="30.7619" fill="#FFF8DE" />
|
||||
</G>
|
||||
<G>
|
||||
<Ellipse cx="5.69032" cy="68.6298" rx="42.8563" ry="30.7619" fill="#FFF8DE" />
|
||||
</G>
|
||||
<Ellipse cx="42.2365" cy="53.3803" rx="3.15507" ry="2.3663" transform="rotate(180 42.2365 53.3803)" fill="#FFB8B9" />
|
||||
<Path d="M41.7813 56.0095C41.9837 55.6589 42.4897 55.6589 42.6921 56.0095L43.1475 56.7982C43.3499 57.1488 43.0969 57.587 42.6921 57.587H41.7813C41.3765 57.587 41.1235 57.1488 41.3259 56.7982L41.7813 56.0095Z" fill="#4C320C" />
|
||||
</G>
|
||||
</Svg>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => router.push('/owner')}>
|
||||
<View>
|
||||
<Ionicons name="person-outline" size={24} color="#4C320C" />
|
||||
{/* <View className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full" /> */}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AskNavbar;
|
||||
46
components/owner/album.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import SettingSvg from '@/assets/icons/svg/setting.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StyleProp, StyleSheet, TouchableOpacity, View, ViewStyle } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
interface CategoryProps {
|
||||
setModalVisible: (visible: boolean) => void;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const AlbumComponent = ({ setModalVisible, style }: CategoryProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<View style={[styles.container, style]}>
|
||||
<TouchableOpacity style={{ flex: 3 }}>
|
||||
<ThemedText style={styles.text}>{t('generalSetting.album', { ns: 'personal' })}</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<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 }]}>
|
||||
<SettingSvg />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 16
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
color: '#AC7E35',
|
||||
borderColor: '#FFD18A',
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
padding: 4,
|
||||
textAlign: "center",
|
||||
}
|
||||
});
|
||||
|
||||
export default AlbumComponent;
|
||||
82
components/owner/category.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import { Image, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
|
||||
interface CategoryProps {
|
||||
title: string;
|
||||
data: { title: string, number: string | number }[];
|
||||
bgSvg: string | null;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const CategoryComponent = ({ title, data, bgSvg, style }: CategoryProps) => {
|
||||
return (
|
||||
<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"
|
||||
/>
|
||||
<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>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 32,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
},
|
||||
backgroundContainer: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)', // 0% 不透明度的黑色
|
||||
},
|
||||
content: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
justifyContent: 'space-between',
|
||||
flex: 1
|
||||
},
|
||||
title: {
|
||||
width: '100%',
|
||||
textAlign: "center",
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
textShadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textShadowOffset: { width: 1, height: 1 },
|
||||
textShadowRadius: 2,
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
itemTitle: {
|
||||
color: 'white',
|
||||
fontSize: 10,
|
||||
fontWeight: '700',
|
||||
},
|
||||
itemNumber: {
|
||||
color: 'white',
|
||||
fontSize: 10,
|
||||
}
|
||||
});
|
||||
|
||||
export default CategoryComponent;
|
||||
171
components/owner/classify.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import { TargetItem } from '@/types/user';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import Cascader, { CascaderItem } from '../cascader';
|
||||
|
||||
interface ClassifyModalProps {
|
||||
modalVisible: boolean;
|
||||
setModalVisible: (visible: boolean) => void;
|
||||
podiumPosition: { x: number, y: number, width: number, height: number };
|
||||
handleChange: (selectedItems: CascaderItem[]) => void;
|
||||
data: TargetItem[];
|
||||
}
|
||||
const ClassifyModal = (props: ClassifyModalProps) => {
|
||||
const { modalVisible, setModalVisible, podiumPosition, handleChange, data } = props;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal
|
||||
isVisible={modalVisible}
|
||||
onBackdropPress={() => setModalVisible(false)}
|
||||
swipeDirection="right" // 支持向右滑动关闭
|
||||
propagateSwipe={true}
|
||||
animationIn="slideInRight" // 入场动画
|
||||
animationOut="slideOutRight" // 出场动画
|
||||
backdropOpacity={0.5}
|
||||
onSwipeComplete={() => setModalVisible(false)}
|
||||
style={{ margin: 0, justifyContent: 'flex-start', marginTop: podiumPosition.height + podiumPosition.y }}
|
||||
>
|
||||
<View style={styles.modalView}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={{ opacity: 0 }}>Settings</Text>
|
||||
<Text style={styles.modalTitle}>{t('generalSetting.classify', { ns: 'personal' })}</Text>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||
<Text style={styles.closeButton}>×</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
||||
<Cascader
|
||||
data={data}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</ScrollView>
|
||||
<TouchableOpacity
|
||||
style={styles.confirmButton}
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
}}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text className="text-[#fff] font-bold text-lg">
|
||||
{t('generalSetting.confirm', { ns: 'personal' })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalView: {
|
||||
width: '100%',
|
||||
height: '60%',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 24,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E5E5E5',
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#4C320C',
|
||||
},
|
||||
closeButton: {
|
||||
fontSize: 28,
|
||||
color: '#4C320C',
|
||||
padding: 10,
|
||||
},
|
||||
modalContent: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 16,
|
||||
color: '#4C320C',
|
||||
},
|
||||
premium: {
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
backgroundColor: '#FAF9F6',
|
||||
borderRadius: 24,
|
||||
paddingVertical: 8
|
||||
},
|
||||
item: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#4C320C',
|
||||
},
|
||||
upgradeButton: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
upgradeButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: "600"
|
||||
},
|
||||
switchContainer: {
|
||||
width: 50,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
switchOn: {
|
||||
backgroundColor: '#E2793F',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
switchOff: {
|
||||
backgroundColor: '#E5E5E5',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
switchCircle: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
},
|
||||
switchCircleOn: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
switchCircleOff: {
|
||||
backgroundColor: '#A5A5A5',
|
||||
},
|
||||
confirmButton: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default ClassifyModal;
|
||||
50
components/owner/count.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
|
||||
interface CountProps {
|
||||
data: { title: string; number: string | number }[];
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
const CountComponent = (props: CountProps) => {
|
||||
return (
|
||||
<View style={[styles.container, props.style]}>
|
||||
{props.data?.map((item, index) => (
|
||||
<View style={styles.item} key={index}>
|
||||
<ThemedText style={styles.title}>{item.title}</ThemedText>
|
||||
<ThemedText style={styles.number}>{item.number}</ThemedText>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: "#FFB645",
|
||||
padding: 16,
|
||||
borderRadius: 20,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
item: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
paddingVertical: 8
|
||||
},
|
||||
title: {
|
||||
color: "#4C320C",
|
||||
fontWeight: "700",
|
||||
fontSize: 14,
|
||||
},
|
||||
number: {
|
||||
color: "#fff",
|
||||
fontWeight: "700",
|
||||
fontSize: 32,
|
||||
textAlign: 'right',
|
||||
flex: 1,
|
||||
paddingTop: 8
|
||||
}
|
||||
})
|
||||
export default CountComponent;
|
||||
68
components/owner/createCount.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
|
||||
interface CreateCountProps {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
number: number;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
const CreateCountComponent = (props: CreateCountProps) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<ThemedText style={styles.title} className="!text-textSecondary">{props.title}</ThemedText>
|
||||
<View className="mt-1">
|
||||
{props.icon}
|
||||
</View>
|
||||
</View>
|
||||
<ThemedText style={styles.number} className="!text-textSecondary">{props.number}</ThemedText>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
gap: 8,
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 3.84,
|
||||
elevation: 5,
|
||||
},
|
||||
header: {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 8,
|
||||
// 靠左展示
|
||||
textAlign: 'left',
|
||||
},
|
||||
title: {
|
||||
width: "53%",
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
// 允许换行
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
number: {
|
||||
fontSize: 32,
|
||||
fontWeight: "700",
|
||||
paddingTop: 8,
|
||||
width: "100%",
|
||||
// 靠右展示
|
||||
textAlign: "right",
|
||||
},
|
||||
})
|
||||
export default CreateCountComponent;
|
||||
190
components/owner/location.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
import locationData from '@/assets/json/location.json';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import Cascader, { CascaderItem } from '../cascader';
|
||||
import { convertRegions } from '../utils/cascaderData';
|
||||
|
||||
interface LocationModalProps {
|
||||
modalVisible: boolean;
|
||||
setModalVisible: (visible: boolean) => void;
|
||||
podiumPosition: { x: number, y: number, width: number, height: number };
|
||||
handleChange: (selectedItems: CascaderItem[]) => void;
|
||||
}
|
||||
|
||||
const LocationModal = React.memo((props: LocationModalProps) => {
|
||||
const { modalVisible, setModalVisible, podiumPosition, handleChange } = props;
|
||||
const transformed = convertRegions(locationData, {
|
||||
nameKey: 'name', // 源数据中表示"名称"的字段
|
||||
valueKey: 'name', // 源数据中作为 value 的字段
|
||||
regionsKey: 'regions', // 源数据中表示"子级区域"的字段
|
||||
childrenKey: 'children' // 输出结构中表示"子级区域"的字段
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isVisible={modalVisible}
|
||||
onBackdropPress={() => setModalVisible(false)}
|
||||
swipeDirection="right" // 支持向右滑动关闭
|
||||
propagateSwipe={true}
|
||||
animationIn="slideInRight" // 入场动画
|
||||
animationOut="slideOutRight" // 出场动画
|
||||
backdropOpacity={0.5}
|
||||
onSwipeComplete={() => setModalVisible(false)}
|
||||
style={{ margin: 0, justifyContent: 'flex-start', marginTop: podiumPosition.height + podiumPosition.y }}
|
||||
>
|
||||
<View style={styles.modalView}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={{ opacity: 0 }}>Settings</Text>
|
||||
<Text style={styles.modalTitle}>{t('generalSetting.location', { ns: 'personal' })}</Text>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||
<Text style={styles.closeButton}>×</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
||||
<Cascader
|
||||
data={transformed}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</ScrollView>
|
||||
<TouchableOpacity
|
||||
style={styles.confirmButton}
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
}}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text className="text-[#fff] font-bold text-lg">
|
||||
{t('generalSetting.confirm', { ns: 'personal' })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}, areEqual);
|
||||
|
||||
function areEqual(prevProps: LocationModalProps, nextProps: LocationModalProps) {
|
||||
// 只有当这些 props 变化时才重新渲染
|
||||
return (
|
||||
prevProps.modalVisible === nextProps.modalVisible &&
|
||||
prevProps.podiumPosition.x === nextProps.podiumPosition.x &&
|
||||
prevProps.podiumPosition.y === nextProps.podiumPosition.y &&
|
||||
prevProps.podiumPosition.width === nextProps.podiumPosition.width &&
|
||||
prevProps.podiumPosition.height === nextProps.podiumPosition.height
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalView: {
|
||||
width: '100%',
|
||||
height: '60%',
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 24,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E5E5E5',
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#4C320C',
|
||||
},
|
||||
closeButton: {
|
||||
fontSize: 28,
|
||||
color: '#4C320C',
|
||||
padding: 10,
|
||||
},
|
||||
modalContent: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 16,
|
||||
color: '#4C320C',
|
||||
},
|
||||
premium: {
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
backgroundColor: '#FAF9F6',
|
||||
borderRadius: 24,
|
||||
paddingVertical: 8
|
||||
},
|
||||
item: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#4C320C',
|
||||
},
|
||||
upgradeButton: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
upgradeButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: "600"
|
||||
},
|
||||
switchContainer: {
|
||||
width: 50,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
switchOn: {
|
||||
backgroundColor: '#E2793F',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
switchOff: {
|
||||
backgroundColor: '#E5E5E5',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
switchCircle: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
},
|
||||
switchCircleOn: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
switchCircleOff: {
|
||||
backgroundColor: '#A5A5A5',
|
||||
},
|
||||
confirmButton: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
marginHorizontal: 16,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default LocationModal;
|
||||
148
components/owner/locationPicker.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import locationData from '@/assets/json/location.json';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import { ThemedText } from '../ThemedText';
|
||||
|
||||
type Region = {
|
||||
name: string;
|
||||
regions?: Region[];
|
||||
};
|
||||
|
||||
const GlobalRegionPicker = () => {
|
||||
const [selectedRegions, setSelectedRegions] = useState<Region[]>([]);
|
||||
const [allLevelsData, setAllLevelsData] = useState<Region[][]>([]);
|
||||
|
||||
// 初始化第一级数据
|
||||
useEffect(() => {
|
||||
setAllLevelsData([locationData as Region[]]);
|
||||
}, []);
|
||||
|
||||
// 处理地区选择
|
||||
const handleSelectRegion = (region: Region, level: number) => {
|
||||
// 更新选中的地区
|
||||
const newSelectedRegions = [...selectedRegions.slice(0, level), region];
|
||||
setSelectedRegions(newSelectedRegions);
|
||||
|
||||
// 如果有子区域,添加下一级数据
|
||||
if (region.regions && region.regions.length > 0) {
|
||||
// 创建新的层级数据数组
|
||||
const newAllLevelsData = [...allLevelsData.slice(0, level + 1)];
|
||||
// 添加新的子区域数据
|
||||
newAllLevelsData.push(region.regions);
|
||||
setAllLevelsData(newAllLevelsData);
|
||||
} else {
|
||||
// 如果没有子区域,截断到当前级别
|
||||
setAllLevelsData(allLevelsData.slice(0, level + 1));
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染某一级的地区列表
|
||||
const renderLevel = (regions: Region[], level: number) => {
|
||||
return (
|
||||
<View style={styles.levelContainer}>
|
||||
{regions.map((region, index) => (
|
||||
<TouchableOpacity
|
||||
key={`${level}-${index}`}
|
||||
style={[
|
||||
styles.regionItem,
|
||||
selectedRegions[level]?.name === region.name && styles.selectedItem
|
||||
]}
|
||||
onPress={() => handleSelectRegion(region, level)}
|
||||
>
|
||||
<ThemedText
|
||||
style={[
|
||||
styles.regionText,
|
||||
selectedRegions[level]?.name === region.name && styles.selectedText
|
||||
]}
|
||||
>
|
||||
{region.name}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// 递归渲染地区列
|
||||
const renderRegionColumns = (data: Region[][], level: number = 0) => {
|
||||
if (!data[level]) return null;
|
||||
|
||||
return (
|
||||
<React.Fragment key={`level-${level}`}>
|
||||
<View style={[
|
||||
styles.column,
|
||||
data[level + 1] && styles.hasNextLevel
|
||||
]}>
|
||||
{level > 0 && <View style={styles.divider} />}
|
||||
{renderLevel(data[level], level)}
|
||||
</View>
|
||||
{data[level + 1] && renderRegionColumns(data, level + 1)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
{renderRegionColumns(allLevelsData)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
column: {
|
||||
width: 120, // 每列固定宽度
|
||||
},
|
||||
hasNextLevel: {
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#f0f0f0',
|
||||
},
|
||||
levelContainer: {
|
||||
width: '100%',
|
||||
},
|
||||
divider: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 1,
|
||||
backgroundColor: '#e0e0e0',
|
||||
},
|
||||
regionItem: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16
|
||||
},
|
||||
selectedItem: {
|
||||
backgroundColor: '#F6F6F6',
|
||||
},
|
||||
regionText: {
|
||||
fontSize: 15,
|
||||
color: '#333',
|
||||
},
|
||||
selectedText: {
|
||||
color: '#AC7E35',
|
||||
fontWeight: '500',
|
||||
},
|
||||
arrow: {
|
||||
fontSize: 18,
|
||||
color: '#999',
|
||||
marginLeft: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default GlobalRegionPicker;
|
||||
103
components/owner/podium.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import UserSvg from "@/assets/icons/svg/ataver.svg";
|
||||
import FirstSvg from "@/assets/icons/svg/first.svg";
|
||||
import SecondSvg from "@/assets/icons/svg/second.svg";
|
||||
import ThirdSvg from "@/assets/icons/svg/third.svg";
|
||||
import { RankingItem } from "@/types/user";
|
||||
import { Image, StyleSheet, View } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
interface IPodium {
|
||||
data: RankingItem[]
|
||||
}
|
||||
const PodiumComponent = ({ data }: IPodium) => {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.item, { opacity: data[1]?.user_id ? 1 : 0 }]}>
|
||||
<SecondSvg />
|
||||
<View style={[styles.titleContainer, { backgroundColor: '#FFB645', borderTopRightRadius: 0, height: 60 }]}>
|
||||
{
|
||||
data[1]?.user_avatar_url
|
||||
? <Image source={{ uri: data[1]?.user_avatar_url }} style={{ width: 30, height: 30, borderRadius: 30 }} />
|
||||
: <UserSvg width={30} height={30} />
|
||||
}
|
||||
<ThemedText
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
style={styles.title}
|
||||
>
|
||||
{data[1]?.user_nick_name}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[styles.item, { marginHorizontal: -16, opacity: data[0]?.user_id ? 1 : 0 }]}>
|
||||
<FirstSvg />
|
||||
<View style={[styles.titleContainer, { backgroundColor: '#E2793F', height: 90 }]}>
|
||||
{
|
||||
data[0]?.user_avatar_url
|
||||
? <Image source={{ uri: data[0]?.user_avatar_url }} style={{ width: 40, height: 40, borderRadius: 40 }} />
|
||||
: <UserSvg width={40} height={40} />
|
||||
}
|
||||
<ThemedText
|
||||
numberOfLines={2}
|
||||
ellipsizeMode="tail"
|
||||
style={styles.title}
|
||||
>
|
||||
{data[0]?.user_nick_name}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[styles.item, { opacity: data[2]?.user_id ? 1 : 0 }]}>
|
||||
<ThirdSvg />
|
||||
<View style={[styles.titleContainer, { backgroundColor: '#FFD18A', borderTopLeftRadius: 0, height: 50 }]}>
|
||||
{
|
||||
data[2]?.user_avatar_url
|
||||
? <Image source={{ uri: data[2]?.user_avatar_url }} style={{ width: 20, height: 20, borderRadius: 20 }} />
|
||||
: <UserSvg width={20} height={20} />
|
||||
}
|
||||
<ThemedText
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
style={styles.title}
|
||||
>
|
||||
{data[2]?.user_nick_name}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#B48C64',
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
paddingHorizontal: 4
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: -29,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
width: 95,
|
||||
paddingHorizontal: 4,
|
||||
}
|
||||
});
|
||||
|
||||
export default PodiumComponent;
|
||||
140
components/owner/qualification/lcenses.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import { Image, Modal, Pressable, StyleSheet } from 'react-native';
|
||||
|
||||
const LcensesModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void }) => {
|
||||
const { modalVisible, setModalVisible } = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => {
|
||||
setModalVisible(!modalVisible);
|
||||
}}>
|
||||
<Pressable
|
||||
style={styles.centeredView}
|
||||
onPress={() => setModalVisible(false)}>
|
||||
<Pressable
|
||||
style={styles.modalView}
|
||||
onPress={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: "https://cdn.fairclip.cn/files/7336715310751420416/2.png" }}
|
||||
className="rounded-xl w-full h-full"
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centeredView: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
modalView: {
|
||||
width: '100%',
|
||||
height: '40%',
|
||||
backgroundColor: 'white',
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#4C320C',
|
||||
},
|
||||
closeButton: {
|
||||
fontSize: 28,
|
||||
color: '#4C320C',
|
||||
padding: 10,
|
||||
},
|
||||
modalContent: {
|
||||
flex: 1,
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 16,
|
||||
color: '#4C320C',
|
||||
},
|
||||
premium: {
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
backgroundColor: '#FAF9F6',
|
||||
borderRadius: 24,
|
||||
paddingVertical: 8
|
||||
},
|
||||
item: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#4C320C',
|
||||
},
|
||||
upgradeButton: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
upgradeButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: "600"
|
||||
},
|
||||
switchContainer: {
|
||||
width: 50,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
switchOn: {
|
||||
backgroundColor: '#E2793F',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
switchOff: {
|
||||
backgroundColor: '#E5E5E5',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
switchCircle: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
},
|
||||
switchCircleOn: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
switchCircleOff: {
|
||||
backgroundColor: '#A5A5A5',
|
||||
},
|
||||
});
|
||||
export default LcensesModal;
|
||||
201
components/owner/qualification/privacy.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import { Policy } from '@/types/personal-info';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Pressable, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import RenderHtml from 'react-native-render-html';
|
||||
|
||||
const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, type: string }) => {
|
||||
const { modalVisible, setModalVisible, type } = props;
|
||||
const [article, setArticle] = useState<Policy>({} as Policy);
|
||||
useEffect(() => {
|
||||
const loadArticle = async () => {
|
||||
// ai协议
|
||||
if (type === 'ai') {
|
||||
fetchApi<Policy>(`/system-config/policy/ai_policy`).then((res: any) => {
|
||||
setArticle(res)
|
||||
}).catch((error: any) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
// 应用协议
|
||||
if (type === 'terms') {
|
||||
fetchApi<Policy>(`/system-config/policy/terms_of_service`).then((res: any) => {
|
||||
setArticle(res)
|
||||
}).catch((error: any) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
// 隐私协议
|
||||
if (type === 'privacy') {
|
||||
fetchApi<Policy>(`/system-config/policy/privacy_policy`).then((res: any) => {
|
||||
setArticle(res)
|
||||
}).catch((error: any) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
//用户协议
|
||||
if (type === 'user') {
|
||||
fetchApi<Policy>(`/system-config/policy/user_agreement`).then((res: any) => {
|
||||
setArticle(res)
|
||||
}).catch((error: any) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
loadArticle();
|
||||
}, []);
|
||||
|
||||
if (!article) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => {
|
||||
setModalVisible(!modalVisible);
|
||||
}}>
|
||||
<Pressable
|
||||
style={styles.centeredView}
|
||||
onPress={() => setModalVisible(false)}>
|
||||
<Pressable
|
||||
style={styles.modalView}
|
||||
onPress={(e) => e.stopPropagation()}>
|
||||
<View style={styles.modalHeader}>
|
||||
<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>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||
<Text style={styles.closeButton}>×</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
||||
<RenderHtml
|
||||
source={{ html: article.content }}
|
||||
tagsStyles={{
|
||||
p: { fontSize: 16, lineHeight: 24 },
|
||||
strong: { fontWeight: 'bold' },
|
||||
em: { fontStyle: 'italic' },
|
||||
}}
|
||||
/>
|
||||
</ScrollView>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centeredView: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
modalView: {
|
||||
width: '100%',
|
||||
height: '80%',
|
||||
backgroundColor: 'white',
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#4C320C',
|
||||
},
|
||||
closeButton: {
|
||||
fontSize: 28,
|
||||
color: '#4C320C',
|
||||
padding: 10,
|
||||
},
|
||||
modalContent: {
|
||||
flex: 1,
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 16,
|
||||
color: '#4C320C',
|
||||
},
|
||||
premium: {
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
backgroundColor: '#FAF9F6',
|
||||
borderRadius: 24,
|
||||
paddingVertical: 8
|
||||
},
|
||||
item: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#4C320C',
|
||||
},
|
||||
upgradeButton: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
upgradeButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: "600"
|
||||
},
|
||||
switchContainer: {
|
||||
width: 50,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
switchOn: {
|
||||
backgroundColor: '#E2793F',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
switchOff: {
|
||||
backgroundColor: '#E5E5E5',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
switchCircle: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
},
|
||||
switchCircleOn: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
switchCircleOff: {
|
||||
backgroundColor: '#A5A5A5',
|
||||
},
|
||||
});
|
||||
export default PrivacyModal;
|
||||
94
components/owner/rankList.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import AtaverSvg from "@/assets/icons/svg/ataver.svg";
|
||||
import OwnerSvg from "@/assets/icons/svg/owner.svg";
|
||||
import { RankingItem } from "@/types/user";
|
||||
import { Image, ScrollView, StyleSheet, View } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
interface IRankList {
|
||||
data: RankingItem[]
|
||||
}
|
||||
const RankList = (props: IRankList) => {
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ flexGrow: 1, paddingHorizontal: 16, marginTop: 16 }}
|
||||
>
|
||||
<View style={styles.item}>
|
||||
<ThemedText style={styles.headerText}>Rank</ThemedText>
|
||||
<ThemedText style={styles.headerText}>Username</ThemedText>
|
||||
<ThemedText style={styles.headerText}>Profile</ThemedText>
|
||||
</View>
|
||||
{props.data?.filter((item, index) => index > 2).map((item, index) => (
|
||||
<View
|
||||
key={index}
|
||||
style={[
|
||||
styles.item,
|
||||
{
|
||||
borderBottomWidth: index === props.data.length - 1 ? 0 : 1,
|
||||
position: index == 1 ? 'relative' : 'static',
|
||||
}
|
||||
]}
|
||||
>
|
||||
{index === 1 && (
|
||||
<View style={styles.self} className="w-self">
|
||||
<OwnerSvg width="100%" height="100%" />
|
||||
</View>
|
||||
)}
|
||||
<ThemedText style={styles.itemRank}>{index + 1}</ThemedText>
|
||||
<ThemedText style={styles.itemName}>{item.user_nick_name}</ThemedText>
|
||||
<View style={{ opacity: index == 1 ? 0 : 1 }}>
|
||||
{item.user_avatar_url ? (
|
||||
<Image
|
||||
source={{ uri: item.user_avatar_url }}
|
||||
style={{ width: 40, height: 40, borderRadius: 40 }}
|
||||
/>
|
||||
) : (
|
||||
<AtaverSvg width={40} height={40} />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
headerText: {
|
||||
fontSize: 14,
|
||||
color: "#4C320C",
|
||||
fontWeight: "600"
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#B48C64',
|
||||
height: 70,
|
||||
position: 'relative',
|
||||
},
|
||||
itemRank: {
|
||||
fontSize: 14,
|
||||
color: "#4C320C",
|
||||
fontWeight: "700"
|
||||
},
|
||||
itemName: {
|
||||
fontSize: 14,
|
||||
color: "#AC7E35",
|
||||
},
|
||||
self: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: "100%",
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
zIndex: -1,
|
||||
}
|
||||
});
|
||||
export default RankList;
|
||||
101
components/owner/ranking.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import RightArrowSvg from "@/assets/icons/svg/rightArrow.svg";
|
||||
import { TitleRankings } from "@/types/user";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlatList, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
|
||||
const Ranking = ({ data }: { data: TitleRankings[] }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={styles.headerItem} onPress={() => {
|
||||
router.push('/top')
|
||||
}}>
|
||||
<ThemedText style={styles.headerTitle}>{t('generalSetting.rank', { ns: 'personal' })}</ThemedText>
|
||||
<RightArrowSvg />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<FlatList
|
||||
data={data}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={{ width: "100%" }}
|
||||
keyExtractor={(item) => item.display_name}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.item}>
|
||||
<ThemedText style={styles.rank}>No.{item.ranking}</ThemedText>
|
||||
<ThemedText style={styles.title}>{item.region}</ThemedText>
|
||||
<ThemedText style={styles.title}>{item.display_name}</ThemedText>
|
||||
<ThemedText style={styles.number}>{item.value}</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
gap: 8,
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 3.84,
|
||||
elevation: 5,
|
||||
},
|
||||
header: {
|
||||
flex: 1,
|
||||
width: "100%",
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
headerItem: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
color: '#4C320C',
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
width: "100%",
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 8, // 建议加行高
|
||||
},
|
||||
rank: {
|
||||
fontSize: 20,
|
||||
fontWeight: '700',
|
||||
color: '#4C320C',
|
||||
minWidth: 80, // 新增
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#4C320C',
|
||||
flex: 1,
|
||||
marginHorizontal: 8, // 新增
|
||||
},
|
||||
number: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#AC7E35',
|
||||
minWidth: 60, // 新增
|
||||
textAlign: 'right'
|
||||
},
|
||||
});
|
||||
|
||||
export default Ranking;
|
||||
78
components/owner/resource.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
|
||||
import * as Progress from 'react-native-progress';
|
||||
import { ThemedText } from "../ThemedText";
|
||||
import { formatBytes } from "../utils/bytes";
|
||||
|
||||
interface Data {
|
||||
all: number;
|
||||
used: number;
|
||||
}
|
||||
interface ResourceProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
data: Data
|
||||
icon: any;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
// 是否要转化单位
|
||||
isFormatBytes?: boolean;
|
||||
}
|
||||
const ResourceComponent = (props: ResourceProps) => {
|
||||
return (
|
||||
<View style={[styles.container, props.style]}>
|
||||
<View style={styles.header}>
|
||||
<View>
|
||||
<ThemedText style={styles.title} className="!text-textSecondary">{props.title}</ThemedText>
|
||||
<ThemedText style={styles.subtitle} className="!text-textPrimary">{props.subtitle || " "}</ThemedText>
|
||||
</View>
|
||||
<View>
|
||||
{props.icon}
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.dataContainer}>
|
||||
<ThemedText style={styles.dataText}>{props.isFormatBytes ? formatBytes(props.data.used) : props.data.used}/{props.isFormatBytes ? formatBytes(props.data.all) : props.data.all}</ThemedText>
|
||||
<Progress.Bar
|
||||
progress={props.data.used / props.data.all}
|
||||
width={"100%"}
|
||||
color="#AC7E35"
|
||||
unfilledColor="#ddd"
|
||||
borderWidth={0}
|
||||
borderRadius={8}
|
||||
height={2}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: "100%",
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 18,
|
||||
},
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
title: {
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 10,
|
||||
fontWeight: "400",
|
||||
},
|
||||
dataContainer: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
dataText: {
|
||||
fontSize: 12,
|
||||
width: "100%",
|
||||
color: "#AC7E35",
|
||||
textAlign: "right",
|
||||
},
|
||||
})
|
||||
|
||||
export default ResourceComponent;
|
||||
469
components/owner/setting.tsx
Normal file
@ -0,0 +1,469 @@
|
||||
import LogoutSvg from '@/assets/icons/svg/logout.svg';
|
||||
import RightArrowSvg from '@/assets/icons/svg/rightArrow.svg';
|
||||
import { useAuth } from '@/contexts/auth-context';
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import { Address, User } from '@/types/user';
|
||||
import * as Location from 'expo-location';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Linking, Modal, Platform, Pressable, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { ThemedText } from '../ThemedText';
|
||||
import LcensesModal from './qualification/lcenses';
|
||||
import PrivacyModal from './qualification/privacy';
|
||||
import CustomSwitch from './switch';
|
||||
import UserInfo from './userInfo';
|
||||
import { getLocationPermission, getPermissions, requestLocationPermission, requestMediaLibraryPermission, reverseGeocode } from './utils';
|
||||
|
||||
const SettingModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, userInfo: User }) => {
|
||||
const { modalVisible, setModalVisible, userInfo } = props;
|
||||
const { t } = useTranslation();
|
||||
const [modalType, setModalType] = useState<'ai' | 'terms' | 'privacy' | 'user'>('ai');
|
||||
// 协议弹窗
|
||||
const [privacyModalVisible, setPrivacyModalVisible] = useState(false);
|
||||
// 许可证弹窗
|
||||
const [lcensesModalVisible, setLcensesModalVisible] = useState(false);
|
||||
|
||||
const { logout } = useAuth();
|
||||
const router = useRouter();
|
||||
// 打开设置
|
||||
const openAppSettings = () => {
|
||||
Linking.openSettings();
|
||||
};
|
||||
// 通知消息权限开关
|
||||
const [notificationsEnabled, setNotificationsEnabled] = useState(false);
|
||||
const toggleNotifications = () => setNotificationsEnabled(previous => !previous);
|
||||
|
||||
// 相册权限
|
||||
const [albumEnabled, setAlbumEnabled] = useState(false);
|
||||
const toggleAlbum = () => {
|
||||
if (albumEnabled) {
|
||||
// 引导去设置关闭权限
|
||||
openAppSettings()
|
||||
} else {
|
||||
requestMediaLibraryPermission().then((res) => {
|
||||
setAlbumEnabled(res as boolean);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 位置权限
|
||||
const [locationEnabled, setLocationEnabled] = useState(false);
|
||||
// 位置权限更改
|
||||
const toggleLocation = async () => {
|
||||
if (locationEnabled) {
|
||||
// 引导去设置关闭权限
|
||||
openAppSettings()
|
||||
} else {
|
||||
requestLocationPermission().then((res) => {
|
||||
setLocationEnabled(res as boolean);
|
||||
})
|
||||
}
|
||||
};
|
||||
// 正在获取位置信息
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// 动画开启
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
// 当前位置状态
|
||||
const [currentLocation, setCurrentLocation] = useState<Address>({} as Address);
|
||||
|
||||
// 获取当前位置
|
||||
const getCurrentLocation = async () => {
|
||||
setIsLoading(true);
|
||||
setIsRefreshing(true);
|
||||
|
||||
try {
|
||||
// 1. 首先检查当前权限状态 -- 获取当前的位置权限
|
||||
let currentStatus = await getLocationPermission();
|
||||
console.log('当前权限状态:', currentStatus);
|
||||
|
||||
// 2. 如果没有权限,则请求权限
|
||||
if (!currentStatus) {
|
||||
const newStatus = await requestLocationPermission();
|
||||
setLocationEnabled(newStatus);
|
||||
currentStatus = newStatus;
|
||||
|
||||
if (!currentStatus) {
|
||||
alert('需要位置权限才能继续');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 确保位置服务已启用
|
||||
const isEnabled = await Location.hasServicesEnabledAsync();
|
||||
if (!isEnabled) {
|
||||
alert('请先启用位置服务');
|
||||
return;
|
||||
}
|
||||
console.log('位置服务已启用');
|
||||
// 4. 获取当前位置
|
||||
const location = await Location.getCurrentPositionAsync({
|
||||
accuracy: Location.Accuracy.High, // 使用高精度
|
||||
timeInterval: 10000, // 可选:最大等待时间(毫秒)
|
||||
});
|
||||
console.log('位置:', location);
|
||||
|
||||
// 地理位置逆编码
|
||||
const address = await reverseGeocode(location.coords.latitude, location.coords.longitude);
|
||||
// 5. 更新位置状态
|
||||
setCurrentLocation(address as Address);
|
||||
|
||||
return location;
|
||||
} catch (error: any) {
|
||||
if (error.code === 'PERMISSION_DENIED' || error.code === 'PERMISSION_DENIED_ERROR') {
|
||||
alert('位置权限被拒绝,请在设置中启用位置服务');
|
||||
} else if (error.code === 'TIMEOUT') {
|
||||
alert('获取位置超时,请检查网络和位置服务');
|
||||
} else {
|
||||
alert(`无法获取您的位置: ${error.message || '未知错误'}`);
|
||||
}
|
||||
throw error; // 重新抛出错误以便上层处理
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 退出登录
|
||||
|
||||
const handleLogout = () => {
|
||||
fetchApi("/iam/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
await logout();
|
||||
setModalVisible(false);
|
||||
router.replace('/login');
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("jwt has expired.");
|
||||
});
|
||||
};
|
||||
// 检查是否有权限
|
||||
useEffect(() => {
|
||||
if (modalVisible) {
|
||||
// 位置权限
|
||||
getLocationPermission().then((res) => {
|
||||
setLocationEnabled(res);
|
||||
})
|
||||
// 媒体库权限
|
||||
getPermissions().then((res) => {
|
||||
setAlbumEnabled(res);
|
||||
})
|
||||
}
|
||||
}, [modalVisible])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => {
|
||||
setModalVisible(!modalVisible);
|
||||
}}>
|
||||
<Pressable
|
||||
style={styles.centeredView}
|
||||
onPress={() => setModalVisible(false)}>
|
||||
<Pressable
|
||||
style={styles.modalView}
|
||||
onPress={(e) => e.stopPropagation()}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={{ opacity: 0 }}>Settings</Text>
|
||||
<Text style={styles.modalTitle}>{t('generalSetting.allTitle', { ns: 'personal' })}</Text>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||
<Text style={styles.closeButton}>×</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
||||
{/* 用户信息 */}
|
||||
<UserInfo
|
||||
userInfo={userInfo}
|
||||
setModalVisible={setModalVisible}
|
||||
modalVisible={modalVisible}
|
||||
setCurrentLocation={setCurrentLocation}
|
||||
getCurrentLocation={getCurrentLocation}
|
||||
isLoading={isLoading}
|
||||
isRefreshing={isRefreshing}
|
||||
currentLocation={currentLocation}
|
||||
/>
|
||||
{/* 升级版本 */}
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.subscription', { ns: 'personal' })}</ThemedText>
|
||||
<View style={styles.premium}>
|
||||
<View>
|
||||
<ThemedText style={styles.itemText}>{t('generalSetting.subscriptionTitle', { ns: 'personal' })}</ThemedText>
|
||||
<ThemedText style={{ color: '#AC7E35', fontSize: 12 }}>{t('generalSetting.subscriptionText', { ns: 'personal' })}</ThemedText>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.upgradeButton}
|
||||
onPress={async () => {
|
||||
|
||||
}}
|
||||
>
|
||||
<Text style={styles.upgradeButtonText}>
|
||||
{t('generalSetting.upgrade', { ns: 'personal' })}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
{/* 消息通知 */}
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('permission.pushNotification', { ns: 'personal' })}</ThemedText>
|
||||
<View style={styles.premium}>
|
||||
<View>
|
||||
<ThemedText style={styles.itemText}>{t('permission.pushNotification', { ns: 'personal' })}</ThemedText>
|
||||
</View>
|
||||
<CustomSwitch
|
||||
isEnabled={notificationsEnabled}
|
||||
toggleSwitch={toggleNotifications}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{/* 权限信息 */}
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('permission.permissionManagement', { ns: 'personal' })}</ThemedText>
|
||||
<View style={styles.content}>
|
||||
{/* 相册权限 */}
|
||||
<View style={styles.item}>
|
||||
<ThemedText style={styles.itemText}>{t('permission.galleryAccess', { ns: 'personal' })}</ThemedText>
|
||||
<CustomSwitch
|
||||
isEnabled={albumEnabled}
|
||||
toggleSwitch={toggleAlbum}
|
||||
/>
|
||||
</View>
|
||||
{/* 分割线 */}
|
||||
<Divider />
|
||||
{/* 位置权限 */}
|
||||
<View style={styles.item}>
|
||||
<View>
|
||||
<ThemedText style={styles.itemText}>{t('permission.locationPermission', { ns: 'personal' })}</ThemedText>
|
||||
</View>
|
||||
<CustomSwitch
|
||||
isEnabled={locationEnabled}
|
||||
toggleSwitch={toggleLocation}
|
||||
/>
|
||||
</View>
|
||||
{/* <Divider /> */}
|
||||
{/* 相册成片权限 */}
|
||||
{/* <View style={styles.item}>
|
||||
<View>
|
||||
<ThemedText style={styles.itemText}>Opus Permission</ThemedText>
|
||||
</View>
|
||||
<CustomSwitch
|
||||
isEnabled={albumEnabled}
|
||||
toggleSwitch={toggleAlbum}
|
||||
/>
|
||||
</View> */}
|
||||
</View>
|
||||
</View>
|
||||
{/* 账号 */}
|
||||
{/* <View style={{ marginTop: 16 }}>
|
||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>Account</ThemedText>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.item}>
|
||||
<View>
|
||||
<ThemedText style={styles.itemText}>Notifications</ThemedText>
|
||||
</View>
|
||||
<CustomSwitch
|
||||
isEnabled={notificationsEnabled}
|
||||
toggleSwitch={toggleNotifications}
|
||||
/>
|
||||
</View>
|
||||
<Divider />
|
||||
<View style={styles.item}>
|
||||
<ThemedText style={styles.itemText}>Delete Account</ThemedText>
|
||||
<DeleteSvg />
|
||||
</View>
|
||||
</View>
|
||||
</View> */}
|
||||
{/* 协议 */}
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('lcenses.title', { ns: 'personal' })}</ThemedText>
|
||||
<View style={styles.content}>
|
||||
<TouchableOpacity style={styles.item} onPress={() => { setLcensesModalVisible(true) }} >
|
||||
<ThemedText style={styles.itemText}>{t('lcenses.qualification', { ns: 'personal' })}</ThemedText>
|
||||
<RightArrowSvg />
|
||||
</TouchableOpacity>
|
||||
<Divider />
|
||||
<TouchableOpacity style={styles.item} onPress={() => Linking.openURL("https://beian.miit.gov.cn/")} >
|
||||
<ThemedText style={styles.itemText}>{t('lcenses.ICP', { ns: 'personal' })}沪ICP备2023032876号-4</ThemedText>
|
||||
<RightArrowSvg />
|
||||
</TouchableOpacity>
|
||||
<Divider />
|
||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('privacy'); setPrivacyModalVisible(true) }} >
|
||||
<ThemedText style={styles.itemText}>{t('lcenses.privacyPolicy', { ns: 'personal' })}</ThemedText>
|
||||
<RightArrowSvg />
|
||||
</TouchableOpacity>
|
||||
<Divider />
|
||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('ai'); setPrivacyModalVisible(true) }} >
|
||||
<ThemedText style={styles.itemText}>{t('lcenses.aiPolicy', { ns: 'personal' })}</ThemedText>
|
||||
<RightArrowSvg />
|
||||
</TouchableOpacity>
|
||||
<Divider />
|
||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('terms'); setPrivacyModalVisible(true) }} >
|
||||
<ThemedText style={styles.itemText}>{t('lcenses.applyPermission', { ns: 'personal' })}</ThemedText>
|
||||
<RightArrowSvg />
|
||||
</TouchableOpacity>
|
||||
<Divider />
|
||||
<TouchableOpacity style={styles.item} onPress={() => { setModalType('user'); setPrivacyModalVisible(true) }} >
|
||||
<ThemedText style={styles.itemText}>{t('lcenses.userAgreement', { ns: 'personal' })}</ThemedText>
|
||||
<RightArrowSvg />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
{/* 其他信息 */}
|
||||
<View style={{ marginTop: 16 }}>
|
||||
<ThemedText style={{ marginLeft: 16, marginVertical: 8, color: '#AC7E35', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.otherInformation', { ns: 'personal' })}</ThemedText>
|
||||
<View style={styles.content}>
|
||||
<TouchableOpacity style={styles.item} onPress={() => Linking.openURL("https://work.weixin.qq.com/kfid/kfca0ac87f4e05e8bfd")} >
|
||||
<ThemedText style={styles.itemText}>{t('generalSetting.contactUs', { ns: 'personal' })}</ThemedText>
|
||||
{/* <RightArrowSvg /> */}
|
||||
</TouchableOpacity>
|
||||
<Divider />
|
||||
{Platform.OS !== 'ios' && (
|
||||
<View>
|
||||
<TouchableOpacity style={styles.item} onPress={() => Linking.openURL("https://work.weixin.qq.com/kfid/kfca0ac87f4e05e8bfd")} >
|
||||
<ThemedText style={styles.itemText}>{t('generalSetting.cleanCache', { ns: 'personal' })}</ThemedText>
|
||||
{/* <RightArrowSvg /> */}
|
||||
</TouchableOpacity>
|
||||
<Divider />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.item}>
|
||||
<ThemedText style={styles.itemText}>{t('generalSetting.version', { ns: 'personal' })}</ThemedText>
|
||||
<ThemedText style={styles.itemText}>{"0.5.0"}</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* 退出 */}
|
||||
<TouchableOpacity style={[styles.premium, { marginVertical: 8 }]} onPress={handleLogout}>
|
||||
<ThemedText style={{ color: '#E2793F', fontSize: 14, fontWeight: '600' }}>{t('generalSetting.logout', { ns: 'personal' })}</ThemedText>
|
||||
<LogoutSvg />
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
{/* 协议弹窗 */}
|
||||
<PrivacyModal modalVisible={privacyModalVisible} setModalVisible={setPrivacyModalVisible} type={modalType} />
|
||||
{/* 许可证弹窗 */}
|
||||
<LcensesModal modalVisible={lcensesModalVisible} setModalVisible={setLcensesModalVisible} />
|
||||
{/* 通知 */}
|
||||
{/* <AuthNotifications setNotificationsEnabled={setNotificationsEnabled} notificationsEnabled={notificationsEnabled} /> */}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
centeredView: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
modalView: {
|
||||
width: '100%',
|
||||
height: '80%',
|
||||
backgroundColor: 'white',
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#4C320C',
|
||||
},
|
||||
closeButton: {
|
||||
fontSize: 28,
|
||||
color: '#4C320C',
|
||||
padding: 10,
|
||||
},
|
||||
modalContent: {
|
||||
flex: 1,
|
||||
},
|
||||
modalText: {
|
||||
fontSize: 16,
|
||||
color: '#4C320C',
|
||||
},
|
||||
premium: {
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
backgroundColor: '#FAF9F6',
|
||||
borderRadius: 24,
|
||||
paddingVertical: 8
|
||||
},
|
||||
item: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#4C320C',
|
||||
},
|
||||
upgradeButton: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
upgradeButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: "600"
|
||||
},
|
||||
switchContainer: {
|
||||
width: 50,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
switchOn: {
|
||||
backgroundColor: '#E2793F',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
switchOff: {
|
||||
backgroundColor: '#E5E5E5',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
switchCircle: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
},
|
||||
switchCircleOn: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
switchCircleOff: {
|
||||
backgroundColor: '#A5A5A5',
|
||||
},
|
||||
});
|
||||
|
||||
const Divider = () => {
|
||||
return (
|
||||
<View className='w-full h-[1px] bg-[#B5977F]'></View>
|
||||
)
|
||||
}
|
||||
export default SettingModal;
|
||||
42
components/owner/switch.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { StyleSheet, TouchableOpacity, View } from "react-native";
|
||||
|
||||
const CustomSwitch = ({ isEnabled, toggleSwitch }: { isEnabled: boolean, toggleSwitch: () => void }) => (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
onPress={toggleSwitch}
|
||||
style={[styles.switchContainer, isEnabled ? styles.switchOn : styles.switchOff]}
|
||||
>
|
||||
<View style={[styles.switchCircle, isEnabled ? styles.switchCircleOn : styles.switchCircleOff]} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
switchContainer: {
|
||||
width: 50,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
switchOn: {
|
||||
backgroundColor: '#FFB645',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
switchOff: {
|
||||
backgroundColor: '#E5E5E5',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
switchCircle: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
},
|
||||
switchCircleOn: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
switchCircleOff: {
|
||||
backgroundColor: '#A5A5A5',
|
||||
},
|
||||
});
|
||||
|
||||
export default CustomSwitch;
|
||||
189
components/owner/userInfo.tsx
Normal file
@ -0,0 +1,189 @@
|
||||
import UserSvg from "@/assets/icons/svg/ataver.svg";
|
||||
import EditSvg from "@/assets/icons/svg/edit.svg";
|
||||
import LocationSvg from "@/assets/icons/svg/location.svg";
|
||||
import RefreshSvg from "@/assets/icons/svg/refresh.svg";
|
||||
import { Address, User } from "@/types/user";
|
||||
import { useRouter } from "expo-router";
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Animated, Easing, Image, Platform, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
|
||||
interface UserInfoProps {
|
||||
userInfo: User;
|
||||
setModalVisible: (visible: boolean) => void;
|
||||
modalVisible: boolean;
|
||||
getCurrentLocation: () => void;
|
||||
isLoading: boolean;
|
||||
isRefreshing: boolean;
|
||||
currentLocation: Address;
|
||||
setCurrentLocation: (location: Address) => void;
|
||||
}
|
||||
const UserInfo = (props: UserInfoProps) => {
|
||||
const { userInfo, setModalVisible, modalVisible, getCurrentLocation, isLoading, isRefreshing, currentLocation, setCurrentLocation } = props;
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
// 获取本地存储的location
|
||||
const getLocation = async () => {
|
||||
if (Platform.OS === 'web') {
|
||||
const location = localStorage.getItem('location');
|
||||
if (location) {
|
||||
setCurrentLocation(JSON.parse(location));
|
||||
}
|
||||
} else {
|
||||
const location = await SecureStore.getItemAsync('location');
|
||||
if (location) {
|
||||
setCurrentLocation(JSON.parse(location));
|
||||
}
|
||||
}
|
||||
};
|
||||
// 添加旋转动画值
|
||||
const spinValue = useRef(new Animated.Value(0)).current;
|
||||
|
||||
|
||||
// 旋转动画
|
||||
const startSpin = () => {
|
||||
spinValue.setValue(0);
|
||||
Animated.loop(
|
||||
Animated.timing(spinValue, {
|
||||
toValue: 1,
|
||||
duration: 1000,
|
||||
easing: Easing.linear,
|
||||
useNativeDriver: true,
|
||||
})
|
||||
).start();
|
||||
};
|
||||
|
||||
// 停止旋转
|
||||
const stopSpin = () => {
|
||||
spinValue.stopAnimation();
|
||||
spinValue.setValue(0);
|
||||
};
|
||||
// 当开始加载时启动旋转
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
startSpin();
|
||||
} else {
|
||||
stopSpin();
|
||||
}
|
||||
}, [isLoading]);
|
||||
|
||||
// 在组件挂载时自动获取位置(可选)
|
||||
useEffect(() => {
|
||||
if (modalVisible) {
|
||||
getLocation();
|
||||
if (Object.keys(currentLocation).length === 0) {
|
||||
getCurrentLocation();
|
||||
}
|
||||
}
|
||||
}, [modalVisible])
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.info}>
|
||||
<ThemedText style={styles.nickname}>{userInfo?.nickname}</ThemedText>
|
||||
<ThemedText style={styles.userId}>{t('generalSetting.userId', { ns: 'personal' })}{userInfo?.user_id}</ThemedText>
|
||||
<View style={styles.location}>
|
||||
<LocationSvg />
|
||||
<ThemedText style={styles.userId}>
|
||||
{currentLocation?.country}-{currentLocation?.city}-{currentLocation?.district}
|
||||
</ThemedText>
|
||||
<TouchableOpacity
|
||||
onPress={getCurrentLocation}
|
||||
disabled={isRefreshing}
|
||||
style={styles.refreshContainer}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.refreshIcon,
|
||||
{
|
||||
transform: [{
|
||||
rotate: spinValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0deg', '360deg']
|
||||
})
|
||||
}]
|
||||
}
|
||||
]}
|
||||
>
|
||||
<RefreshSvg color="#4C320C" width={16} height={16} />
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.avatar}>
|
||||
{userInfo?.avatar_file_url
|
||||
?
|
||||
<Image
|
||||
className='rounded-full'
|
||||
style={{ width: 80, height: 80 }}
|
||||
source={{ uri: userInfo?.avatar_file_url }}
|
||||
/>
|
||||
:
|
||||
<UserSvg width={80} height={80} />
|
||||
}
|
||||
<TouchableOpacity style={styles.edit} onPress={() => {
|
||||
setModalVisible(false);
|
||||
router.push('/user-message')
|
||||
}}>
|
||||
<EditSvg />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
backgroundColor: "#FAF9F6",
|
||||
padding: 16,
|
||||
borderRadius: 24,
|
||||
},
|
||||
info: {
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
},
|
||||
nickname: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#4C320C',
|
||||
},
|
||||
userId: {
|
||||
fontSize: 12,
|
||||
color: '#4C320C',
|
||||
},
|
||||
location: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: 4,
|
||||
gap: 4
|
||||
},
|
||||
refreshContainer: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 4
|
||||
},
|
||||
refreshIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
avatar: {
|
||||
position: 'relative',
|
||||
},
|
||||
edit: {
|
||||
position: 'absolute',
|
||||
padding: 8,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 50,
|
||||
right: -5,
|
||||
bottom: -5,
|
||||
}
|
||||
})
|
||||
|
||||
export default UserInfo;
|
||||
70
components/owner/userName.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import UserSvg from '@/assets/icons/svg/ataver.svg';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { UserInfoDetails } from '@/types/user';
|
||||
// import { Image } from 'expo-image';
|
||||
import { Image, ScrollView, View } from 'react-native';
|
||||
export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) {
|
||||
|
||||
return (
|
||||
<View className='flex flex-row items-center mt-[1rem] gap-[1rem]'>
|
||||
{/* 用户名 */}
|
||||
<View className='flex flex-col gap-4 w-[68vw]'>
|
||||
<View className='flex flex-row items-center justify-between w-full'>
|
||||
<View className='flex flex-row items-center gap-2'>
|
||||
<ThemedText
|
||||
className='max-w-[36vw] !text-textSecondary !font-semibold !text-2xl'
|
||||
numberOfLines={1} // 限制为1行
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{userInfo?.user_info?.nickname}
|
||||
</ThemedText>
|
||||
<ScrollView
|
||||
className='max-w-[26vw] '
|
||||
horizontal // 水平滚动
|
||||
showsHorizontalScrollIndicator={false} // 隐藏滚动条
|
||||
contentContainerStyle={{
|
||||
flexDirection: 'row',
|
||||
gap: 8, // 间距,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{
|
||||
userInfo?.medal_infos?.map((item, index) => (
|
||||
<Image
|
||||
key={index}
|
||||
source={{ uri: item.url }}
|
||||
style={{ width: 24, height: 24 }}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView
|
||||
className='max-w-[68vw]'
|
||||
horizontal // 水平滚动
|
||||
showsHorizontalScrollIndicator={false} // 隐藏滚动条
|
||||
contentContainerStyle={{
|
||||
flexDirection: 'row',
|
||||
gap: 8 // 间距
|
||||
}}
|
||||
>
|
||||
<ThemedText style={{ color: '#AC7E35', fontSize: 12, fontWeight: '600' }}>User ID:{userInfo?.user_info?.user_id}</ThemedText>
|
||||
</ScrollView>
|
||||
</View>
|
||||
{/* 头像 */}
|
||||
<View>
|
||||
{userInfo?.user_info?.avatar_file_url
|
||||
?
|
||||
<Image
|
||||
source={{ uri: "http://cdn.fairclip.cn/files/7348942720074911745/original_1752124481039_198d7b9a428c67f2bfd87ec128daad1b.jpg" }}
|
||||
style={{ width: 80, height: 80, borderRadius: 40 }}
|
||||
/>
|
||||
:
|
||||
<UserSvg width={80} height={80} />
|
||||
}
|
||||
</View>
|
||||
</View >
|
||||
);
|
||||
}
|
||||
|
||||
195
components/owner/utils.ts
Normal file
@ -0,0 +1,195 @@
|
||||
// 地理位置逆编码
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import * as Location from 'expo-location';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { Alert, Linking, Platform } from 'react-native';
|
||||
export const reverseGeocode = async (latitude: number, longitude: number) => {
|
||||
try {
|
||||
const addressResults = await Location.reverseGeocodeAsync({ latitude, longitude });
|
||||
for (let address of addressResults) {
|
||||
console.log('地址:', address);
|
||||
if (Platform.OS === 'web') {
|
||||
localStorage.setItem('location', JSON.stringify(address));
|
||||
} else {
|
||||
SecureStore.setItemAsync('location', JSON.stringify(address));
|
||||
}
|
||||
return address;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('逆地理编码失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取位置权限
|
||||
export const getLocationPermission = async () => {
|
||||
const { status } = await Location.getForegroundPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
// Alert.alert('需要位置权限', '请允许访问位置以继续');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 请求位置权限
|
||||
export const requestLocationPermission = async () => {
|
||||
try {
|
||||
// 1. 先检查当前权限状态
|
||||
const { status, canAskAgain } = await Location.getForegroundPermissionsAsync();
|
||||
console.log('当前权限状态:', { status, canAskAgain });
|
||||
console.log("canAskAgain", canAskAgain);
|
||||
|
||||
// 2. 如果已经有权限,直接返回
|
||||
if (status === 'granted') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 如果用户之前选择了"拒绝且不再询问"
|
||||
if (status === 'denied' && !canAskAgain) {
|
||||
// 显示提示,引导用户去设置
|
||||
const openSettings = await new Promise(resolve => {
|
||||
Alert.alert(
|
||||
'需要位置权限',
|
||||
'您之前拒绝了位置权限。要使用此功能,请在设置中启用位置权限。',
|
||||
[
|
||||
{
|
||||
text: '取消',
|
||||
style: 'cancel',
|
||||
onPress: () => resolve(false)
|
||||
},
|
||||
{
|
||||
text: '去设置',
|
||||
onPress: () => resolve(true)
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
if (openSettings) {
|
||||
// 打开应用设置
|
||||
await Linking.openSettings();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 如果是第一次请求或可以再次询问,则请求权限
|
||||
console.log('请求位置权限...');
|
||||
const { status: newStatus } = await Location.requestForegroundPermissionsAsync();
|
||||
console.log('新权限状态:', newStatus);
|
||||
|
||||
if (newStatus !== 'granted') {
|
||||
Alert.alert('需要位置权限', '请允许访问位置以使用此功能');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('请求位置权限时出错:', error);
|
||||
Alert.alert('错误', '请求位置权限时出错');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取媒体库权限
|
||||
export const getPermissions = async () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
const { status: mediaStatus } = await ImagePicker.getMediaLibraryPermissionsAsync();
|
||||
if (mediaStatus !== 'granted') {
|
||||
// Alert.alert('需要媒体库权限', '请允许访问媒体库以继续');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 请求媒体库权限
|
||||
export const requestPermissions = async () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
const mediaStatus = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
if (!mediaStatus.granted) {
|
||||
// Alert.alert('需要媒体库权限', '请允许访问媒体库以继续');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查相册/媒体库权限
|
||||
* @returns 返回权限状态对象
|
||||
*/
|
||||
export const checkMediaLibraryPermission = async (): Promise<{
|
||||
hasPermission: boolean;
|
||||
canAskAgain: boolean;
|
||||
status: ImagePicker.PermissionStatus;
|
||||
}> => {
|
||||
if (Platform.OS === 'web') {
|
||||
return { hasPermission: true, canAskAgain: true, status: 'granted' };
|
||||
}
|
||||
|
||||
const { status, canAskAgain } = await ImagePicker.getMediaLibraryPermissionsAsync();
|
||||
|
||||
return {
|
||||
hasPermission: status === 'granted',
|
||||
canAskAgain,
|
||||
status
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 请求相册/媒体库权限
|
||||
* @param showAlert 是否在无权限时显示提示
|
||||
* @returns 返回是否已授权
|
||||
*/
|
||||
export const requestMediaLibraryPermission = async (showAlert: boolean = true): Promise<boolean> => {
|
||||
if (Platform.OS === 'web') {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 检查当前权限状态
|
||||
const { status: existingStatus, canAskAgain } = await checkMediaLibraryPermission();
|
||||
|
||||
// 2. 如果已经有权限,直接返回
|
||||
if (existingStatus === 'granted') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 如果之前被拒绝且不能再次询问
|
||||
if (existingStatus === 'denied' && !canAskAgain) {
|
||||
if (showAlert) {
|
||||
const openSettings = await new Promise<boolean>(resolve => {
|
||||
Alert.alert(
|
||||
'需要媒体库权限',
|
||||
'您之前拒绝了媒体库访问权限。要选择照片,请在设置中启用媒体库权限。',
|
||||
[
|
||||
{ text: '取消', style: 'cancel', onPress: () => resolve(false) },
|
||||
{ text: '去设置', onPress: () => resolve(true) }
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
if (openSettings) {
|
||||
await Linking.openSettings();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 请求权限
|
||||
const { status: newStatus } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
|
||||
if (newStatus !== 'granted' && showAlert) {
|
||||
Alert.alert('需要媒体库权限', '请允许访问媒体库以方便后续操作');
|
||||
}
|
||||
|
||||
return newStatus === 'granted';
|
||||
} catch (error) {
|
||||
console.error('请求媒体库权限时出错:', error);
|
||||
if (showAlert) {
|
||||
Alert.alert('错误', '请求媒体库权限时出错');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
17
components/utils/bytes.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 将字节数转换为易读的格式
|
||||
* @param bytes 字节数
|
||||
* @param decimals 保留的小数位数,默认为2
|
||||
* @returns 格式化后的字符串,如 "1.5 GB"
|
||||
*/
|
||||
export function formatBytes(bytes: number, decimals = 2): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
90
components/utils/cascaderData.ts
Normal file
@ -0,0 +1,90 @@
|
||||
// 定义源数据类型
|
||||
type SourceRegion = {
|
||||
name: string;
|
||||
shortCode?: string;
|
||||
regions?: SourceRegion[];
|
||||
[key: string]: any; // 允许其他自定义属性
|
||||
};
|
||||
|
||||
// 定义目标数据类型
|
||||
type TargetRegion = {
|
||||
name: string;
|
||||
value?: string | number; // 添加 value 字段
|
||||
children?: TargetRegion[];
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* 将任意结构的地区数据转换为统一的嵌套结构
|
||||
* @param data 源数据数组
|
||||
* @param keys 字段映射配置
|
||||
* @returns 转换后的数据
|
||||
*/
|
||||
export function convertRegions(
|
||||
data: SourceRegion[],
|
||||
keys: {
|
||||
nameKey?: string;
|
||||
valueKey?: string; // 新增:指定 value 字段
|
||||
regionsKey?: string;
|
||||
childrenKey?: string;
|
||||
} = {}
|
||||
): TargetRegion[] {
|
||||
const {
|
||||
nameKey = 'name',
|
||||
valueKey = 'shortCode', // 默认使用 shortCode 作为 value
|
||||
regionsKey = 'regions',
|
||||
childrenKey = 'children',
|
||||
} = keys;
|
||||
|
||||
return data.map(item => {
|
||||
const converted: TargetRegion = {
|
||||
name: item[nameKey] as string,
|
||||
};
|
||||
|
||||
// 如果指定了 valueKey 且源数据中存在该字段,则添加 value
|
||||
if (valueKey && item[valueKey] !== undefined) {
|
||||
converted.value = item[valueKey];
|
||||
}
|
||||
|
||||
if (item[regionsKey]) {
|
||||
const children = convertRegions(
|
||||
item[regionsKey] as SourceRegion[],
|
||||
keys // 传递 keys 以保持配置一致
|
||||
);
|
||||
if (children.length > 0) {
|
||||
converted[childrenKey] = children;
|
||||
}
|
||||
}
|
||||
|
||||
return converted;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 使用示例
|
||||
* regionsData 是原数据
|
||||
*
|
||||
*
|
||||
* const transformed = convertRegions(regionsData, {
|
||||
* nameKey: 'name', // 源数据中表示“名称”的字段
|
||||
* regionsKey: 'regions', // 源数据中表示“子级区域”的字段
|
||||
* childrenKey: 'children' // 输出结构中表示“子级区域”的字段
|
||||
* });
|
||||
*
|
||||
*
|
||||
* 输出示例:
|
||||
* [
|
||||
* {
|
||||
* "name": "北京市",
|
||||
* "value": "BJ",
|
||||
* "children": [
|
||||
* {
|
||||
* "name": "东城区",
|
||||
* "value": "Dongcheng"
|
||||
* },
|
||||
* // ...
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
22
components/utils/objectToCascader.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { GroupedData, TargetItem } from "@/types/user";
|
||||
|
||||
export function transformData(data: GroupedData): TargetItem[] {
|
||||
const result: TargetItem[] = [];
|
||||
|
||||
for (const category in data) {
|
||||
const items = data[category];
|
||||
|
||||
// 构建该类别的 children
|
||||
const children = items.map(item => ({
|
||||
name: item.display_name,
|
||||
value: item.id
|
||||
}));
|
||||
|
||||
result.push({
|
||||
name: category,
|
||||
children,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
28
components/utils/time.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 将秒数转换为更友好的时间格式
|
||||
* @param seconds 总秒数
|
||||
* @returns 格式化后的时间字符串
|
||||
*/
|
||||
export function formatDuration(seconds: number): string {
|
||||
if (seconds < 60) {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
|
||||
if (minutes < 60) {
|
||||
return remainingSeconds > 0
|
||||
? `${minutes}min${remainingSeconds}s`
|
||||
: `${minutes}min`;
|
||||
}
|
||||
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const remainingMinutes = minutes % 60;
|
||||
|
||||
if (remainingMinutes === 0) {
|
||||
return `${hours}h`;
|
||||
}
|
||||
|
||||
return `${hours}h${remainingMinutes}min`;
|
||||
}
|
||||
@ -1,24 +1,25 @@
|
||||
{
|
||||
"info": {
|
||||
"photoCount": "Photos",
|
||||
"videoCount": "Videos",
|
||||
"memoryData": "Storage"
|
||||
"photoCount": "Photo Count",
|
||||
"videoCount": "Video Count",
|
||||
"memoryData": "Memory Data"
|
||||
},
|
||||
"pro": {
|
||||
"title": "Subscribe to MemoWark",
|
||||
"subtitle": "Let the love of family flow again like memories in photos"
|
||||
"subtitle": "Family love flows again like memories in photos"
|
||||
},
|
||||
"setting": {
|
||||
"syncThirdPartyData": "Sync WeChat Data",
|
||||
"myGallery": "My Gallery",
|
||||
"myVideo": "My Created",
|
||||
"myVideo": "My Creations",
|
||||
"AIVideo": "AI Video",
|
||||
"setting": "General Settings",
|
||||
"recommendMemoWake": "Recommend MemoWake",
|
||||
"fiveStarReview": "Rate 5 Stars",
|
||||
"fiveStarReview": "5-Star Rating",
|
||||
"contactUs": "Contact Support",
|
||||
"qualification": "Certifications",
|
||||
"otherAgreement": "Other Agreements"
|
||||
"qualification": "Qualifications",
|
||||
"otherAgreement": "Other Agreements",
|
||||
"version": "Version"
|
||||
},
|
||||
"personalInfo": {
|
||||
"title": "Personal Information",
|
||||
@ -26,10 +27,61 @@
|
||||
"nickname": "Nickname",
|
||||
"userId": "User ID",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"phone": "Phone Number",
|
||||
"chat": "WeChat",
|
||||
"changeNickname": "Change Nickname",
|
||||
"nicknameSave": "Save",
|
||||
"phoneBind": "Bind Phone Number"
|
||||
},
|
||||
"lcenses": {
|
||||
"title": "Agreements",
|
||||
"ICP": "ICP: ",
|
||||
"licencePhoto": "Business License",
|
||||
"otherLcenses": "Other Agreements",
|
||||
"userAgreement": "User Agreement",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"aiPolicy": "AI Feature Usage Guidelines",
|
||||
"applyPermission": "Request Permission",
|
||||
"qualification": "Qualifications"
|
||||
},
|
||||
"permission": {
|
||||
"permissionManagement": "Permission Settings",
|
||||
"pushNotification": "Push Notifications",
|
||||
"galleryAccess": "Photo Library Access",
|
||||
"locationPermission": "Location Access",
|
||||
"personalizedRecommendation": "Personalized Recommendations"
|
||||
},
|
||||
"generalSetting": {
|
||||
"title": "General Settings",
|
||||
"permissionManagement": "Permission Settings",
|
||||
"pushNotification": "Push Notifications",
|
||||
"galleryAccess": "Photo Library Access",
|
||||
"personalizedRecommendation": "Personalized Recommendations",
|
||||
"deleteAccount": "Delete Account",
|
||||
"logout": "Log Out",
|
||||
"upgrade": "Upgrade",
|
||||
"subscription": "Subscription",
|
||||
"subscriptionTitle": "MemoWake Premium",
|
||||
"subscriptionText": "Unlock more of what you love",
|
||||
"otherInformation": "Other Information",
|
||||
"contactUs": "Contact Support",
|
||||
"version": "Version",
|
||||
"cleanCache": "Clear Cache",
|
||||
"allTitle": "Settings",
|
||||
"album": "Gallery",
|
||||
"shareProfile": "Share Profile",
|
||||
"classify": "Categories",
|
||||
"confirm": "Confirm",
|
||||
"location": "Location",
|
||||
"rank": "Top Memory Makers",
|
||||
"userId": "User ID",
|
||||
"usedStorage": "Storage Used",
|
||||
"remainingPoints": "Points Remaining",
|
||||
"totalVideo": "Total Videos",
|
||||
"totalPhoto": "Total Photos",
|
||||
"live": "Live Photos",
|
||||
"videoLength": "Video Duration",
|
||||
"storiesCreated": "Stories Created",
|
||||
"conversationsWithMemo": "Conversations with Memo"
|
||||
}
|
||||
}
|
||||
@ -34,14 +34,22 @@
|
||||
"phoneBind": "绑定手机号"
|
||||
},
|
||||
"lcenses": {
|
||||
"title": "资质证照",
|
||||
"title": "协议",
|
||||
"ICP": "ICP备案:",
|
||||
"licencePhoto": "营业执照",
|
||||
"otherLcenses": "其他协议",
|
||||
"userAgreement": "用户协议",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"aiPolicy": "《AI功能使用规范》",
|
||||
"applyPermission": "申请使用权限"
|
||||
"applyPermission": "申请使用权限",
|
||||
"qualification": "资质证照"
|
||||
},
|
||||
"permission": {
|
||||
"permissionManagement": "权限管理设置",
|
||||
"pushNotification": "推送权限",
|
||||
"galleryAccess": "相册权限",
|
||||
"locationPermission": "位置权限",
|
||||
"personalizedRecommendation": "个性化推荐设置"
|
||||
},
|
||||
"generalSetting": {
|
||||
"title": "通用设置",
|
||||
@ -50,6 +58,30 @@
|
||||
"galleryAccess": "相册权限",
|
||||
"personalizedRecommendation": "个性化推荐设置",
|
||||
"deleteAccount": "注销账号",
|
||||
"logout": "退出登录"
|
||||
"logout": "退出登录",
|
||||
"upgrade": "升级",
|
||||
"subscription": "订阅",
|
||||
"subscriptionTitle": "MemoWake Premium",
|
||||
"subscriptionText": "Unlock more of what you love",
|
||||
"otherInformation": "其他信息",
|
||||
"contactUs": "联系客服",
|
||||
"version": "版本号",
|
||||
"cleanCache": "清理缓存",
|
||||
"allTitle": "设置",
|
||||
"album": "图库",
|
||||
"shareProfile": "分享个人资料",
|
||||
"classify": "分类",
|
||||
"confirm": "确定",
|
||||
"location": "地区选择",
|
||||
"rank": "Top Memory Makers",
|
||||
"userId": "User ID",
|
||||
"usedStorage": "已使用存储",
|
||||
"remainingPoints": "剩余积分",
|
||||
"totalVideo": "视频总量",
|
||||
"totalPhoto": "照片总量",
|
||||
"live": "动图",
|
||||
"videoLength": "视频时长",
|
||||
"storiesCreated": "创作视频",
|
||||
"conversationsWithMemo": "Memo对话"
|
||||
}
|
||||
}
|
||||
610
package-lock.json
generated
@ -24,16 +24,18 @@
|
||||
"expo-blur": "~14.1.5",
|
||||
"expo-constants": "~17.1.6",
|
||||
"expo-dev-client": "~5.2.1",
|
||||
"expo-device": "~7.1.4",
|
||||
"expo-file-system": "~18.1.10",
|
||||
"expo-font": "~13.3.1",
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image": "~2.3.0",
|
||||
"expo-image": "~2.3.2",
|
||||
"expo-image-manipulator": "~13.1.7",
|
||||
"expo-image-picker": "~16.1.4",
|
||||
"expo-linking": "~7.1.5",
|
||||
"expo-localization": "^16.1.5",
|
||||
"expo-location": "~18.1.5",
|
||||
"expo-media-library": "~17.1.7",
|
||||
"expo-notifications": "~0.31.4",
|
||||
"expo-router": "~5.1.0",
|
||||
"expo-secure-store": "~14.2.3",
|
||||
"expo-splash-screen": "~0.30.9",
|
||||
@ -53,8 +55,11 @@
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-native": "0.79.4",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-modal": "^14.0.0-rc.1",
|
||||
"react-native-picker-select": "^9.3.1",
|
||||
"react-native-progress": "^5.0.1",
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-render-html": "^6.3.4",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "~4.11.1",
|
||||
"react-native-svg": "15.11.2",
|
||||
|
||||
@ -13,7 +13,7 @@ fi
|
||||
|
||||
# 分支名到端口映射
|
||||
declare -A PORT_MAP
|
||||
PORT_MAP[v0.4.0_front]="10280:80"
|
||||
PORT_MAP[v0.5.0]="10280:80"
|
||||
PORT_MAP[main]="10080:80"
|
||||
|
||||
PORTS=${PORT_MAP[$BRANCH_NAME]}
|
||||
|
||||
@ -20,6 +20,10 @@ module.exports = {
|
||||
buttonFill: '#E2793F',
|
||||
aiBubble: '#FFF8DE',
|
||||
},
|
||||
width: {
|
||||
'owner-card': 'calc((100% - 1rem)/3)',
|
||||
'self': 'calc(100% + 32px)',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
import { FileStatus } from "@/components/file-upload/file-uploader";
|
||||
import { MediaType } from "expo-image-picker";
|
||||
import { ReactNode } from "react";
|
||||
import { StyleProp, ViewStyle } from "react-native";
|
||||
|
||||
export interface FileStatus {
|
||||
file: File;
|
||||
status: 'pending' | 'uploading' | 'success' | 'error';
|
||||
progress: number;
|
||||
error?: string;
|
||||
}
|
||||
export interface MaterialFile {
|
||||
id: string;
|
||||
file_name: string;
|
||||
|
||||
@ -11,3 +11,95 @@ export interface User {
|
||||
refresh_token?: string
|
||||
avatar_file_url?: string
|
||||
}
|
||||
|
||||
interface UserCountData {
|
||||
video_count: number,
|
||||
photo_count: number,
|
||||
live_count: number,
|
||||
video_length: number,
|
||||
cover_url: string | null
|
||||
}
|
||||
export interface CountData {
|
||||
used_bytes: number;
|
||||
total_bytes: number;
|
||||
counter: {
|
||||
user_id: number;
|
||||
total_count: UserCountData,
|
||||
category_count: {
|
||||
[key: string]: UserCountData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Counter {
|
||||
user_id: number,
|
||||
total_count: UserCountData,
|
||||
category_count: {
|
||||
[key: string]: UserCountData
|
||||
}
|
||||
}
|
||||
export interface TitleRankings {
|
||||
display_name: string,
|
||||
ranking: number,
|
||||
value: number,
|
||||
material_type: string,
|
||||
user_id: string,
|
||||
region: string
|
||||
}
|
||||
export interface UserInfoDetails {
|
||||
material_counter: Counter,
|
||||
user_info: User,
|
||||
stories_count: number,
|
||||
conversations_count: number,
|
||||
remain_points: number,
|
||||
total_points: number,
|
||||
title_rankings: TitleRankings[],
|
||||
medal_infos: {
|
||||
"id": number,
|
||||
"url": string
|
||||
}[],
|
||||
membership_level: string
|
||||
}
|
||||
|
||||
|
||||
export type SourceItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
display_name: string;
|
||||
statistical_granularity: 'Count' | 'Duration';
|
||||
};
|
||||
|
||||
export type GroupedData = {
|
||||
[key: string]: SourceItem[];
|
||||
};
|
||||
|
||||
export type TargetItem = {
|
||||
name: string;
|
||||
children: { name: string, value: number }[];
|
||||
};
|
||||
|
||||
export type RankingItem = {
|
||||
display_name: string
|
||||
material_type: string
|
||||
ranking: number
|
||||
region: string
|
||||
user_avatar_url: string
|
||||
user_id: string
|
||||
value: number
|
||||
user_nick_name: string
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
city: string;
|
||||
country: string;
|
||||
district: string;
|
||||
formattedAddress: string;
|
||||
isoCountryCode: string;
|
||||
name: string;
|
||||
postalCode: string | null;
|
||||
region: string;
|
||||
street: string;
|
||||
streetNumber: string;
|
||||
subregion: string | null;
|
||||
timezone: string | null;
|
||||
}
|
||||