diff --git a/app.json b/app.json index 3d3bfb3..4f5d0c5 100644 --- a/app.json +++ b/app.json @@ -46,7 +46,17 @@ "plugins": [ "expo-router", "expo-secure-store", - [ + [ + "expo-font", + { + "fonts": [ + "./assets/fonts/Quicksand.otf", + "./assets/fonts/SF-Pro.otf", + "./assets/fonts/Inter-Regular.otf" + ] + } + ], + [ "expo-background-task", { "minimumInterval": 15 diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 5e4c871..54c5796 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -9,13 +9,16 @@ import { prefetchChats } from '@/lib/prefetch'; import { fetchApi } from '@/lib/server-api-util'; import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util'; import { TransitionPresets } from '@react-navigation/bottom-tabs'; +import { useFonts } from 'expo-font'; import * as Notifications from 'expo-notifications'; import { Tabs } from 'expo-router'; import * as SecureStore from 'expo-secure-store'; +import * as SplashScreen from 'expo-splash-screen'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Platform } from 'react-native'; +SplashScreen.preventAutoHideAsync(); interface PollingData { title: string; id: string; @@ -55,6 +58,19 @@ export default function TabLayout() { }); }; + // 加载字体 + const [loaded, error] = useFonts({ + quicksand: require('@/assets/fonts/Quicksand.otf'), + sfPro: require('@/assets/fonts/SF-Pro.otf'), + inter: require('@/assets/fonts/Inter-Regular.otf'), + }); + + useEffect(() => { + if (loaded || error) { + SplashScreen.hideAsync(); + } + }, [loaded, error]); + // 监听通知点击事件 useEffect(() => { const notificationListener = Notifications.addNotificationResponseReceivedListener(response => { diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx index 3ac0086..95e078d 100644 --- a/app/(tabs)/ask.tsx +++ b/app/(tabs)/ask.tsx @@ -57,6 +57,7 @@ export default function AskScreen() { if (translationX > threshold) { // 从左向右滑动,跳转页面 runOnJS(router.replace)("/memo-list"); + runOnJS(setConversationId)("") } }) .minPointers(1) @@ -244,11 +245,12 @@ export default function AskScreen() { useFocusEffect( useCallback(() => { + Keyboard.dismiss(); if (!sessionId) { setIsHello(true); setUserMessages([]) } - }, [sessionId]) + }, [sessionId, Keyboard]) ); return ( @@ -329,6 +331,7 @@ export default function AskScreen() { setUserMessages={setUserMessages} selectedImages={selectedImages} setSelectedImages={setSelectedImages} + isHello={isHello} /> diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index ac7cdaf..f766889 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,291 +1,183 @@ +import { Fonts } from '@/constants/Fonts'; import { checkAuthStatus } from '@/lib/auth'; import { useRouter } from 'expo-router'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Animated, Dimensions, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { Dimensions, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import Animated, { + interpolate, + useAnimatedStyle, + useSharedValue, + withDelay, + withRepeat, + withSequence, + withTiming +} from 'react-native-reanimated'; import { useSafeAreaInsets } from "react-native-safe-area-context"; +// Worklet function for animations +const runShakeAnimation = (value: Animated.SharedValue) => { + 'worklet'; + return withRepeat( + withSequence( + withTiming(1, { duration: 300 }), + withTiming(-1, { duration: 300 }), + withTiming(1, { duration: 300 }), + withTiming(-1, { duration: 300 }), + withTiming(0, { duration: 200 }), + withDelay(1000, withTiming(0, { duration: 0 })) + ), + -1 + ); +}; + +const runWaveAnimation = (value: Animated.SharedValue) => { + 'worklet'; + return withRepeat( + withSequence( + withTiming(1, { duration: 500 }), + withTiming(-1, { duration: 500 }), + withTiming(0, { duration: 500 }), + withDelay(1000, withTiming(0, { duration: 0 })) + ), + -1 + ); +}; + export default function HomeScreen() { const router = useRouter(); const { t } = useTranslation(); const insets = useSafeAreaInsets(); - const [isLoading, setIsLoading] = useState(false); - - // 获取屏幕宽度 + const [isLoading, setIsLoading] = React.useState(false); const screenWidth = Dimensions.get('window').width; - // 动画值 - const fadeAnim = useRef(new Animated.Value(0)).current; // IP图标的淡入动画 - const shakeAnim = useRef(new Animated.Value(0)).current; // IP图标的摇晃动画 - const animationRef = useRef(null); // 动画引用 - const descriptionAnim = useRef(new Animated.Value(0)).current; // 描述文本的淡入动画 - const buttonAnim = useRef(new Animated.Value(0)).current; // 按钮的淡入动画 - const buttonShakeAnim = useRef(new Animated.Value(0)).current; // 按钮的摇晃动画 - const buttonLoopAnim = useRef(null); // 按钮循环动画引用 - const fadeInAnim = useRef(new Animated.Value(0)).current; + // Animation values + const fadeAnim = useSharedValue(0); + const shakeAnim = useSharedValue(0); + const waveAnim = useSharedValue(0); + const buttonShakeAnim = useSharedValue(0); + const fadeInAnim = useSharedValue(0); + const descriptionAnim = useSharedValue(0); + const textAnimations = { + line1: useSharedValue(0), + line2: useSharedValue(0), + line3: useSharedValue(0), + subtitle: useSharedValue(0), + }; - // 文本行动画值 - const [textAnimations] = useState(() => ({ - line1: new Animated.Value(0), // 第一行文本动画 - line2: new Animated.Value(0), // 第二行文本动画 - line3: new Animated.Value(0), // 第三行文本动画 - subtitle: new Animated.Value(0), // 副标题动画 + // Animation styles + const ipAnimatedStyle = useAnimatedStyle(() => ({ + opacity: fadeAnim.value, + transform: [ + { translateX: interpolate(shakeAnim.value, [-1, 1], [-2, 2]) }, + { rotate: `${interpolate(shakeAnim.value, [-1, 1], [-2, 2])}deg` }, + ], })); - // 添加挥手动画值 - const waveAnim = useRef(new Animated.Value(0)).current; + const waveAnimatedStyle = useAnimatedStyle(() => ({ + transform: [ + { rotate: `${interpolate(waveAnim.value, [-1, 0, 1], [-15, 0, 15])}deg` }, + ], + })); - // 启动IP图标摇晃动画 - const startShaking = () => { - // 停止任何正在进行的动画 - if (animationRef.current) { - animationRef.current.stop(); - } + const buttonStyle = useAnimatedStyle(() => ({ + opacity: fadeInAnim.value, + transform: [ + { translateY: interpolate(fadeInAnim.value, [0, 1], [20, 0]) }, + { translateX: interpolate(buttonShakeAnim.value, [-1, 0, 1], [-5, 0, 5]) } + ] + })); - // 创建动画序列 - const sequence = Animated.sequence([ - // 第一次左右摇晃 - Animated.timing(shakeAnim, { - toValue: 1, - duration: 300, - useNativeDriver: true, - }), - Animated.timing(shakeAnim, { - toValue: -1, - duration: 300, - useNativeDriver: true, - }), - // 第二次左右摇晃 - Animated.timing(shakeAnim, { - toValue: 1, - duration: 300, - useNativeDriver: true, - }), - Animated.timing(shakeAnim, { - toValue: -1, - duration: 300, - useNativeDriver: true, - }), - // 回到中心位置 - Animated.timing(shakeAnim, { - toValue: 0, - duration: 200, - useNativeDriver: true, - }), - // 1秒延迟 - Animated.delay(1000), - ]); + const welcomeStyle = useAnimatedStyle(() => ({ + opacity: fadeInAnim.value, + transform: [{ translateY: interpolate(fadeInAnim.value, [0, 1], [20, 0]) }] + })); - // 循环播放动画序列 - animationRef.current = Animated.loop(sequence); - animationRef.current.start(); - }; + const descriptionStyle = useAnimatedStyle(() => ({ + opacity: descriptionAnim.value, + transform: [{ translateY: interpolate(descriptionAnim.value, [0, 1], [20, 0]) }] + })); - // 启动文本动画 - const startTextAnimations = () => { - // 按顺序延迟启动每行文本动画 - return new Promise((resolve) => { - Animated.stagger(300, [ - Animated.timing(textAnimations.line1, { - toValue: 1, - duration: 500, - useNativeDriver: true, - }), - Animated.timing(textAnimations.line2, { - toValue: 1, - duration: 500, - useNativeDriver: true, - }), - Animated.timing(textAnimations.line3, { - toValue: 1, - duration: 500, - useNativeDriver: true, - }), - Animated.timing(textAnimations.subtitle, { - toValue: 1, - duration: 500, - useNativeDriver: true, - }), - ]).start(() => resolve()); - }); - }; + const textLine1Style = useAnimatedStyle(() => ({ + opacity: textAnimations.line1.value, + transform: [{ translateY: interpolate(textAnimations.line1.value, [0, 1], [10, 0]) }] + })); - // 启动描述文本动画 - const startDescriptionAnimation = () => { - // IP图标显示后淡入描述文本 - return new Promise((resolve) => { - Animated.sequence([ - Animated.delay(200), // IP图标显示后延迟200ms - Animated.timing(descriptionAnim, { - toValue: 1, - duration: 800, - useNativeDriver: true, - }) - ]).start(() => resolve()); - }); - }; - // 启动欢迎语动画 - const startWelcomeAnimation = () => { - // IP图标显示后淡入描述文本 - return new Promise((resolve) => { - Animated.sequence([ - Animated.delay(200), // IP图标显示后延迟200ms - Animated.timing(fadeInAnim, { - toValue: 1, - duration: 800, - useNativeDriver: true, - }) - ]).start(() => resolve()); - }); - }; + const textLine2Style = useAnimatedStyle(() => ({ + opacity: textAnimations.line2.value, + transform: [{ translateY: interpolate(textAnimations.line2.value, [0, 1], [10, 0]) }] + })); - // 启动按钮动画 - const startButtonAnimation = () => { - // 首先淡入按钮 - Animated.sequence([ - Animated.timing(buttonAnim, { - toValue: 1, - duration: 800, - useNativeDriver: true, - }) - ]).start(() => { - // 淡入完成后开始循环摇晃动画 - startButtonShakeLoop(); - }); - }; + const textLine3Style = useAnimatedStyle(() => ({ + opacity: textAnimations.line3.value, + transform: [{ translateY: interpolate(textAnimations.line3.value, [0, 1], [10, 0]) }] + })); - // 启动按钮循环摇晃动画 - const startButtonShakeLoop = () => { - // 停止任何正在进行的动画 - if (buttonLoopAnim.current) { - buttonLoopAnim.current.stop(); - } + const subtitleStyle = useAnimatedStyle(() => ({ + opacity: textAnimations.subtitle.value, + transform: [{ translateY: interpolate(textAnimations.subtitle.value, [0, 1], [10, 0]) }] + })); - // 创建摇晃动画序列 - const shakeSequence = Animated.sequence([ - // 向右摇晃 - Animated.timing(buttonShakeAnim, { - toValue: 1, - duration: 100, - useNativeDriver: true, - }), - // 向左摇晃 - Animated.timing(buttonShakeAnim, { - toValue: -1, - duration: 100, - useNativeDriver: true, - }), - // 再次向右摇晃 - Animated.timing(buttonShakeAnim, { - toValue: 1, - duration: 100, - useNativeDriver: true, - }), - // 回到中心位置 - Animated.timing(buttonShakeAnim, { - toValue: 0, - duration: 100, - useNativeDriver: true, - }), - // 暂停3秒 - Animated.delay(3000) - ]); - - // 循环播放动画序列 - buttonLoopAnim.current = Animated.loop(shakeSequence); - buttonLoopAnim.current.start(); - }; - - // 启动挥手动画 - const startWaveAnimation = () => { - // 创建循环动画:左右摇摆 - Animated.loop( - Animated.sequence([ - Animated.timing(waveAnim, { - toValue: 1, - duration: 500, - useNativeDriver: true, - }), - Animated.timing(waveAnim, { - toValue: -1, - duration: 500, - useNativeDriver: true, - }), - Animated.timing(waveAnim, { - toValue: 0, - duration: 500, - useNativeDriver: true, - }), - Animated.delay(1000), // 暂停1秒 - ]) - ).start(); - }; - - // 组件挂载时启动动画 + // Start animations useEffect(() => { setIsLoading(true); + checkAuthStatus(router, () => { - router.replace('/ask') + router.replace('/ask'); }, false).then(() => { setIsLoading(false); }).catch(() => { setIsLoading(false); }); - // IP图标的淡入动画 - Animated.timing(fadeAnim, { - toValue: 1, - duration: 1000, - useNativeDriver: true, - }).start(() => { - // 淡入完成后开始摇晃动画 - startShaking(); - // IP显示后开始文本动画 - startTextAnimations() - .then(() => startWelcomeAnimation()) - .then(() => startDescriptionAnimation()) - .then(() => startButtonAnimation()) - .catch(console.error); - // 启动挥手动画 - startWaveAnimation(); + + // Start fade in animation + fadeAnim.value = withTiming(1, { duration: 1000 }, () => { + // Start shake animation + shakeAnim.value = runShakeAnimation(shakeAnim); + + // Start text animations with delays + textAnimations.line1.value = withDelay(0, withTiming(1, { duration: 500 })); + textAnimations.line2.value = withDelay(300, withTiming(1, { duration: 500 })); + textAnimations.line3.value = withDelay(600, withTiming(1, { duration: 500 })); + textAnimations.subtitle.value = withDelay(900, withTiming(1, { duration: 500 })); + + // Start welcome animation + fadeInAnim.value = withDelay(200, withTiming(1, { duration: 800 })); + + // Start description animation + descriptionAnim.value = withDelay(200, withTiming(1, { duration: 800 })); + + // Start button animation + fadeInAnim.value = withDelay(200, withTiming(1, { duration: 800 }, () => { + // Start button shake animation + buttonShakeAnim.value = withRepeat( + withSequence( + withTiming(1, { duration: 100 }), + withTiming(-1, { duration: 100 }), + withTiming(1, { duration: 100 }), + withTiming(0, { duration: 100 }), + withDelay(3000, withTiming(0, { duration: 0 })) + ), + -1 + ); + })); + + // Start wave animation + waveAnim.value = runWaveAnimation(waveAnim); }); - // 组件卸载时清理动画 + // Cleanup return () => { - if (buttonLoopAnim.current) { - buttonLoopAnim.current.stop(); - } - if (animationRef.current) { - animationRef.current.stop(); - } + fadeAnim.value = 0; + shakeAnim.value = 0; + waveAnim.value = 0; + buttonShakeAnim.value = 0; + fadeInAnim.value = 0; + descriptionAnim.value = 0; + Object.values(textAnimations).forEach(anim => anim.value = 0); }; - }, []); - // 动画样式 - const animatedStyle = { - opacity: fadeAnim, - transform: [ - { - translateX: shakeAnim.interpolate({ - inputRange: [-1, 1], - outputRange: [-2, 2], - }) - }, - { - rotate: shakeAnim.interpolate({ - inputRange: [-1, 1], - outputRange: ['-2deg', '2deg'], - }), - }, - ], - }; - - // 旋转动画插值 - const rotate = waveAnim.interpolate({ - inputRange: [-1, 0, 1], - outputRange: ['-15deg', '0deg', '15deg'], - }); - if (isLoading) { return ( @@ -297,86 +189,27 @@ export default function HomeScreen() { return ( - {/* 标题区域 */} - + {t('auth.welcomeAwaken.awaken', { ns: 'login' })} - + {t('auth.welcomeAwaken.your', { ns: 'login' })} - + {t('auth.welcomeAwaken.pm', { ns: 'login' })} - + {t('auth.welcomeAwaken.slogan', { ns: 'login' })} - {/* 欢迎语 */} + - + - {/* Animated IP */} - + - {/* 介绍文本 */} - + {t('auth.welcomeAwaken.gallery', { ns: 'login' })} {"\n"} {t('auth.welcomeAwaken.back', { ns: 'login' })} - {/* 唤醒按钮 */} - + { - router.push('/login'); - }} + onPress={() => router.push('/login')} activeOpacity={0.8} > @@ -460,6 +256,7 @@ const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FFB645', + fontFamily: 'english' }, loadingContainer: { flex: 1, @@ -484,17 +281,19 @@ const styles = StyleSheet.create({ }, titleText: { color: '#FFFFFF', - fontSize: 30, + fontSize: 32, fontWeight: 'bold', marginBottom: 12, textAlign: 'left', lineHeight: 36, + fontFamily: Fonts['quicksand'] }, subtitleText: { color: 'rgba(255, 255, 255, 0.85)', fontSize: 16, textAlign: 'left', lineHeight: 24, + fontFamily: Fonts['inter'] }, ipContainer: { alignItems: 'center', @@ -513,11 +312,12 @@ const styles = StyleSheet.create({ opacity: 0.9, paddingHorizontal: 40, marginTop: -16, + fontFamily: Fonts['inter'] }, awakenButton: { backgroundColor: '#FFFFFF', borderRadius: 28, - paddingVertical: 16, + paddingVertical: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, @@ -531,5 +331,6 @@ const styles = StyleSheet.create({ color: '#4C320C', fontWeight: 'bold', fontSize: 18, + fontFamily: Fonts['quicksand'] }, }); \ No newline at end of file diff --git a/app/(tabs)/login.tsx b/app/(tabs)/login.tsx index e332311..46cd102 100644 --- a/app/(tabs)/login.tsx +++ b/app/(tabs)/login.tsx @@ -74,16 +74,16 @@ const LoginScreen = () => { keyboardShouldPersistTaps="handled" bounces={false} > - - - Awake your Memo + + + {t('login:auth.login.titleText')} 0 ? windowHeight - containerHeight - 210 + statusBarHeight - insets.top - 28 : 0, - transform: [{ translateX: -200 }, { translateY: keyboardOffset > 0 ? -keyboardOffset + statusBarHeight - insets.top - 28 : -keyboardOffset }] + transform: [{ translateX: -200 }, { translateY: keyboardOffset > 0 ? -keyboardOffset + statusBarHeight : -keyboardOffset }] }} > { @@ -98,14 +98,15 @@ const LoginScreen = () => { className="absolute left-1/2 z-[1000] -translate-x-[39.5px] -translate-y-[4px]" style={{ top: containerHeight > 0 ? windowHeight - containerHeight - 1 + statusBarHeight - insets.top - 30 : 0, - transform: [{ translateX: -39.5 }, { translateY: keyboardOffset > 0 ? -4 - keyboardOffset + statusBarHeight - insets.top - 30 : -4 - keyboardOffset }] + transform: [{ translateX: -39.5 }, { translateY: keyboardOffset > 0 ? -4 - keyboardOffset + statusBarHeight : -4 - keyboardOffset }] }} > { > {/* 错误提示 */} - + {error} @@ -161,22 +162,22 @@ const LoginScreen = () => { return components[status as keyof typeof components] || components.login; })()} - + {status == 'login' || !status && - + {status === 'login' || !status ? t('auth.agree.logintext', { ns: 'login' }) : t('auth.agree.singupText', { ns: 'login' })} { setModalVisible(true); setModalType('terms') }}> - + {t('auth.agree.terms', { ns: 'login' })} - + {t('auth.agree.join', { ns: 'login' })} { setModalVisible(true); setModalType('privacy') }}> - + {t('auth.agree.privacyPolicy', { ns: 'login' })} diff --git a/app/(tabs)/memo-list.tsx b/app/(tabs)/memo-list.tsx index d2e26db..4b1282c 100644 --- a/app/(tabs)/memo-list.tsx +++ b/app/(tabs)/memo-list.tsx @@ -13,6 +13,7 @@ import UploaderProgress from '@/components/file-upload/upload-progress/uploader- import SkeletonItem from '@/components/memo/SkeletonItem'; // 类型定义 +import { Fonts } from '@/constants/Fonts'; import { useUploadManager } from '@/hooks/useUploadManager'; import { getCachedData, prefetchChatDetail, prefetchChats } from '@/lib/prefetch'; import { fetchApi } from '@/lib/server-api-util'; @@ -227,7 +228,7 @@ const MemoList = () => { return ( - + {/* 会员卡 */} - + {/* 分类 */} - + {/* 作品数据 */} - } number={userInfoDetails.stories_count} /> - } number={userInfoDetails.conversations_count} /> + } number={userInfoDetails.stories_count} /> + } number={userInfoDetails.conversations_count} /> {/* 排行榜 */} diff --git a/app/(tabs)/rights.tsx b/app/(tabs)/rights.tsx index ed2c1d7..2b99bf1 100644 --- a/app/(tabs)/rights.tsx +++ b/app/(tabs)/rights.tsx @@ -37,9 +37,10 @@ export default function Rights() { requestProducts, ErrorCode } = useIAP(); - const { pro } = useLocalSearchParams<{ + const { points, pro } = useLocalSearchParams<{ credit: string; - pro: string; + points: string; + pro: string }>(); // 用户勾选协议 const [agree, setAgree] = useState(false); @@ -215,9 +216,9 @@ export default function Rights() { {/* 会员卡 */} {userType === 'normal' ? ( - + ) : ( - + )} @@ -226,7 +227,7 @@ export default function Rights() { - {pro} + {points} @@ -266,8 +267,6 @@ export default function Rights() { padding: 16, paddingBottom: 32, backgroundColor: '#fff', - borderTopWidth: 1, - borderTopColor: '#eee', position: 'absolute', bottom: 0, left: 0, @@ -303,8 +302,8 @@ export default function Rights() { }} activeOpacity={0.8} > - - {t('rights.subscribe', { ns: 'personal' })} + + {t('rights.subscribe', { ns: 'personal' })} {payType?.split('_')[payType?.split('_')?.length - 1]} @@ -365,8 +364,8 @@ const styles = StyleSheet.create({ }, goPay: { backgroundColor: '#E2793F', - borderRadius: 24, - paddingVertical: 10, + borderRadius: 32, + paddingVertical: 16, display: "flex", alignItems: "center", width: "100%", @@ -379,9 +378,9 @@ const styles = StyleSheet.create({ marginBottom: 16 }, switchButtonItem: { - width: "48%", + width: "47%", borderRadius: 24, - paddingVertical: 6, + paddingVertical: 8, display: "flex", alignItems: "center", borderWidth: 1 @@ -390,7 +389,7 @@ const styles = StyleSheet.create({ marginHorizontal: 16, marginVertical: 16, padding: 16, - borderRadius: 12, + borderRadius: 32, backgroundColor: '#fff', shadowColor: '#000', shadowOffset: { @@ -421,7 +420,7 @@ const styles = StyleSheet.create({ marginHorizontal: 16, marginVertical: 16, backgroundColor: '#FFB645', - borderRadius: 12, + borderRadius: 32, }, cardContent: { position: 'absolute', @@ -440,7 +439,7 @@ const styles = StyleSheet.create({ fontWeight: '700', color: '#E2793F', backgroundColor: '#fff', - paddingHorizontal: 8, + paddingHorizontal: 16, paddingVertical: 2, borderRadius: 20, textAlign: 'center', @@ -450,12 +449,12 @@ const styles = StyleSheet.create({ display: 'flex', flexDirection: 'row', alignItems: 'center', - gap: 4 + gap: 8 }, cardPointsText: { fontSize: 32, fontWeight: '700', color: '#4C320C', - lineHeight: 32 + lineHeight: 36 } }); \ No newline at end of file diff --git a/app/(tabs)/setting.tsx b/app/(tabs)/setting.tsx index dcfb7d2..22e9bfa 100644 --- a/app/(tabs)/setting.tsx +++ b/app/(tabs)/setting.tsx @@ -11,7 +11,7 @@ import { checkNotificationPermission, getLocationPermission, getPermissions, req import { ThemedText } from '@/components/ThemedText'; import { useAuth } from '@/contexts/auth-context'; import { fetchApi } from '@/lib/server-api-util'; -import { Address, User, UserInfoDetails } from '@/types/user'; +import { Address, User } from '@/types/user'; import * as Location from 'expo-location'; import { useFocusEffect, useRouter } from 'expo-router'; import * as SecureStore from 'expo-secure-store'; @@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next'; import { Linking, Platform, Pressable, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from "react-native-safe-area-context"; -const Setting = (props: { userInfo: UserInfoDetails }) => { +const Setting = () => { const [userInfo, setUserInfo] = useState(null); const getUserInfo = async () => { @@ -210,7 +210,7 @@ const Setting = (props: { userInfo: UserInfoDetails }) => { }, []) return ( - + @@ -221,7 +221,7 @@ const Setting = (props: { userInfo: UserInfoDetails }) => { { router.push('/owner') }}> - {t('generalSetting.allTitle', { ns: 'personal' })} + {t('generalSetting.allTitle', { ns: 'personal' })} × @@ -269,11 +269,11 @@ const Setting = (props: { userInfo: UserInfoDetails }) => { */} {/* 权限信息 */} - {t('permission.permissionManagement', { ns: 'personal' })} + {t('permission.permissionManagement', { ns: 'personal' })} {/* 相册权限 */} - {t('permission.galleryAccess', { ns: 'personal' })} + {t('permission.galleryAccess', { ns: 'personal' })} { {/* 位置权限 */} - {t('permission.locationPermission', { ns: 'personal' })} + {t('permission.locationPermission', { ns: 'personal' })} { - {t('permission.pushNotification', { ns: 'personal' })} + {t('permission.pushNotification', { ns: 'personal' })} { */} {/* 协议 */} - {t('lcenses.title', { ns: 'personal' })} + {t('lcenses.title', { ns: 'personal' })} { setModalType('privacy'); setPrivacyModalVisible(true) }} > - {t('lcenses.privacyPolicy', { ns: 'personal' })} + {t('lcenses.privacyPolicy', { ns: 'personal' })} { setModalType('terms'); setPrivacyModalVisible(true) }} > - {t('lcenses.applyPermission', { ns: 'personal' })} + {t('lcenses.applyPermission', { ns: 'personal' })} { setModalType('user'); setPrivacyModalVisible(true) }} > - {t('lcenses.userAgreement', { ns: 'personal' })} + {t('lcenses.userAgreement', { ns: 'personal' })} { setModalType('ai'); setPrivacyModalVisible(true) }} > - {t('lcenses.aiPolicy', { ns: 'personal' })} + {t('lcenses.aiPolicy', { ns: 'personal' })} { setLcensesModalVisible(true) }} > - {t('lcenses.qualification', { ns: 'personal' })} + {t('lcenses.qualification', { ns: 'personal' })} Linking.openURL("https://beian.miit.gov.cn/")} > - {t('lcenses.ICP', { ns: 'personal' })}沪ICP备2025133004号-2A + {t('lcenses.ICP', { ns: 'personal' })}沪ICP备2025133004号-2A {/* 其他信息 */} - {t('generalSetting.otherInformation', { ns: 'personal' })} + {t('generalSetting.otherInformation', { ns: 'personal' })} Linking.openURL("https://work.weixin.qq.com/kfid/kfca0ac87f4e05e8bfd")} > - {t('generalSetting.contactUs', { ns: 'personal' })} + {t('generalSetting.contactUs', { ns: 'personal' })} {/* */} - {t('generalSetting.version', { ns: 'personal' })} + {t('generalSetting.version', { ns: 'personal' })} {"0.5.0"} {/* 退出 */} - {t('generalSetting.logout', { ns: 'personal' })} + {t('generalSetting.logout', { ns: 'personal' })} {/* 注销账号 */} setDeleteModalVisible(true)}> - {t('generalSetting.deleteAccount', { ns: 'personal' })} + {t('generalSetting.deleteAccount', { ns: 'personal' })} @@ -405,14 +405,11 @@ const Setting = (props: { userInfo: UserInfoDetails }) => { const styles = StyleSheet.create({ centeredView: { - flex: 1, - justifyContent: 'flex-end', - backgroundColor: 'rgba(0,0,0,0.5)', + flex: 1 }, modalView: { width: '100%', height: '100%', - backgroundColor: 'white', paddingHorizontal: 16, }, modalHeader: { @@ -440,7 +437,8 @@ const styles = StyleSheet.create({ }, premium: { backgroundColor: "#FAF9F6", - padding: 16, + paddingHorizontal: 16, + paddingVertical: 20, borderRadius: 24, flexDirection: 'row', justifyContent: 'space-between', @@ -452,11 +450,11 @@ const styles = StyleSheet.create({ gap: 4, backgroundColor: '#FAF9F6', borderRadius: 24, - paddingVertical: 8 + padding: 8 }, item: { paddingHorizontal: 16, - paddingVertical: 8, + paddingVertical: 12, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', diff --git a/app/(tabs)/top.tsx b/app/(tabs)/top.tsx index 8916bde..f4d9008 100644 --- a/app/(tabs)/top.tsx +++ b/app/(tabs)/top.tsx @@ -1,5 +1,7 @@ -import ArrowSvg from '@/assets/icons/svg/arrow.svg'; +import DownSvg from '@/assets/icons/svg/down.svg'; +import PlaceSvg from '@/assets/icons/svg/place.svg'; import ReturnArrowSvg from '@/assets/icons/svg/returnArrow.svg'; +import SearchSvg from '@/assets/icons/svg/search.svg'; import { CascaderItem } from '@/components/cascader'; import ClassifyModal from '@/components/owner/classify'; import LocationModal from '@/components/owner/location'; @@ -14,7 +16,8 @@ import { GroupedData, RankingItem, TargetItem } from '@/types/user'; import { useRouter } from "expo-router"; import * as SecureStore from 'expo-secure-store'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { LayoutChangeEvent, Platform, StyleSheet, TouchableOpacity, View } from 'react-native'; +import { Keyboard, LayoutChangeEvent, Platform, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native'; +import { TextInput } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from "react-native-safe-area-context"; interface LocationData { id: number; @@ -25,6 +28,14 @@ interface LocationData { export default function OwnerPage() { const insets = useSafeAreaInsets(); const router = useRouter(); + const HOT_CITIES = [ + ['北京', '上海', '广州', '深圳'], + ['杭州', '成都', '乌鲁木齐', '武汉'], + ['西安', '重庆', '西宁', '哈尔滨'], + ['长沙', '南宁', '贵阳', '昆明'] + ]; + // 位置搜索数据 + const [locationSearch, setLocationSearch] = useState(''); // 位置弹窗 const [locationModalVisible, setLocationModalVisible] = useState(false); // 分类弹窗 @@ -161,36 +172,62 @@ export default function OwnerPage() { }; }, [fetchLocationData]); + useEffect(() => { + // console.log(locationData); + + }, [locationSearch]) + return ( - - {/* 导航栏 */} - - { router.push('/owner') }} style={{ padding: 16 }}> - - - - Top Memory Makers - - 123 - - - { setLocationModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}> - 0 ? '#FFB645' : '#4C320C' }}> - {selectedLocation?.length > 0 ? selectedLocation[selectedLocation?.length - 1].name : "地区"} + { + Keyboard.dismiss(); + }}> + + {/* 导航栏 */} + + { router.push('/owner') }} style={{ padding: 16 }}> + + + { setClassifyModalVisible(true) }}> + {selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].name : "分类"} - { + 123 + + + + { setLocationModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}> + + 0 ? '#FFB645' : '#4C320C' }}> + {selectedLocation?.length > 0 ? selectedLocation[selectedLocation?.length - 1].name : "地区"} + + + {/* { selectedLocation?.length > 0 ? : - } - - { setClassifyModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}> + } */} + + + + + + + { + setLocationSearch(text.nativeEvent.text) + }} + /> + + {/* { setClassifyModalVisible(true) }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 }}> 0 ? '#FFB645' : '#4C320C' }}> {selectedClassify?.length > 0 ? selectedClassify[selectedClassify?.length - 1].name : "分类"} @@ -200,30 +237,56 @@ export default function OwnerPage() { : } - - - {/* 颁奖台 */} - - {/* 排名区域 */} - + */} - {/* 地区选择弹窗 */} - - {/* 分类选择弹窗 */} - - + + {/* 热门城市 */} + + 热门城市 + {HOT_CITIES.map((row, rowIndex) => ( + + {row.map((city, cityIndex) => ( + { + setLocationSearch(city); + }} + > + {city} + + ))} + + ))} + + + + {/* 颁奖台 */} + + {/* 排名区域 */} + + + {/* 地区选择弹窗 */} + + {/* 分类选择弹窗 */} + + + ); } @@ -243,5 +306,46 @@ const styles = StyleSheet.create({ fontSize: 20, fontWeight: '700', color: '#4C320C', + }, + searchContainer: { + position: 'relative', + flex: 1, + }, + input: { + backgroundColor: '#D9D9D9', + borderRadius: 24, + paddingLeft: 40, // 给图标留出空间 + paddingVertical: 8, + paddingRight: 16, + fontSize: 14, + lineHeight: 16, + }, + searchIcon: { + position: 'absolute', + left: 20, + top: '50%', + zIndex: 10, + transform: [{ translateY: -6 }] + }, + item: { + paddingVertical: 8, + borderRadius: 12, + fontSize: 14, + backgroundColor: '#D9D9D9', + width: '23%', + textAlign: 'center', + color: "#4C320C" + }, + hotCity: { + backgroundColor: "#FBFBFB", + padding: 16, + borderRadius: 24 + }, + cityRow: { + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'space-between', + marginBottom: 16 } }); \ No newline at end of file diff --git a/assets/fonts/Inter-Regular.otf b/assets/fonts/Inter-Regular.otf new file mode 100644 index 0000000..fdb121d Binary files /dev/null and b/assets/fonts/Inter-Regular.otf differ diff --git a/assets/fonts/Quicksand.otf b/assets/fonts/Quicksand.otf new file mode 100644 index 0000000..4f371fa Binary files /dev/null and b/assets/fonts/Quicksand.otf differ diff --git a/assets/fonts/SF-Pro.otf b/assets/fonts/SF-Pro.otf new file mode 100644 index 0000000..025b25c Binary files /dev/null and b/assets/fonts/SF-Pro.otf differ diff --git a/assets/icons/svg/down.svg b/assets/icons/svg/down.svg new file mode 100644 index 0000000..14b3c88 --- /dev/null +++ b/assets/icons/svg/down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/place.svg b/assets/icons/svg/place.svg new file mode 100644 index 0000000..3f63289 --- /dev/null +++ b/assets/icons/svg/place.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/svg/proIcon.svg b/assets/icons/svg/proIcon.svg new file mode 100644 index 0000000..8604de2 --- /dev/null +++ b/assets/icons/svg/proIcon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/svg/search.svg b/assets/icons/svg/search.svg new file mode 100644 index 0000000..ac15e3f --- /dev/null +++ b/assets/icons/svg/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/png/icon/ip.png b/assets/images/png/icon/ip.png index 2355ed9..04e7239 100644 Binary files a/assets/images/png/icon/ip.png and b/assets/images/png/icon/ip.png differ diff --git a/assets/images/png/owner/ask.png b/assets/images/png/owner/ask.png index 0fe206a..34ce9cd 100644 Binary files a/assets/images/png/owner/ask.png and b/assets/images/png/owner/ask.png differ diff --git a/assets/images/png/owner/proIcon.png b/assets/images/png/owner/proIcon.png new file mode 100644 index 0000000..f0279d6 Binary files /dev/null and b/assets/images/png/owner/proIcon.png differ diff --git a/components/ThemedText.tsx b/components/ThemedText.tsx index 9d214a2..99a618e 100644 --- a/components/ThemedText.tsx +++ b/components/ThemedText.tsx @@ -1,31 +1,79 @@ -import { StyleSheet, Text, type TextProps } from 'react-native'; +import { StyleProp, StyleSheet, Text, TextStyle, type TextProps } from 'react-native'; +import { Colors } from '@/constants/Colors'; +import { FontColor, Fonts, FontSize, FontWeight } from '@/constants/Fonts'; import { useThemeColor } from '@/hooks/useThemeColor'; +export type ThemeColor = keyof typeof Colors.light & keyof typeof Colors.dark; +export type ColorValue = `#${string}` | `rgb(${string})` | `rgba(${string})` | string; + export type ThemedTextProps = TextProps & { lightColor?: string; darkColor?: string; - type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'; + type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link' | 'sfPro' | 'inter'; + weight?: FontWeight; + size?: FontSize; + radius?: FontSize + color?: ThemeColor | FontColor | ColorValue; }; +export function isFontColorKey(key: string): key is FontColor { + return ['bgPrimary', 'bgSecondary', 'textPrimary', 'textSecondary', 'textThird', 'textWhite'].includes(key); +} + export function ThemedText({ style, lightColor, darkColor, type = 'default', + weight = 'regular', + size, + radius, + color, ...rest }: ThemedTextProps) { - const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); + + const themeColor = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); + + const textColor = (() => { + if (!color) return themeColor; + + // 检查是否是主题颜色 + const themeColors = Object.keys(Colors.light) as ThemeColor[]; + if (themeColors.includes(color as ThemeColor)) { + return useThemeColor({ light: lightColor, dark: darkColor }, color as ThemeColor); + } + + // 检查是否是 Fonts 中定义的颜色 + if (isFontColorKey(color)) { + return Fonts[color as FontColor]; + } + + // 返回自定义颜色值 + return color; + })(); + + + const baseStyle: StyleProp = { + fontFamily: Fonts.quicksand, + color: textColor, + fontWeight: Number(Fonts[weight as keyof typeof Fonts]) as TextStyle['fontWeight'], + }; return ( ; +export function ThemedView({ className, style, bgColor, ...props }: ThemedViewProps) { + const themeColor = useThemeColor({ light: bgColor, dark: bgColor }, 'background'); + + const bgColorValue = (() => { + if (!bgColor) return themeColor; + + // 检查是否是主题颜色 + const themeColors = Object.keys(Colors.light) as ThemeColor[]; + if (themeColors.includes(bgColor as ThemeColor)) { + return useThemeColor({ light: bgColor, dark: bgColor }, bgColor as ThemeColor); + } + // 检查是否是 Fonts 中定义的颜色 + if (isFontColorKey(bgColor)) { + return Fonts[bgColor]; + } + // 返回自定义颜色值 + return bgColor; + })(); + + return ; } diff --git a/components/ask/chat.tsx b/components/ask/chat.tsx index f5e4b80..f790d66 100644 --- a/components/ask/chat.tsx +++ b/components/ask/chat.tsx @@ -1,9 +1,11 @@ import { ContentPart, Message } from '@/types/ask'; +import { useFocusEffect } from 'expo-router'; import React, { Dispatch, ForwardedRef, forwardRef, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FlatList, FlatListProps, + Keyboard, SafeAreaView, View } from 'react-native'; @@ -52,6 +54,13 @@ function ChatComponent( } }, [userMessages.length]); + useFocusEffect( + useCallback(() => { + Keyboard.dismiss(); + }, [Keyboard, sessionId]) + ); + + const renderMessageItem = useCallback(({ item, index }: { item: Message, index: number }) => { const itemStyle = index === 0 ? { marginTop: 16, marginHorizontal: 16 } : { marginHorizontal: 16 }; return ( diff --git a/components/ask/hello.tsx b/components/ask/hello.tsx index 8f7acba..639548d 100644 --- a/components/ask/hello.tsx +++ b/components/ask/hello.tsx @@ -1,4 +1,5 @@ import { ThemedText } from "@/components/ThemedText"; +import { Fonts } from "@/constants/Fonts"; import { webSocketManager } from "@/lib/websocket-util"; import { Message } from "@/types/ask"; import { Dispatch, SetStateAction } from "react"; @@ -58,15 +59,13 @@ export default function AskHello({ setUserMessages, setConversationId, setIsHell keyboardShouldPersistTaps="handled" > - + {t('ask.hi', { ns: 'ask' })} {"\n"} {t('ask.iAmMemo', { ns: 'ask' })} - - - - + + {t('ask.ready', { ns: 'ask' })} {"\n"} {t('ask.justAsk', { ns: 'ask' })} @@ -112,11 +111,14 @@ const styles = StyleSheet.create({ }, case: { borderWidth: 1, - borderColor: "#AC7E35", + borderColor: Fonts["textPrimary"], borderRadius: 10, paddingHorizontal: 8, + paddingVertical: 2, width: 'auto', - fontSize: 14, - color: "#4C320C" + fontSize: Fonts["sm"], + color: Fonts["textSecondary"], + fontFamily: Fonts["sfPro"] + } }) \ No newline at end of file diff --git a/components/ask/send.tsx b/components/ask/send.tsx index 1f4e291..a2f8c5f 100644 --- a/components/ask/send.tsx +++ b/components/ask/send.tsx @@ -12,6 +12,7 @@ import { View } from 'react-native'; +import { Fonts } from '@/constants/Fonts'; import { webSocketManager, WsMessage } from '@/lib/websocket-util'; import { Message } from '@/types/ask'; import { useTranslation } from 'react-i18next'; @@ -25,11 +26,12 @@ interface Props { setConversationId: (conversationId: string) => void, selectedImages: string[]; setSelectedImages: Dispatch>; + isHello: boolean; } const RENDER_INTERVAL = 50; // 渲染间隔,单位毫秒 export default function SendMessage(props: Props) { - const { setIsHello, conversationId, setUserMessages, setConversationId, selectedImages, setSelectedImages } = props; + const { setIsHello, conversationId, setUserMessages, setConversationId, selectedImages, setSelectedImages, isHello } = props; const { t } = useTranslation() @@ -189,32 +191,33 @@ export default function SendMessage(props: Props) { } ])); let currentSessionId = conversationId; + console.log("currentSessionIdcurrentSessionId", currentSessionId); + // 如果没有对话ID,先创建一个新对话 if (!currentSessionId) { - currentSessionId = await createNewConversation(text); - setConversationId(currentSessionId); - webSocketManager.send({ - type: 'Chat', - session_id: currentSessionId, - message: text, - image_material_ids: selectedImages.length > 0 ? selectedImages : undefined, - }); - setSelectedImages([]); + const newCurrentSessionId = await createNewConversation(text); + if (newCurrentSessionId) { + setConversationId(newCurrentSessionId); + } else { + console.error("无法获取 session_id,消息发送失败1。"); + setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng')); + } } // 通过 WebSocket 发送消息 if (currentSessionId) { - webSocketManager.send({ - type: 'Chat', - session_id: currentSessionId, - message: text, - image_material_ids: selectedImages.length > 0 ? selectedImages : undefined, - }); - setSelectedImages([]); - } else { - console.error("无法获取 session_id,消息发送失败。"); - // 可以在这里处理错误,例如显示一个提示 - setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng')); + try { + webSocketManager.send({ + type: 'Chat', + session_id: currentSessionId, + message: text, + image_material_ids: selectedImages.length > 0 ? selectedImages : undefined, + }); + setSelectedImages([]); + } catch (error) { + console.error("无法获取 session_id,消息发送失败2。", error); + setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng')); + } } // 将输入框清空 setInputValue(''); @@ -243,19 +246,20 @@ export default function SendMessage(props: Props) { return ( - + handleQuitly('search')}> - {t("ask:ask.search")} - handleQuitly('video')}> + {t("ask:ask.search")} + + handleQuitly('video')}> - {t("ask:ask.video")} + {t("ask:ask.video")} { setInputValue(text); @@ -286,28 +290,35 @@ const styles = StyleSheet.create({ margin: 5, borderRadius: 25, alignItems: 'center', - borderWidth: 2, + borderWidth: 1, display: 'flex', flexDirection: 'row', gap: 5, - // backgroundColor: '#F8F8F8' }, container: { justifyContent: 'center', backgroundColor: '#transparent', }, input: { - // borderColor: '#d9d9d9', + color: Fonts["textPrimary"], borderColor: '#AC7E35', borderWidth: 1, - // borderRadius: 18, - borderRadius: 25, + borderRadius: 28, paddingHorizontal: 20, - paddingVertical: 13, + paddingVertical: 16, lineHeight: 20, fontSize: 16, - width: '100%', // 确保输入框宽度撑满 - paddingRight: 50 + width: '100%', + paddingRight: 50, + backgroundColor: '#fff', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.15, + shadowRadius: 3.84, + elevation: 5, }, voiceButton: { padding: 8, diff --git a/components/cascader.tsx b/components/cascader.tsx index 9e0d8b1..ec25ef8 100644 --- a/components/cascader.tsx +++ b/components/cascader.tsx @@ -72,7 +72,7 @@ const CascaderComponent: React.FC = ({ // 渲染某一级选项 const renderLevel = (items: CascaderItem[], level: number) => { return ( - + {items.map((item, index) => { const isActive = selectedItems[level]?.name === item.name; return ( @@ -95,6 +95,7 @@ const CascaderComponent: React.FC = ({ textStyle, isActive && [styles.activeText, activeTextStyle] ]} + type='sfPro' > {item.name} @@ -112,27 +113,46 @@ const CascaderComponent: React.FC = ({ // 渲染所有级联列 const renderColumns = () => { - return allLevelsData.map((items, level) => ( - - { + // 计算每列的宽度 + let width; + if (totalLevels === 1) { + width = '100%'; // 只有一级时占满全部宽度 + } else if (totalLevels === 2) { + width = level === 0 ? '40%' : '60%'; // 两级时第一级40%,第二级60% + } else { + // 三级或以上时,前两级各占30%,其余级别平分剩余40% + if (level < 2) { + width = '30%'; + } else { + const remainingLevels = totalLevels - 2; + width = remainingLevels > 0 ? `${40 / remainingLevels}%` : '40%'; + } + } + + return ( + - {renderLevel(items, level)} - - - )); + + {renderLevel(items, level)} + + + ); + }); }; // 自定义显示内容 @@ -166,15 +186,17 @@ const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', - height: 300, // Set a fixed height for the container + height: 300, }, scrollContent: { flexGrow: 1, height: '100%', + flexDirection: 'row', }, column: { height: '100%', maxHeight: '100%', + flexShrink: 0, }, columnWithDivider: { borderRightWidth: 1, diff --git a/components/chat/message-item/MessageBubble.tsx b/components/chat/message-item/MessageBubble.tsx index d808c4e..0b62cad 100644 --- a/components/chat/message-item/MessageBubble.tsx +++ b/components/chat/message-item/MessageBubble.tsx @@ -26,7 +26,7 @@ const MessageBubble = ({ }: MessageBubbleProps) => { return ( - {/* {item.askAgain && item.askAgain.length > 0 && ( - - {item.askAgain.map((suggestion, index, array) => ( - - {suggestion.text} - - ))} + {/* {item.content instanceof Array && item.content.filter((media: ContentPart) => media.type !== 'text').length > 0 && ( + + { + + }}> + Help me create a warm, cozy video. + + + Help me find materials for subsequent operations. + )} */} - ); }; +const styles = StyleSheet.create({ + tips: { + flexDirection: 'column', + gap: 5, + marginRight: 10, + }, + tip: { + backgroundColor: '#FFF8DE', + paddingHorizontal: 16, + paddingVertical: 8, + }, + tipText: { + color: '#4C320C', + fontSize: 14, + fontFamily: Fonts['inter'] + } +}); + export default React.memo(MessageItem); diff --git a/components/layout/ask.tsx b/components/layout/ask.tsx index ea0a890..1e0d0b1 100644 --- a/components/layout/ask.tsx +++ b/components/layout/ask.tsx @@ -116,18 +116,18 @@ const AskNavbar = ({ wsStatus }: AskNavbarProps) => { bottom: 0, left: 0, right: 0, - height: 80, // Set a fixed height for the navbar + height: 80, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 32, - backgroundColor: 'transparent', // Make sure it's transparent + backgroundColor: 'transparent', }, centerButton: { position: 'absolute', - left: width / 2, - top: -30, // Adjust this value to move the button up or down - marginLeft: -42.5, // Half of the button width (85/2) + left: '50%', + top: -30, + transform: [{ translateX: -17 }], width: 85, height: 85, justifyContent: 'center', @@ -143,8 +143,8 @@ const AskNavbar = ({ wsStatus }: AskNavbarProps) => { }, statusIndicator: { position: 'absolute', - top: 15, - right: 15, + top: 3, + right: 20, width: 10, height: 10, borderRadius: 5, @@ -162,7 +162,7 @@ const AskNavbar = ({ wsStatus }: AskNavbarProps) => { return ( - + navigateTo('/memo-list')} diff --git a/components/login/code.tsx b/components/login/code.tsx index 8d62af9..34e9206 100644 --- a/components/login/code.tsx +++ b/components/login/code.tsx @@ -115,13 +115,13 @@ const Code = ({ phone }: CodeProps) => { - + {t("auth.telLogin.codeTitle", { ns: 'login' })} - + {t("auth.telLogin.secondTitle", { ns: 'login' })} - + {phone} @@ -144,13 +144,13 @@ const Code = ({ phone }: CodeProps) => { /> - + {error} - + {t("auth.telLogin.sendAgain", { ns: 'login' })} { @@ -158,10 +158,16 @@ const Code = ({ phone }: CodeProps) => { sendVerificationCode() } }}> - 0 && styles.disabledResendText - ]}> + 0 && styles.disabledResendText + ]} + size="sm" + color="bgSecondary" + type="inter" + weight="bold" + > {countdown > 0 ? `${countdown}s${t("auth.telLogin.resend", { ns: 'login' })}` : t("auth.telLogin.resend", { ns: 'login' })} @@ -185,23 +191,13 @@ const styles = StyleSheet.create({ marginBottom: 16, }, title: { - fontSize: 24, - fontWeight: '600', marginBottom: 8, paddingTop: 4, - color: '#111827', }, subtitle: { - fontSize: 16, - color: '#4B5563', textAlign: 'center', marginBottom: 4, }, - phoneNumber: { - fontSize: 16, - fontWeight: '500', - color: '#E2793F', - }, otpContainer: { width: '100%', height: 80, @@ -228,9 +224,6 @@ const styles = StyleSheet.create({ alignItems: 'center', }, errorText: { - fontSize: 16, - fontWeight: '500', - color: '#E2793F', marginLeft: 8, }, footerContainer: { @@ -238,12 +231,7 @@ const styles = StyleSheet.create({ justifyContent: 'center', marginTop: 8, }, - footerText: { - color: '#6B7280', - }, resendText: { - color: '#E2793F', - fontWeight: '500', marginLeft: 4, }, disabledResendText: { diff --git a/components/login/forgetPwd.tsx b/components/login/forgetPwd.tsx index da377d2..e02278b 100644 --- a/components/login/forgetPwd.tsx +++ b/components/login/forgetPwd.tsx @@ -1,9 +1,12 @@ +import { Fonts } from "@/constants/Fonts"; import { fetchApi } from "@/lib/server-api-util"; import { User } from "@/types/user"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { ActivityIndicator, StyleSheet, TextInput, TouchableOpacity, View } from "react-native"; +import { StyleSheet, TouchableOpacity, View } from "react-native"; import { ThemedText } from "../ThemedText"; +import Button from "./ui/Button"; +import TextInput from "./ui/TextInput"; interface LoginProps { setIsSignUp?: (isSignUp: string) => void; @@ -69,45 +72,29 @@ const ForgetPwd = ({ setIsSignUp, updateUrlParam, setError }: LoginProps) => { return ( - - - {t('auth.forgetPwd.title', { ns: 'login' })} - - - - - - {loading ? ( - - ) : ( - - {isDisabled - ? `${t("auth.forgetPwd.sendEmailBtnDisabled", { ns: "login" })} (${countdown}s)` - : t("auth.forgetPwd.sendEmailBtn", { ns: "login" })} - - )} - + {/* 邮箱 */} + + {/* 发送邮箱 */} +