mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
- Hide live streaming tab in mobile navigation components - Hide remote input and live stream configurations in settings - Hide API configuration description text on mobile - Disable HTTP server startup on mobile devices to save resources This streamlines the mobile interface while preserving full functionality on tablet and TV platforms.
266 lines
8.4 KiB
TypeScript
266 lines
8.4 KiB
TypeScript
import React, { useState, useEffect, useRef } from "react";
|
|
import { View, StyleSheet, FlatList, Alert, KeyboardAvoidingView, Platform } from "react-native";
|
|
import { useTVEventHandler } from "react-native";
|
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
import { ThemedText } from "@/components/ThemedText";
|
|
import { ThemedView } from "@/components/ThemedView";
|
|
import { StyledButton } from "@/components/StyledButton";
|
|
import { useThemeColor } from "@/hooks/useThemeColor";
|
|
import { useSettingsStore } from "@/stores/settingsStore";
|
|
// import useAuthStore from "@/stores/authStore";
|
|
import { useRemoteControlStore } from "@/stores/remoteControlStore";
|
|
import { APIConfigSection } from "@/components/settings/APIConfigSection";
|
|
import { LiveStreamSection } from "@/components/settings/LiveStreamSection";
|
|
import { RemoteInputSection } from "@/components/settings/RemoteInputSection";
|
|
import { UpdateSection } from "@/components/settings/UpdateSection";
|
|
// import { VideoSourceSection } from "@/components/settings/VideoSourceSection";
|
|
import Toast from "react-native-toast-message";
|
|
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
|
import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles";
|
|
import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation";
|
|
import ResponsiveHeader from "@/components/navigation/ResponsiveHeader";
|
|
import { DeviceUtils } from "@/utils/DeviceUtils";
|
|
|
|
export default function SettingsScreen() {
|
|
const { loadSettings, saveSettings, setApiBaseUrl, setM3uUrl } = useSettingsStore();
|
|
const { lastMessage, targetPage, clearMessage } = useRemoteControlStore();
|
|
const backgroundColor = useThemeColor({}, "background");
|
|
const insets = useSafeAreaInsets();
|
|
|
|
// 响应式布局配置
|
|
const responsiveConfig = useResponsiveLayout();
|
|
const commonStyles = getCommonResponsiveStyles(responsiveConfig);
|
|
const { deviceType, spacing } = responsiveConfig;
|
|
|
|
const [hasChanges, setHasChanges] = useState(false);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [currentFocusIndex, setCurrentFocusIndex] = useState(0);
|
|
const [currentSection, setCurrentSection] = useState<string | null>(null);
|
|
|
|
const saveButtonRef = useRef<any>(null);
|
|
const apiSectionRef = useRef<any>(null);
|
|
const liveStreamSectionRef = useRef<any>(null);
|
|
|
|
useEffect(() => {
|
|
loadSettings();
|
|
}, [loadSettings]);
|
|
|
|
useEffect(() => {
|
|
if (lastMessage && !targetPage) {
|
|
const realMessage = lastMessage.split("_")[0];
|
|
handleRemoteInput(realMessage);
|
|
clearMessage(); // Clear the message after processing
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [lastMessage, targetPage]);
|
|
|
|
const handleRemoteInput = (message: string) => {
|
|
// Handle remote input based on currently focused section
|
|
if (currentSection === "api" && apiSectionRef.current) {
|
|
// API Config Section
|
|
setApiBaseUrl(message);
|
|
} else if (currentSection === "livestream" && liveStreamSectionRef.current) {
|
|
// Live Stream Section
|
|
setM3uUrl(message);
|
|
}
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
await saveSettings();
|
|
setHasChanges(false);
|
|
Toast.show({
|
|
type: "success",
|
|
text1: "保存成功",
|
|
});
|
|
} catch {
|
|
Alert.alert("错误", "保存设置失败");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const markAsChanged = () => {
|
|
setHasChanges(true);
|
|
};
|
|
|
|
const sections = [
|
|
// 远程输入配置 - 仅在非手机端显示
|
|
deviceType !== "mobile" && {
|
|
component: (
|
|
<RemoteInputSection
|
|
onChanged={markAsChanged}
|
|
onFocus={() => {
|
|
setCurrentFocusIndex(0);
|
|
setCurrentSection("remote");
|
|
}}
|
|
/>
|
|
),
|
|
key: "remote",
|
|
},
|
|
{
|
|
component: (
|
|
<APIConfigSection
|
|
ref={apiSectionRef}
|
|
onChanged={markAsChanged}
|
|
hideDescription={deviceType === "mobile"}
|
|
onFocus={() => {
|
|
setCurrentFocusIndex(1);
|
|
setCurrentSection("api");
|
|
}}
|
|
/>
|
|
),
|
|
key: "api",
|
|
},
|
|
// 直播源配置 - 仅在非手机端显示
|
|
deviceType !== "mobile" && {
|
|
component: (
|
|
<LiveStreamSection
|
|
ref={liveStreamSectionRef}
|
|
onChanged={markAsChanged}
|
|
onFocus={() => {
|
|
setCurrentFocusIndex(2);
|
|
setCurrentSection("livestream");
|
|
}}
|
|
/>
|
|
),
|
|
key: "livestream",
|
|
},
|
|
// {
|
|
// component: (
|
|
// <VideoSourceSection
|
|
// onChanged={markAsChanged}
|
|
// onFocus={() => {
|
|
// setCurrentFocusIndex(3);
|
|
// setCurrentSection("videoSource");
|
|
// }}
|
|
// />
|
|
// ),
|
|
// key: "videoSource",
|
|
// },
|
|
Platform.OS === "android" && {
|
|
component: <UpdateSection />,
|
|
key: "update",
|
|
},
|
|
].filter(Boolean);
|
|
|
|
// TV遥控器事件处理 - 仅在TV设备上启用
|
|
const handleTVEvent = React.useCallback(
|
|
(event: any) => {
|
|
if (deviceType !== "tv") return;
|
|
|
|
if (event.eventType === "down") {
|
|
const nextIndex = Math.min(currentFocusIndex + 1, sections.length);
|
|
setCurrentFocusIndex(nextIndex);
|
|
if (nextIndex === sections.length) {
|
|
saveButtonRef.current?.focus();
|
|
}
|
|
} else if (event.eventType === "up") {
|
|
const prevIndex = Math.max(currentFocusIndex - 1, 0);
|
|
setCurrentFocusIndex(prevIndex);
|
|
}
|
|
},
|
|
[currentFocusIndex, sections.length, deviceType]
|
|
);
|
|
|
|
useTVEventHandler(deviceType === "tv" ? handleTVEvent : () => {});
|
|
|
|
// 动态样式
|
|
const dynamicStyles = createResponsiveStyles(deviceType, spacing, insets);
|
|
|
|
const renderSettingsContent = () => (
|
|
<KeyboardAvoidingView style={{ flex: 1, backgroundColor }} behavior={Platform.OS === "ios" ? "padding" : "height"}>
|
|
<ThemedView style={[commonStyles.container, dynamicStyles.container]}>
|
|
{deviceType === "tv" && (
|
|
<View style={dynamicStyles.header}>
|
|
<ThemedText style={dynamicStyles.title}>设置</ThemedText>
|
|
</View>
|
|
)}
|
|
|
|
<View style={dynamicStyles.scrollView}>
|
|
<FlatList
|
|
data={sections}
|
|
renderItem={({ item }) => {
|
|
if (item) {
|
|
return item.component;
|
|
}
|
|
return null;
|
|
}}
|
|
keyExtractor={(item) => (item ? item.key : "default")}
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={dynamicStyles.listContent}
|
|
/>
|
|
</View>
|
|
|
|
<View style={dynamicStyles.footer}>
|
|
<StyledButton
|
|
text={isLoading ? "保存中..." : "保存设置"}
|
|
onPress={handleSave}
|
|
variant="primary"
|
|
disabled={!hasChanges || isLoading}
|
|
style={[dynamicStyles.saveButton, (!hasChanges || isLoading) && dynamicStyles.disabledButton]}
|
|
/>
|
|
</View>
|
|
</ThemedView>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
|
|
// 根据设备类型决定是否包装在响应式导航中
|
|
if (deviceType === "tv") {
|
|
return renderSettingsContent();
|
|
}
|
|
|
|
return (
|
|
<ResponsiveNavigation>
|
|
<ResponsiveHeader title="设置" showBackButton />
|
|
{renderSettingsContent()}
|
|
</ResponsiveNavigation>
|
|
);
|
|
}
|
|
|
|
const createResponsiveStyles = (deviceType: string, spacing: number, insets: any) => {
|
|
const isMobile = deviceType === "mobile";
|
|
const isTablet = deviceType === "tablet";
|
|
const isTV = deviceType === "tv";
|
|
const minTouchTarget = DeviceUtils.getMinTouchTargetSize();
|
|
|
|
return StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
padding: spacing,
|
|
paddingTop: isTV ? spacing * 2 : isMobile ? insets.top + spacing : insets.top + spacing * 1.5,
|
|
},
|
|
header: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
marginBottom: spacing,
|
|
},
|
|
title: {
|
|
fontSize: isMobile ? 24 : isTablet ? 28 : 32,
|
|
fontWeight: "bold",
|
|
paddingTop: spacing,
|
|
color: "white",
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
},
|
|
listContent: {
|
|
paddingBottom: spacing,
|
|
},
|
|
footer: {
|
|
paddingTop: spacing,
|
|
alignItems: isMobile ? "center" : "flex-end",
|
|
},
|
|
saveButton: {
|
|
minHeight: isMobile ? minTouchTarget : isTablet ? 50 : 50,
|
|
width: isMobile ? "100%" : isTablet ? 140 : 120,
|
|
maxWidth: isMobile ? 280 : undefined,
|
|
},
|
|
disabledButton: {
|
|
opacity: 0.5,
|
|
},
|
|
});
|
|
};
|