From 0b1fa9df6d92f27abb84cb9e80651ac83cb93d77 Mon Sep 17 00:00:00 2001 From: zimplexing Date: Tue, 15 Jul 2025 22:33:11 +0800 Subject: [PATCH] feat: enhance LoginModal with TV event handling and input focus management --- app/index.tsx | 4 +-- components/LoginModal.tsx | 55 ++++++++++++++++++++++++++++++++++----- stores/authStore.ts | 31 +++++++++------------- stores/detailStore.ts | 18 ++++++------- 4 files changed, 73 insertions(+), 35 deletions(-) diff --git a/app/index.tsx b/app/index.tsx index fc35900..4c0b213 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -5,7 +5,7 @@ import { ThemedText } from "@/components/ThemedText"; import { api } from "@/services/api"; import VideoCard from "@/components/VideoCard.tv"; import { useFocusEffect, useRouter } from "expo-router"; -import { Search, Settings, LogOut, Star } from "lucide-react-native"; +import { Search, Settings, LogOut, Heart } from "lucide-react-native"; import { StyledButton } from "@/components/StyledButton"; import useHomeStore, { RowItem, Category } from "@/stores/homeStore"; import useAuthStore from "@/stores/authStore"; @@ -125,7 +125,7 @@ export default function HomeScreen() { router.push("/favorites")} variant="ghost"> - + { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); + const usernameInputRef = useRef(null); const passwordInputRef = useRef(null); const loginButtonRef = useRef(null); + const [focused, setFocused] = useState("username"); + + const tvEventHandler = (evt: any) => { + if (!evt || !isLoginModalVisible) { + return; + } + + const isUsernameVisible = serverConfig?.StorageType !== "localstorage"; + + if (evt.eventType === "down") { + if (focused === "username" && isUsernameVisible) { + passwordInputRef.current?.focus(); + } else if (focused === "password") { + loginButtonRef.current?.focus(); + } + } + + if (evt.eventType === "up") { + if (focused === "button") { + passwordInputRef.current?.focus(); + } else if (focused === "password" && isUsernameVisible) { + usernameInputRef.current?.focus(); + } + } + }; + + useTVEventHandler(tvEventHandler); + + useEffect(() => { + if (isLoginModalVisible) { + const isUsernameVisible = serverConfig?.StorageType !== "localstorage"; + setTimeout(() => { + if (isUsernameVisible) { + usernameInputRef.current?.focus(); + } else { + passwordInputRef.current?.focus(); + } + }, 200); + } + }, [isLoginModalVisible, serverConfig]); const handleLogin = async () => { const isLocalStorage = serverConfig?.StorageType === "localstorage"; @@ -49,14 +90,14 @@ const LoginModal = () => { 服务器需要验证您的身份 {serverConfig?.StorageType !== "localstorage" && ( passwordInputRef.current?.focus()} + onFocus={() => setFocused("username")} /> )} { secureTextEntry value={password} onChangeText={setPassword} - returnKeyType="next" - onSubmitEditing={() => loginButtonRef.current?.focus()} + returnKeyType="go" + onFocus={() => setFocused("password")} + onSubmitEditing={handleLogin} /> setFocused("button")} text={isLoading ? "" : "登录"} onPress={handleLogin} disabled={isLoading} diff --git a/stores/authStore.ts b/stores/authStore.ts index dc6a476..a8746be 100644 --- a/stores/authStore.ts +++ b/stores/authStore.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import Cookies from "@react-native-cookies/cookies"; -import { api } from "@/services/api"; +import { api, ServerConfig } from "@/services/api"; interface AuthState { isLoggedIn: boolean; @@ -22,23 +22,18 @@ const useAuthStore = create((set) => ({ return; } try { - await api.login(); - set({ isLoginModalVisible: true }); - } catch { - try { - const cookies = await Cookies.get(api.baseURL); - const isLoggedIn = cookies && !!cookies.auth; - set({ isLoggedIn }); - if (!isLoggedIn) { - set({ isLoginModalVisible: true }); - } - } catch (error) { - console.info("Failed to check login status:", error); - if (error instanceof Error && error.message === "UNAUTHORIZED") { - set({ isLoggedIn: false, isLoginModalVisible: true }); - } else { - set({ isLoggedIn: false }); - } + const cookies = await Cookies.get(api.baseURL); + const isLoggedIn = cookies && !!cookies.auth; + set({ isLoggedIn }); + if (!isLoggedIn) { + set({ isLoginModalVisible: true }); + } + } catch (error) { + console.info("Failed to check login status:", error); + if (error instanceof Error && error.message === "UNAUTHORIZED") { + set({ isLoggedIn: false, isLoginModalVisible: true }); + } else { + set({ isLoggedIn: false }); } } }, diff --git a/stores/detailStore.ts b/stores/detailStore.ts index b0cf320..c214392 100644 --- a/stores/detailStore.ts +++ b/stores/detailStore.ts @@ -122,7 +122,7 @@ const useDetailStore = create((set, get) => ({ } } } catch (error) { - console.warn(`Failed to fetch from ${resource.name}:`, error); + console.info(`Failed to fetch from ${resource.name}:`, error); } }); @@ -133,11 +133,11 @@ const useDetailStore = create((set, get) => ({ set({ error: "未找到任何播放源" }); } - if (get().detail) { - const { source, id } = get().detail!; - const isFavorited = await FavoriteManager.isFavorited(source, id.toString()); - set({ isFavorited }); - } + // if (get().detail) { + // const { source, id } = get().detail!; + // const isFavorited = await FavoriteManager.isFavorited(source, id.toString()); + // set({ isFavorited }); + // } } catch (e) { if ((e as Error).name !== "AbortError") { set({ error: e instanceof Error ? e.message : "获取数据失败" }); @@ -151,9 +151,9 @@ const useDetailStore = create((set, get) => ({ setDetail: async (detail) => { set({ detail }); - const { source, id } = detail; - const isFavorited = await FavoriteManager.isFavorited(source, id.toString()); - set({ isFavorited }); + // const { source, id } = detail; + // const isFavorited = await FavoriteManager.isFavorited(source, id.toString()); + // set({ isFavorited }); }, abort: () => {