feat: Refactor settings management into a dedicated page with new configuration options

This commit is contained in:
zimplexing
2025-07-11 13:49:45 +08:00
parent 7b3fd4b9d5
commit fc8da352fb
13 changed files with 596 additions and 157 deletions

View File

@@ -18,11 +18,12 @@ export default function RootLayout() {
const [loaded, error] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
const initializeSettings = useSettingsStore((state) => state.loadSettings);
const { loadSettings, remoteInputEnabled } = useSettingsStore();
const { startServer, stopServer } = useRemoteControlStore();
useEffect(() => {
initializeSettings();
}, [initializeSettings]);
loadSettings();
}, [loadSettings]);
useEffect(() => {
if (loaded || error) {
@@ -34,21 +35,12 @@ export default function RootLayout() {
}, [loaded, error]);
useEffect(() => {
// Initialize the service with store actions to break require cycle
const { setMessage, hideModal } = useRemoteControlStore.getState();
remoteControlService.init({
onMessage: setMessage,
onHandshake: hideModal,
});
// Start the remote control server on app launch
useRemoteControlStore.getState().startServer();
return () => {
// Stop the server on app close
useRemoteControlStore.getState().stopServer();
};
}, []);
if (remoteInputEnabled) {
startServer();
} else {
stopServer();
}
}, [remoteInputEnabled, startServer, stopServer]);
if (!loaded && !error) {
return null;
@@ -62,6 +54,7 @@ export default function RootLayout() {
{Platform.OS !== "web" && <Stack.Screen name="play" options={{ headerShown: false }} />}
<Stack.Screen name="search" options={{ headerShown: false }} />
<Stack.Screen name="live" options={{ headerShown: false }} />
<Stack.Screen name="settings" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
<Toast />

View File

@@ -6,10 +6,8 @@ import { api } from "@/services/api";
import VideoCard from "@/components/VideoCard.tv";
import { useFocusEffect, useRouter } from "expo-router";
import { Search, Settings } from "lucide-react-native";
import { SettingsModal } from "@/components/SettingsModal";
import { StyledButton } from "@/components/StyledButton";
import useHomeStore, { RowItem, Category } from "@/stores/homeStore";
import { useSettingsStore } from "@/stores/settingsStore";
const NUM_COLUMNS = 5;
const { width } = Dimensions.get("window");
@@ -34,8 +32,6 @@ export default function HomeScreen() {
refreshPlayRecords,
} = useHomeStore();
const showSettingsModal = useSettingsStore((state) => state.showModal);
useFocusEffect(
useCallback(() => {
refreshPlayRecords();
@@ -52,14 +48,14 @@ export default function HomeScreen() {
setSelectedTag(defaultTag);
selectCategory({ ...selectedCategory, tag: defaultTag });
}
}, [selectedCategory, fetchInitialData]);
}, [selectedCategory, fetchInitialData, selectCategory]);
useEffect(() => {
if (selectedCategory && selectedCategory.tag) {
fetchInitialData();
flatListRef.current?.scrollToOffset({ animated: false, offset: 0 });
}
}, [selectedCategory?.tag]);
}, [fetchInitialData, selectedCategory, selectedCategory.tag]);
const handleCategorySelect = (category: Category) => {
setSelectedTag(null);
@@ -133,7 +129,7 @@ export default function HomeScreen() {
>
<Search color={colorScheme === "dark" ? "white" : "black"} size={24} />
</StyledButton>
<StyledButton style={styles.searchButton} onPress={showSettingsModal} variant="ghost">
<StyledButton style={styles.searchButton} onPress={() => router.push("/settings")} variant="ghost">
<Settings color={colorScheme === "dark" ? "white" : "black"} size={24} />
</StyledButton>
</View>
@@ -207,7 +203,6 @@ export default function HomeScreen() {
}
/>
)}
<SettingsModal />
</ThemedView>
);
}

98
app/settings.tsx Normal file
View File

@@ -0,0 +1,98 @@
import React, { useState, useEffect } from "react";
import { View, StyleSheet, ScrollView, Alert } from "react-native";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { StyledButton } from "@/components/StyledButton";
import { useSettingsStore } from "@/stores/settingsStore";
import { APIConfigSection } from "@/components/settings/APIConfigSection";
import { LiveStreamSection } from "@/components/settings/LiveStreamSection";
import { RemoteInputSection } from "@/components/settings/RemoteInputSection";
import { PlaySourceSection } from "@/components/settings/PlaybackSourceSection";
export default function SettingsScreen() {
const { loadSettings, saveSettings } = useSettingsStore();
const [hasChanges, setHasChanges] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
loadSettings();
}, [loadSettings]);
const handleSave = async () => {
setIsLoading(true);
try {
await saveSettings();
setHasChanges(false);
Alert.alert("成功", "设置已保存");
} catch {
Alert.alert("错误", "保存设置失败");
} finally {
setIsLoading(false);
}
};
const markAsChanged = () => {
setHasChanges(true);
};
return (
<ThemedView style={styles.container}>
<View style={styles.header}>
<ThemedText style={styles.title}></ThemedText>
</View>
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
<RemoteInputSection onChanged={markAsChanged} />
<APIConfigSection onChanged={markAsChanged} />
<LiveStreamSection onChanged={markAsChanged} />
<PlaySourceSection onChanged={markAsChanged} />
</ScrollView>
<View style={styles.footer}>
<StyledButton
text={isLoading ? "保存中..." : "保存设置"}
onPress={handleSave}
variant="primary"
disabled={!hasChanges || isLoading}
style={[styles.saveButton, (!hasChanges || isLoading) && styles.disabledButton]}
/>
</View>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
},
title: {
fontSize: 32,
fontWeight: "bold",
paddingTop: 24,
},
backButton: {
minWidth: 100,
},
scrollView: {
flex: 1,
},
footer: {
paddingTop: 24,
borderTopWidth: 1,
borderTopColor: "#333",
},
saveButton: {
minHeight: 50,
},
disabledButton: {
opacity: 0.5,
},
});