feat: 登录交互

This commit is contained in:
jinyaqiu 2025-07-17 14:05:07 +08:00
parent 6c270302f5
commit 006db2af07
13 changed files with 98 additions and 35 deletions

View File

@ -3,7 +3,7 @@ import { registerBackgroundUploadTask, triggerManualUpload } from '@/components/
import * as MediaLibrary from 'expo-media-library';
import { useRouter } from 'expo-router';
import * as SecureStore from 'expo-secure-store';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Platform, Text, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from "react-native-safe-area-context";
@ -15,20 +15,26 @@ export default function HomeScreen() {
const insets = useSafeAreaInsets();
const [isLoading, setIsLoading] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [token, setToken] = useState('');
const tokenInterval = useRef<NodeJS.Timeout | number>(null);
const isMounted = useRef(true);
const getAuthToken = async (): Promise<string> => {
let tokenValue = '';
if (Platform.OS === 'web') {
tokenValue = localStorage.getItem('token') || '';
} else {
tokenValue = (await SecureStore.getItemAsync('token')) || '';
}
setToken(tokenValue); // 只在获取到新token时更新状态
return tokenValue;
};
useEffect(() => {
const checkAuthStatus = async () => {
try {
let token;
if (Platform.OS === 'web') {
token = localStorage.getItem('token') || '';
} else {
token = await SecureStore.getItemAsync('token') || '';
}
const loggedIn = !!token;
setIsLoggedIn(loggedIn);
console.log(loggedIn);
if (loggedIn) {
// 已登录,请求必要的权限
@ -49,10 +55,38 @@ export default function HomeScreen() {
setIsLoading(false);
}
};
checkAuthStatus();
}, []);
// 轮询获取token
useEffect(() => {
// 如果已经有token直接返回
if (token) {
if (tokenInterval.current) {
clearInterval(tokenInterval.current);
}
return;
}
if (!tokenInterval.current) return;
// 设置轮询
tokenInterval.current = setInterval(async () => {
if (isMounted.current) {
const currentToken = await getAuthToken();
// 如果获取到token清除定时器
if (currentToken && tokenInterval.current) {
clearInterval(tokenInterval.current);
}
}
}, 5000);
// 返回清理函数
return () => {
if (tokenInterval.current) {
clearInterval(tokenInterval.current);
}
};
}, [token]); // 添加token作为依赖
if (isLoading) {
return (
<View className="flex-1 bg-bgPrimary justify-center items-center">
@ -91,7 +125,7 @@ export default function HomeScreen() {
{"\n"}
{t('auth.welcomeAwaken.back', { ns: 'login' })}
</Text>
{/* <MessagePush /> */}
{/* 唤醒按钮 */}
<TouchableOpacity
className="bg-white rounded-full px-10 py-4 shadow-[0_2px_4px_rgba(0,0,0,0.1)] w-full items-center"

View File

@ -5,7 +5,8 @@ import UserName from '@/components/user-message.tsx/userName';
import { fetchApi } from '@/lib/server-api-util';
import { FileUploadItem } from '@/types/upload';
import { User } from '@/types/user';
import { useEffect, useState } from 'react';
import { useLocalSearchParams } from 'expo-router';
import React, { useEffect, useState } from 'react';
import { KeyboardAvoidingView, Platform, ScrollView, StatusBar, View } from 'react-native';
export type Steps = "userName" | "look" | "choice" | "done";
export default function UserMessage() {
@ -18,6 +19,10 @@ export default function UserMessage() {
const [userInfo, setUserInfo] = useState<User | null>(null);
const statusBarHeight = StatusBar?.currentHeight ?? 0;
// 获取路由参数
const params = useLocalSearchParams();
const { username: usernameParam } = params;
// 获取用户信息
const getUserInfo = async () => {
const res = await fetchApi<User>("/iam/user-info");
@ -44,7 +49,7 @@ export default function UserMessage() {
useEffect(() => {
getUserInfo();
setSteps("userName")
}, []);
}, [usernameParam]);
return (
<KeyboardAvoidingView

View File

@ -1,4 +1,4 @@
<svg width="30" height="27" viewBox="0 0 30 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.2222 7.25H27.4444C28.3036 7.25 29 7.94955 29 8.8125V26L23.8153 21.6734C23.536 21.4403 23.1834 21.3125 22.8203 21.3125H10.3333C9.47422 21.3125 8.77778 20.6129 8.77778 19.75V15.0625H7.17969C6.8166 15.0625 6.46396 15.1901 6.18468 15.4231L1 19.7505V2.5625C1 1.69956 1.69645 1 2.55556 1H19.6667C20.5258 1 21.2222 1.69956 21.2222 2.5625V7.25Z" fill="#4C320C"/>
<path d="M21.2222 7.25H27.4444C28.3036 7.25 29 7.94956 29 8.8125V26L23.8153 21.6734C23.536 21.4403 23.1834 21.3125 22.8203 21.3125H10.3333C9.47422 21.3125 8.77778 20.6129 8.77778 19.75V15.0625M21.2222 7.25V2.5625C21.2222 1.69956 20.5258 1 19.6667 1H2.55556C1.69645 1 1 1.69956 1 2.5625V19.7505L6.18468 15.4231C6.46397 15.1901 6.8166 15.0625 7.17969 15.0625H8.77778M21.2222 7.25V13.5C21.2222 14.3629 20.5258 15.0625 19.6667 15.0625H8.77778" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
<path d="M4.913 2.658c2.075-.27 4.19-.408 6.337-.408 2.147 0 4.262.139 6.337.408 1.922.25 3.291 1.861 3.405 3.727a4.403 4.403 0 0 0-1.032-.211 50.89 50.89 0 0 0-8.42 0c-2.358.196-4.04 2.19-4.04 4.434v4.286a4.47 4.47 0 0 0 2.433 3.984L7.28 21.53A.75.75 0 0 1 6 21v-4.03a48.527 48.527 0 0 1-1.087-.128C2.905 16.58 1.5 14.833 1.5 12.862V6.638c0-1.97 1.405-3.718 3.413-3.979Z" />
<path d="M15.75 7.5c-1.376 0-2.739.057-4.086.169C10.124 7.797 9 9.103 9 10.609v4.285c0 1.507 1.128 2.814 2.67 2.94 1.243.102 2.5.157 3.768.165l2.782 2.781a.75.75 0 0 0 1.28-.53v-2.39l.33-.026c1.542-.125 2.67-1.433 2.67-2.94v-4.286c0-1.505-1.125-2.811-2.664-2.94A49.392 49.392 0 0 0 15.75 7.5Z" />
</svg>

Before

Width:  |  Height:  |  Size: 992 B

After

Width:  |  Height:  |  Size: 780 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" />
</svg>

After

Width:  |  Height:  |  Size: 717 B

View File

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M24 20a7 7 0 1 0 0-14a7 7 0 0 0 0 14M6 40.8V42h36v-1.2c0-4.48 0-6.72-.872-8.432a8 8 0 0 0-3.496-3.496C35.92 28 33.68 28 29.2 28H18.8c-4.48 0-6.72 0-8.432.872a8 8 0 0 0-3.496 3.496C6 34.08 6 36.32 6 40.8"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
<path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" />
</svg>

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 344 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -7,6 +7,7 @@ import { Message, Video } from "@/types/ask";
import { MaterialItem } from "@/types/personal-info";
import { useVideoPlayer, VideoView } from 'expo-video';
import React from 'react';
import { useTranslation } from "react-i18next";
import {
FlatList,
Image,
@ -40,7 +41,7 @@ const renderMessage = ({ insets, item, sessionId, setModalVisible, modalVisible,
const isVideo = (data: Video | MaterialItem): data is Video => {
return 'video' in data;
};
const { t } = useTranslation();
// 创建一个新的 VideoPlayer 组件
const VideoPlayer = ({
videoUrl,
@ -222,7 +223,7 @@ const renderMessage = ({ insets, item, sessionId, setModalVisible, modalVisible,
<TouchableOpacity onPress={() => setModalDetailsVisible(false)}>
<ReturnArrow />
</TouchableOpacity>
<ThemedText style={detailsStyles.headerText}>Select Photo</ThemedText>
<ThemedText style={detailsStyles.headerText}>{t('ask.selectPhoto', { ns: 'ask' })}</ThemedText>
<FolderSvg />
</View>
<View style={{ overflow: 'scroll', height: "100%" }}>
@ -292,7 +293,7 @@ const renderMessage = ({ insets, item, sessionId, setModalVisible, modalVisible,
activeOpacity={0.8}
>
<Text style={detailsStyles.continueButtonText}>
Continue Asking
{t('ask.continueAsking', { ns: 'ask' })}
</Text>
</TouchableOpacity>
</View>

View File

@ -1,10 +1,10 @@
import ChatInSvg from "@/assets/icons/svg/chatIn.svg";
import NavbarSvg from "@/assets/icons/svg/navbar.svg";
import ChatNotInSvg from "@/assets/icons/svg/chatNotIn.svg";
import PersonInSvg from "@/assets/icons/svg/personIn.svg";
import { Ionicons } from "@expo/vector-icons";
import PersonNotInSvg from "@/assets/icons/svg/personNotIn.svg";
import { router, usePathname } from "expo-router";
import React from 'react';
import { Dimensions, Platform, TouchableOpacity, View } from 'react-native';
import { Dimensions, Image, Platform, TouchableOpacity, View } from 'react-native';
import { Circle, Ellipse, G, Mask, Path, Rect, Svg } from 'react-native-svg';
const AskNavbar = () => {
@ -21,10 +21,11 @@ const AskNavbar = () => {
shadowRadius: 8,
elevation: 10, // For Android
}}>
<NavbarSvg className="w-full h-full" />
{/* <NavbarSvg className="w-[150%] h-full" /> */}
<Image source={require('@/assets/images/png/owner/ask.png')} style={{ width: width, height: 80, resizeMode: 'cover' }} />
<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')} >
{pathname === "/memo-list" ? <ChatInSvg /> : <Ionicons name="chatbubbles-outline" size={24} color="#4C320C" />}
<TouchableOpacity onPress={() => router.push('/memo-list')} style={{ padding: 16 }}>
{pathname === "/memo-list" ? <ChatInSvg width={24} height={24} /> : <ChatNotInSvg width={24} height={24} />}
</TouchableOpacity>
<TouchableOpacity
@ -34,7 +35,7 @@ const AskNavbar = () => {
params: { newSession: "true" }
});
}}
className={`${Platform.OS === 'web' ? '-mt-[4rem]' : width <= 375 ? '-mt-[5rem] ml-[2rem]' : '-mt-[5rem] ml-[0.8rem]'}`}
className={`${Platform.OS === 'web' ? '-mt-[4rem]' : width <= 375 ? '-mt-[5rem]' : '-mt-[5rem]'}`}
>
<View style={{
shadowColor: '#FFB645',
@ -74,9 +75,9 @@ const AskNavbar = () => {
</Svg>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => router.push('/owner')}>
<TouchableOpacity onPress={() => router.push('/owner')} style={{ padding: 16 }}>
<View>
{pathname === "/owner" ? <PersonInSvg width={24} height={24} /> : <Ionicons name="person-outline" size={24} color="#4C320C" />}
{pathname === "/owner" ? <PersonInSvg width={24} height={24} /> : <PersonNotInSvg width={24} height={24} />}
{/* <View className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full" /> */}
</View>
</TouchableOpacity>

View File

@ -45,9 +45,13 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
body: JSON.stringify(body),
});
login({ ...res, email: res?.account }, res.access_token || '');
const userInfo = await fetchApi<User>("/iam/user-info");
if (userInfo?.nickname) {
router.replace('/ask');
} else {
router.replace('/user-message');
}
} catch (error) {
// console.error('Login failed', error);
} finally {
setIsLoading(false);
}

View File

@ -125,7 +125,13 @@ const UserInfo = (props: UserInfoProps) => {
}
<TouchableOpacity style={styles.edit} onPress={() => {
setModalVisible(false);
router.push('/user-message')
// 携带参数跳转
router.push({
pathname: '/user-message',
params: {
username: "true"
}
});
}}>
<EditSvg />
</TouchableOpacity>

View File

@ -3,6 +3,8 @@
"hi": "Hi,",
"iAmMemo": "I'm Memo!",
"ready": "Ready to wake up your memories?",
"justAsk": "Just ask MeMo, let me bring them back to life!"
"justAsk": "Just ask MeMo, let me bring them back to life!",
"selectPhoto": "Select Photo",
"continueAsking": "Continue Asking"
}
}

View File

@ -3,6 +3,8 @@
"hi": "Hi,",
"iAmMemo": "I'm Memo!",
"ready": "Ready to wake up your memories?",
"justAsk": "Just ask MeMo, let me bring them back to life!"
"justAsk": "Just ask MeMo, let me bring them back to life!",
"selectPhoto": "Select Photo",
"continueAsking": "Continue Asking"
}
}