feat: 登录交互
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 |
3
assets/icons/svg/chatNotIn.svg
Normal 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 |
@ -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 |
3
assets/icons/svg/personNotIn.svg
Normal 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 |
BIN
assets/images/png/owner/ask.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||