diff --git a/app/favorites.tsx b/app/favorites.tsx
index 0a83091..a8fc3b5 100644
--- a/app/favorites.tsx
+++ b/app/favorites.tsx
@@ -1,11 +1,12 @@
import React, { useEffect } from "react";
-import { View, FlatList, StyleSheet, ActivityIndicator } from "react-native";
+import { View, StyleSheet, ActivityIndicator } from "react-native";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import useFavoritesStore from "@/stores/favoritesStore";
import { Favorite } from "@/services/storage";
import VideoCard from "@/components/VideoCard.tv";
import { api } from "@/services/api";
+import CustomScrollView from "@/components/CustomScrollView";
export default function FavoritesScreen() {
const { favorites, loading, error, fetchFavorites } = useFavoritesStore();
@@ -14,31 +15,7 @@ export default function FavoritesScreen() {
fetchFavorites();
}, [fetchFavorites]);
- if (loading) {
- return (
-
-
-
- );
- }
-
- if (error) {
- return (
-
- {error}
-
- );
- }
-
- if (favorites.length === 0) {
- return (
-
- 暂无收藏
-
- );
- }
-
- const renderItem = ({ item }: { item: Favorite & { key: string } }) => {
+ const renderItem = ({ item }: { item: Favorite & { key: string }; index: number }) => {
const [source, id] = item.key.split("+");
return (
我的收藏
- item.key}
numColumns={5}
- contentContainerStyle={styles.list}
+ loading={loading}
+ error={error}
+ emptyMessage="暂无收藏"
/>
);
diff --git a/app/index.tsx b/app/index.tsx
index 0ba550a..34222f4 100644
--- a/app/index.tsx
+++ b/app/index.tsx
@@ -9,15 +9,17 @@ 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";
+import CustomScrollView from "@/components/CustomScrollView";
const NUM_COLUMNS = 5;
const { width } = Dimensions.get("window");
-const ITEM_WIDTH = width / NUM_COLUMNS - 24;
+
+// Threshold for triggering load more data (in pixels)
+const LOAD_MORE_THRESHOLD = 200;
export default function HomeScreen() {
const router = useRouter();
const colorScheme = "dark";
- const flatListRef = useRef(null);
const [selectedTag, setSelectedTag] = useState(null);
const {
@@ -43,7 +45,6 @@ export default function HomeScreen() {
useEffect(() => {
if (selectedCategory && !selectedCategory.tags) {
fetchInitialData();
- flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
} else if (selectedCategory?.tags && !selectedCategory.tag) {
// Category with tags selected, but no specific tag yet. Select the first one.
const defaultTag = selectedCategory.tags[0];
@@ -55,7 +56,6 @@ export default function HomeScreen() {
useEffect(() => {
if (selectedCategory && selectedCategory.tag) {
fetchInitialData();
- flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
}
}, [fetchInitialData, selectedCategory, selectedCategory.tag]);
@@ -86,7 +86,7 @@ export default function HomeScreen() {
);
};
- const renderContentItem = ({ item }: { item: RowItem }) => (
+ const renderContentItem = ({ item, index }: { item: RowItem; index: number }) => (
) : (
- `${item.source}-${item.id}-${index}`}
numColumns={NUM_COLUMNS}
- contentContainerStyle={styles.listContent}
+ loading={loading}
+ loadingMore={loadingMore}
+ error={error}
onEndReached={loadMoreData}
- onEndReachedThreshold={0.5}
+ loadMoreThreshold={LOAD_MORE_THRESHOLD}
+ emptyMessage={selectedCategory?.tags ? "请选择一个子分类" : "该分类下暂无内容"}
ListFooterComponent={renderFooter}
- ListEmptyComponent={
-
- {selectedCategory?.tags ? "请选择一个子分类" : "该分类下暂无内容"}
-
- }
/>
)}
@@ -272,7 +268,6 @@ const styles = StyleSheet.create({
},
itemContainer: {
margin: 8,
- width: ITEM_WIDTH,
alignItems: "center",
},
});
diff --git a/app/search.tsx b/app/search.tsx
index 01b3edd..227186e 100644
--- a/app/search.tsx
+++ b/app/search.tsx
@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from "react";
-import { View, TextInput, StyleSheet, FlatList, Alert, Keyboard } from "react-native";
+import { View, TextInput, StyleSheet, Alert, Keyboard, TouchableOpacity, Pressable } from "react-native";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import VideoCard from "@/components/VideoCard.tv";
@@ -12,6 +12,7 @@ import { RemoteControlModal } from "@/components/RemoteControlModal";
import { useSettingsStore } from "@/stores/settingsStore";
import { useRouter } from "expo-router";
import { Colors } from "@/constants/Colors";
+import CustomScrollView from "@/components/CustomScrollView";
export default function SearchScreen() {
const [keyword, setKeyword] = useState("");
@@ -35,13 +36,13 @@ export default function SearchScreen() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lastMessage]);
- useEffect(() => {
- // Focus the text input when the screen loads
- const timer = setTimeout(() => {
- textInputRef.current?.focus();
- }, 200);
- return () => clearTimeout(timer);
- }, []);
+ // useEffect(() => {
+ // // Focus the text input when the screen loads
+ // const timer = setTimeout(() => {
+ // textInputRef.current?.focus();
+ // }, 200);
+ // return () => clearTimeout(timer);
+ // }, []);
const handleSearch = async (searchText?: string) => {
const term = typeof searchText === "string" ? searchText : keyword;
@@ -80,7 +81,7 @@ export default function SearchScreen() {
showRemoteModal();
};
- const renderItem = ({ item }: { item: SearchResult }) => (
+ const renderItem = ({ item, index }: { item: SearchResult; index: number }) => (
- textInputRef.current?.focus()}
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
- onSubmitEditing={onSearchPress}
- returnKeyType="search"
- />
+ >
+
+
@@ -129,17 +141,13 @@ export default function SearchScreen() {
{error}
) : (
- `${item.id}-${item.source}-${index}`}
- numColumns={5} // Adjust based on your card size and desired layout
- contentContainerStyle={styles.listContent}
- ListEmptyComponent={
-
- 输入关键词开始搜索
-
- }
+ numColumns={5}
+ loading={loading}
+ error={error}
+ emptyMessage="输入关键词开始搜索"
/>
)}
diff --git a/components/CustomScrollView.tsx b/components/CustomScrollView.tsx
new file mode 100644
index 0000000..ab45e42
--- /dev/null
+++ b/components/CustomScrollView.tsx
@@ -0,0 +1,130 @@
+import React, { useState, useCallback } from "react";
+import { View, StyleSheet, ScrollView, Dimensions, ActivityIndicator } from "react-native";
+import { ThemedText } from "@/components/ThemedText";
+
+interface CustomScrollViewProps {
+ data: any[];
+ renderItem: ({ item, index }: { item: any; index: number }) => React.ReactNode;
+ numColumns?: number;
+ loading?: boolean;
+ loadingMore?: boolean;
+ error?: string | null;
+ onEndReached?: () => void;
+ loadMoreThreshold?: number;
+ emptyMessage?: string;
+ ListFooterComponent?: React.ComponentType | React.ReactElement | null;
+}
+
+const { width } = Dimensions.get("window");
+
+const CustomScrollView: React.FC = ({
+ data,
+ renderItem,
+ numColumns = 1,
+ loading = false,
+ loadingMore = false,
+ error = null,
+ onEndReached,
+ loadMoreThreshold = 200,
+ emptyMessage = "暂无内容",
+ ListFooterComponent,
+}) => {
+ const ITEM_WIDTH = numColumns > 0 ? width / numColumns - 24 : width - 24;
+
+ const handleScroll = useCallback(
+ ({ nativeEvent }: { nativeEvent: any }) => {
+ const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
+ const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - loadMoreThreshold;
+
+ if (isCloseToBottom && !loadingMore && onEndReached) {
+ onEndReached();
+ }
+ },
+ [onEndReached, loadingMore, loadMoreThreshold]
+ );
+
+ const renderFooter = () => {
+ if (ListFooterComponent) {
+ if (React.isValidElement(ListFooterComponent)) {
+ return ListFooterComponent;
+ } else if (typeof ListFooterComponent === "function") {
+ const Component = ListFooterComponent as React.ComponentType;
+ return ;
+ }
+ return null;
+ }
+ if (loadingMore) {
+ return ;
+ }
+ return null;
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+ {error}
+
+
+ );
+ }
+
+ if (data.length === 0) {
+ return (
+
+ {emptyMessage}
+
+ );
+ }
+
+ return (
+
+ {data.length > 0 ? (
+ <>
+ {/* Render content in a grid layout */}
+ {Array.from({ length: Math.ceil(data.length / numColumns) }).map((_, rowIndex) => (
+
+ {data.slice(rowIndex * numColumns, (rowIndex + 1) * numColumns).map((item, index) => (
+
+ {renderItem({ item, index: rowIndex * numColumns + index })}
+
+ ))}
+
+ ))}
+ {renderFooter()}
+ >
+ ) : (
+
+ {emptyMessage}
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ centerContainer: {
+ flex: 1,
+ paddingTop: 20,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ listContent: {
+ paddingHorizontal: 16,
+ paddingBottom: 20,
+ },
+ itemContainer: {
+ margin: 8,
+ alignItems: "center",
+ },
+});
+
+export default CustomScrollView;
diff --git a/components/LoginModal.tsx b/components/LoginModal.tsx
index fed6838..4802b2f 100644
--- a/components/LoginModal.tsx
+++ b/components/LoginModal.tsx
@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from "react";
-import { Modal, View, TextInput, StyleSheet, ActivityIndicator, useTVEventHandler } from "react-native";
+import { Modal, View, TextInput, StyleSheet, ActivityIndicator } from "react-native";
import { usePathname } from "expo-router";
import Toast from "react-native-toast-message";
import useAuthStore from "@/stores/authStore";
@@ -19,47 +19,24 @@ const LoginModal = () => {
const [isLoading, setIsLoading] = useState(false);
const usernameInputRef = useRef(null);
const passwordInputRef = useRef(null);
- const loginButtonRef = useRef(null);
- const [focused, setFocused] = useState("username");
const pathname = usePathname();
const isSettingsPage = pathname.includes("settings");
- const tvEventHandler = (evt: any) => {
- if (!evt || !isLoginModalVisible || isSettingsPage) {
- 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);
-
+ // Focus management with better TV remote handling
useEffect(() => {
if (isLoginModalVisible && !isSettingsPage) {
const isUsernameVisible = serverConfig?.StorageType !== "localstorage";
- setTimeout(() => {
+
+ // Use a small delay to ensure the modal is fully rendered
+ const focusTimeout = setTimeout(() => {
if (isUsernameVisible) {
usernameInputRef.current?.focus();
} else {
passwordInputRef.current?.focus();
}
- }, 200);
+ }, 100);
+
+ return () => clearTimeout(focusTimeout);
}
}, [isLoginModalVisible, serverConfig, isSettingsPage]);
@@ -85,6 +62,11 @@ const LoginModal = () => {
}
};
+ // Handle navigation between inputs using returnKeyType
+ const handleUsernameSubmit = () => {
+ passwordInputRef.current?.focus();
+ };
+
return (
{
value={username}
onChangeText={setUsername}
returnKeyType="next"
- onFocus={() => setFocused("username")}
+ onSubmitEditing={handleUsernameSubmit}
+ blurOnSubmit={false}
/>
)}
{
value={password}
onChangeText={setPassword}
returnKeyType="go"
- onFocus={() => setFocused("password")}
onSubmitEditing={handleLogin}
/>
setFocused("button")}
text={isLoading ? "" : "登录"}
onPress={handleLogin}
disabled={isLoading}
style={styles.button}
+ hasTVPreferredFocus={!serverConfig || serverConfig.StorageType === "localstorage"}
>
{isLoading && }
diff --git a/components/SourceSelectionModal.tsx b/components/SourceSelectionModal.tsx
index af43570..a5ced62 100644
--- a/components/SourceSelectionModal.tsx
+++ b/components/SourceSelectionModal.tsx
@@ -5,12 +5,24 @@ import useDetailStore from "@/stores/detailStore";
import usePlayerStore from "@/stores/playerStore";
export const SourceSelectionModal: React.FC = () => {
- const { showSourceModal, setShowSourceModal } = usePlayerStore();
+ const { showSourceModal, setShowSourceModal, loadVideo, currentEpisodeIndex, status } = usePlayerStore();
const { searchResults, detail, setDetail } = useDetailStore();
const onSelectSource = (index: number) => {
+ console.log("onSelectSource", index, searchResults[index].source, detail?.source);
if (searchResults[index].source !== detail?.source) {
- setDetail(searchResults[index]);
+ const newDetail = searchResults[index];
+ setDetail(newDetail);
+
+ // Reload the video with the new source, preserving current position
+ const currentPosition = status?.isLoaded ? status.positionMillis : undefined;
+ loadVideo({
+ source: newDetail.source,
+ id: newDetail.id.toString(),
+ episodeIndex: currentEpisodeIndex,
+ title: newDetail.title,
+ position: currentPosition
+ });
}
setShowSourceModal(false);
};
diff --git a/components/VideoCard.tv.tsx b/components/VideoCard.tv.tsx
index accc8ee..e248514 100644
--- a/components/VideoCard.tv.tsx
+++ b/components/VideoCard.tv.tsx
@@ -1,14 +1,14 @@
-import React, { useState, useEffect, useCallback, useRef } from "react";
-import { View, Text, Image, StyleSheet, Pressable, TouchableOpacity, Alert } from "react-native";
+import React, { useState, useCallback, useRef, forwardRef } from "react";
+import { View, Text, Image, StyleSheet, TouchableOpacity, Alert } from "react-native";
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
import { useRouter } from "expo-router";
-import { Heart, Star, Play, Trash2 } from "lucide-react-native";
-import { FavoriteManager, PlayRecordManager } from "@/services/storage";
-import { API, api } from "@/services/api";
+import { Star, Play } from "lucide-react-native";
+import { PlayRecordManager } from "@/services/storage";
+import { API } from "@/services/api";
import { ThemedText } from "@/components/ThemedText";
import { Colors } from "@/constants/Colors";
-interface VideoCardProps {
+interface VideoCardProps extends React.ComponentProps {
id: string;
source: string;
title: string;
@@ -25,166 +25,175 @@ interface VideoCardProps {
api: API;
}
-export default function VideoCard({
- id,
- source,
- title,
- poster,
- year,
- rate,
- sourceName,
- progress,
- episodeIndex,
- onFocus,
- onRecordDeleted,
- api,
- playTime = 0,
-}: VideoCardProps) {
- const router = useRouter();
- const [isFocused, setIsFocused] = useState(false);
+const VideoCard = forwardRef(
+ (
+ {
+ id,
+ source,
+ title,
+ poster,
+ year,
+ rate,
+ sourceName,
+ progress,
+ episodeIndex,
+ onFocus,
+ onRecordDeleted,
+ api,
+ playTime = 0,
+ }: VideoCardProps,
+ ref
+ ) => {
+ const router = useRouter();
+ const [isFocused, setIsFocused] = useState(false);
- const longPressTriggered = useRef(false);
+ const longPressTriggered = useRef(false);
- const scale = useSharedValue(1);
+ const scale = useSharedValue(1);
- const animatedStyle = useAnimatedStyle(() => {
- return {
- transform: [{ scale: scale.value }],
+ const animatedStyle = useAnimatedStyle(() => {
+ return {
+ transform: [{ scale: scale.value }],
+ };
+ });
+
+ const handlePress = () => {
+ if (longPressTriggered.current) {
+ longPressTriggered.current = false;
+ return;
+ }
+ // 如果有播放进度,直接转到播放页面
+ if (progress !== undefined && episodeIndex !== undefined) {
+ router.push({
+ pathname: "/play",
+ params: { source, id, episodeIndex: episodeIndex - 1, title, position: playTime * 1000 },
+ });
+ } else {
+ router.push({
+ pathname: "/detail",
+ params: { source, q: title },
+ });
+ }
};
- });
- const handlePress = () => {
- if (longPressTriggered.current) {
- longPressTriggered.current = false;
- return;
- }
- // 如果有播放进度,直接转到播放页面
- if (progress !== undefined && episodeIndex !== undefined) {
- router.push({
- pathname: "/play",
- params: { source, id, episodeIndex: episodeIndex - 1, title, position: playTime * 1000 },
- });
- } else {
- router.push({
- pathname: "/detail",
- params: { source, q: title },
- });
- }
- };
+ const handleFocus = useCallback(() => {
+ setIsFocused(true);
+ scale.value = withSpring(1.05, { damping: 15, stiffness: 200 });
+ onFocus?.();
+ }, [scale, onFocus]);
- const handleFocus = useCallback(() => {
- setIsFocused(true);
- scale.value = withSpring(1.05, { damping: 15, stiffness: 200 });
- onFocus?.();
- }, [scale, onFocus]);
+ const handleBlur = useCallback(() => {
+ setIsFocused(false);
+ scale.value = withSpring(1.0);
+ }, [scale]);
- const handleBlur = useCallback(() => {
- setIsFocused(false);
- scale.value = withSpring(1.0);
- }, [scale]);
+ const handleLongPress = () => {
+ // Only allow long press for items with progress (play records)
+ if (progress === undefined) return;
- const handleLongPress = () => {
- // Only allow long press for items with progress (play records)
- if (progress === undefined) return;
+ longPressTriggered.current = true;
- longPressTriggered.current = true;
-
- // Show confirmation dialog to delete play record
- Alert.alert("删除观看记录", `确定要删除"${title}"的观看记录吗?`, [
- {
- text: "取消",
- style: "cancel",
- },
- {
- text: "删除",
- style: "destructive",
- onPress: async () => {
- try {
- // Delete from local storage
- await PlayRecordManager.remove(source, id);
-
- // Call the onRecordDeleted callback
- if (onRecordDeleted) {
- onRecordDeleted();
- }
- // 如果没有回调函数,则使用导航刷新作为备选方案
- else if (router.canGoBack()) {
- router.replace("/");
- }
- } catch (error) {
- console.info("Failed to delete play record:", error);
- Alert.alert("错误", "删除观看记录失败,请重试");
- }
+ // Show confirmation dialog to delete play record
+ Alert.alert("删除观看记录", `确定要删除"${title}"的观看记录吗?`, [
+ {
+ text: "取消",
+ style: "cancel",
},
- },
- ]);
- };
+ {
+ text: "删除",
+ style: "destructive",
+ onPress: async () => {
+ try {
+ // Delete from local storage
+ await PlayRecordManager.remove(source, id);
- // 是否是继续观看的视频
- const isContinueWatching = progress !== undefined && progress > 0 && progress < 1;
+ // Call the onRecordDeleted callback
+ if (onRecordDeleted) {
+ onRecordDeleted();
+ }
+ // 如果没有回调函数,则使用导航刷新作为备选方案
+ else if (router.canGoBack()) {
+ router.replace("/");
+ }
+ } catch (error) {
+ console.info("Failed to delete play record:", error);
+ Alert.alert("错误", "删除观看记录失败,请重试");
+ }
+ },
+ },
+ ]);
+ };
- return (
-
-
-
-
- {isFocused && (
-
- {isContinueWatching && (
-
-
- 继续观看
-
- )}
-
- )}
+ // 是否是继续观看的视频
+ const isContinueWatching = progress !== undefined && progress > 0 && progress < 1;
- {/* 进度条 */}
- {isContinueWatching && (
-
-
-
- )}
+ return (
+
+
+
+
+ {isFocused && (
+
+ {isContinueWatching && (
+
+
+ 继续观看
+
+ )}
+
+ )}
- {rate && (
-
-
- {rate}
-
- )}
- {year && (
-
- {year}
-
- )}
- {sourceName && (
-
- {sourceName}
-
- )}
-
-
- {title}
- {isContinueWatching && (
-
-
- 第{episodeIndex! + 1}集 已观看 {Math.round((progress || 0) * 100)}%
-
-
- )}
-
-
-
- );
-}
+ {/* 进度条 */}
+ {isContinueWatching && (
+
+
+
+ )}
+
+ {rate && (
+
+
+ {rate}
+
+ )}
+ {year && (
+
+ {year}
+
+ )}
+ {sourceName && (
+
+ {sourceName}
+
+ )}
+
+
+ {title}
+ {isContinueWatching && (
+
+
+ 第{episodeIndex! + 1}集 已观看 {Math.round((progress || 0) * 100)}%
+
+
+ )}
+
+
+
+ );
+ }
+);
+
+VideoCard.displayName = "VideoCard";
+
+export default VideoCard;
const CARD_WIDTH = 160;
const CARD_HEIGHT = 240;
diff --git a/package.json b/package.json
index a73538e..2f2b83c 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "OrionTV",
"private": true,
"main": "expo-router/entry",
- "version": "1.2.7",
+ "version": "1.2.8",
"scripts": {
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
@@ -80,4 +80,4 @@
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
-}
+}
\ No newline at end of file
diff --git a/stores/authStore.ts b/stores/authStore.ts
index 1bc6181..c971efa 100644
--- a/stores/authStore.ts
+++ b/stores/authStore.ts
@@ -2,6 +2,7 @@ import { create } from "zustand";
import Cookies from "@react-native-cookies/cookies";
import { api } from "@/services/api";
import { useSettingsStore } from "./settingsStore";
+import Toast from "react-native-toast-message";
interface AuthState {
isLoggedIn: boolean;
@@ -24,6 +25,10 @@ const useAuthStore = create((set) => ({
}
try {
const serverConfig = useSettingsStore.getState().serverConfig;
+ if (!serverConfig?.StorageType) {
+ Toast.show({ type: "error", text1: "请检查网络或者 API 地址是否可用" });
+ return
+ }
const cookies = await Cookies.get(api.baseURL);
if (serverConfig && serverConfig.StorageType === "localstorage" && !cookies.auth) {
const loginResult = await api.login().catch(() => {
diff --git a/stores/playerStore.ts b/stores/playerStore.ts
index 3c94fa0..3e22a4b 100644
--- a/stores/playerStore.ts
+++ b/stores/playerStore.ts
@@ -254,7 +254,6 @@ const usePlayerStore = create((set, get) => ({
...existingRecord,
...updates,
});
- console.log("Play record saved")
}
},
diff --git a/stores/settingsStore.ts b/stores/settingsStore.ts
index 576d8a0..dc16a72 100644
--- a/stores/settingsStore.ts
+++ b/stores/settingsStore.ts
@@ -56,9 +56,10 @@ export const useSettingsStore = create((set, get) => ({
const config = await api.getServerConfig();
if (config) {
storageConfig.setStorageType(config.StorageType);
+ set({ serverConfig: config });
}
- set({ serverConfig: config });
} catch (error) {
+ set({ serverConfig: null });
console.info("Failed to fetch server config:", error);
}
},