diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 9f73004..8b7acb1 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -5,7 +5,9 @@
"Bash(rm:*)",
"Bash(yarn install)",
"Bash(yarn lint)",
- "Bash(yarn prebuild-tv:*)"
+ "Bash(yarn prebuild-tv:*)",
+ "Bash(mkdir:*)",
+ "Bash(yarn lint:*)"
],
"deny": []
}
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 6535068..072a576 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -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" && }
+
diff --git a/app/index.tsx b/app/index.tsx
index 661e5dc..18f186d 100644
--- a/app/index.tsx
+++ b/app/index.tsx
@@ -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() {
>
-
+ router.push("/settings")} variant="ghost">
@@ -207,7 +203,6 @@ export default function HomeScreen() {
}
/>
)}
-
);
}
diff --git a/app/settings.tsx b/app/settings.tsx
new file mode 100644
index 0000000..a411d0a
--- /dev/null
+++ b/app/settings.tsx
@@ -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 (
+
+
+ 设置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+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,
+ },
+});
diff --git a/components/SettingsModal.tsx b/components/SettingsModal.tsx
deleted file mode 100644
index 995281b..0000000
--- a/components/SettingsModal.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import React, { useState, useEffect, useRef } from "react";
-import { Modal, View, Text, TextInput, StyleSheet } from "react-native";
-import { ThemedText } from "./ThemedText";
-import { ThemedView } from "./ThemedView";
-import { useSettingsStore } from "@/stores/settingsStore";
-import { StyledButton } from "./StyledButton";
-
-export const SettingsModal: React.FC = () => {
- const { isModalVisible, hideModal, apiBaseUrl, setApiBaseUrl, saveSettings, loadSettings } = useSettingsStore();
-
- const [isInputFocused, setIsInputFocused] = useState(false);
- const colorScheme = "dark"; // Replace with useColorScheme() if needed
- const inputRef = useRef(null);
-
- useEffect(() => {
- if (isModalVisible) {
- loadSettings();
- const timer = setTimeout(() => {
- inputRef.current?.focus();
- }, 200);
- return () => clearTimeout(timer);
- }
- }, [isModalVisible, loadSettings]);
-
- const handleSave = () => {
- saveSettings();
- };
-
- const styles = StyleSheet.create({
- modalContainer: {
- flex: 1,
- justifyContent: "center",
- alignItems: "center",
- backgroundColor: "rgba(0, 0, 0, 0.6)",
- },
- modalContent: {
- width: "80%",
- maxWidth: 500,
- padding: 24,
- borderRadius: 12,
- elevation: 10,
- },
- title: {
- fontSize: 24,
- fontWeight: "bold",
- marginBottom: 20,
- textAlign: "center",
- },
- input: {
- height: 50,
- borderWidth: 2,
- borderRadius: 8,
- paddingHorizontal: 15,
- fontSize: 16,
- marginBottom: 24,
- backgroundColor: colorScheme === "dark" ? "#3a3a3c" : "#f0f0f0",
- color: colorScheme === "dark" ? "white" : "black",
- borderColor: "transparent",
- },
- inputFocused: {
- borderColor: "#007AFF",
- shadowColor: "#007AFF",
- shadowOffset: { width: 0, height: 0 },
- shadowOpacity: 0.8,
- shadowRadius: 10,
- elevation: 5,
- },
- buttonContainer: {
- flexDirection: "row",
- justifyContent: "space-around",
- },
- button: {
- flex: 1,
- marginHorizontal: 8,
- },
- buttonText: {
- fontSize: 18,
- },
- });
-
- return (
-
-
-
- 设置
- setIsInputFocused(true)}
- onBlur={() => setIsInputFocused(false)}
- />
-
-
-
-
-
-
-
- );
-};
diff --git a/components/settings/APIConfigSection.tsx b/components/settings/APIConfigSection.tsx
new file mode 100644
index 0000000..5d22a30
--- /dev/null
+++ b/components/settings/APIConfigSection.tsx
@@ -0,0 +1,81 @@
+import React, { useState, useRef } from "react";
+import { View, TextInput, StyleSheet } from "react-native";
+import { ThemedText } from "@/components/ThemedText";
+import { ThemedView } from "@/components/ThemedView";
+import { useSettingsStore } from "@/stores/settingsStore";
+
+interface APIConfigSectionProps {
+ onChanged: () => void;
+}
+
+export const APIConfigSection: React.FC = ({ onChanged }) => {
+ const { apiBaseUrl, setApiBaseUrl } = useSettingsStore();
+ const [isInputFocused, setIsInputFocused] = useState(false);
+ const inputRef = useRef(null);
+
+ const handleUrlChange = (url: string) => {
+ setApiBaseUrl(url);
+ onChanged();
+ };
+
+ return (
+
+
+ API 地址
+ setIsInputFocused(true)}
+ onBlur={() => setIsInputFocused(false)}
+ />
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ section: {
+ padding: 20,
+ marginBottom: 16,
+ borderRadius: 12,
+ borderWidth: 1,
+ borderColor: "#333",
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: "bold",
+ marginBottom: 8,
+ },
+ inputContainer: {
+ marginBottom: 12,
+ },
+ label: {
+ fontSize: 16,
+ marginBottom: 8,
+ color: "#ccc",
+ },
+ input: {
+ height: 50,
+ borderWidth: 2,
+ borderRadius: 8,
+ paddingHorizontal: 15,
+ fontSize: 16,
+ backgroundColor: "#3a3a3c",
+ color: "white",
+ borderColor: "transparent",
+ },
+ inputFocused: {
+ borderColor: "#007AFF",
+ shadowColor: "#007AFF",
+ shadowOffset: { width: 0, height: 0 },
+ shadowOpacity: 0.8,
+ shadowRadius: 10,
+ elevation: 5,
+ },
+});
diff --git a/components/settings/LiveStreamSection.tsx b/components/settings/LiveStreamSection.tsx
new file mode 100644
index 0000000..98a396b
--- /dev/null
+++ b/components/settings/LiveStreamSection.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { ThemedText } from '@/components/ThemedText';
+import { ThemedView } from '@/components/ThemedView';
+
+interface LiveStreamSectionProps {
+ onChanged: () => void;
+}
+
+export const LiveStreamSection: React.FC = ({ onChanged }) => {
+ return (
+
+ 直播源配置
+ 直播源配置功能即将上线
+
+ );
+};
+
+const styles = StyleSheet.create({
+ section: {
+ padding: 20,
+ marginBottom: 16,
+ borderRadius: 12,
+ borderWidth: 1,
+ borderColor: '#333',
+ },
+ sectionTitle: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ marginBottom: 16,
+ },
+ placeholder: {
+ fontSize: 14,
+ color: '#888',
+ fontStyle: 'italic',
+ },
+});
\ No newline at end of file
diff --git a/components/settings/PlaybackSourceSection.tsx b/components/settings/PlaybackSourceSection.tsx
new file mode 100644
index 0000000..8895461
--- /dev/null
+++ b/components/settings/PlaybackSourceSection.tsx
@@ -0,0 +1,37 @@
+import React from "react";
+import { StyleSheet } from "react-native";
+import { ThemedText } from "@/components/ThemedText";
+import { ThemedView } from "@/components/ThemedView";
+
+interface PlaybackSourceSectionProps {
+ onChanged: () => void;
+}
+
+export const PlaySourceSection: React.FC = ({ onChanged }) => {
+ return (
+
+ 播放源配置
+ 播放源配置功能即将上线
+
+ );
+};
+
+const styles = StyleSheet.create({
+ section: {
+ padding: 20,
+ marginBottom: 16,
+ borderRadius: 12,
+ borderWidth: 1,
+ borderColor: "#333",
+ },
+ sectionTitle: {
+ fontSize: 20,
+ fontWeight: "bold",
+ marginBottom: 16,
+ },
+ placeholder: {
+ fontSize: 14,
+ color: "#888",
+ fontStyle: "italic",
+ },
+});
diff --git a/components/settings/RemoteInputSection.tsx b/components/settings/RemoteInputSection.tsx
new file mode 100644
index 0000000..7e59d7b
--- /dev/null
+++ b/components/settings/RemoteInputSection.tsx
@@ -0,0 +1,121 @@
+import React from "react";
+import { View, Switch, StyleSheet } from "react-native";
+import { ThemedText } from "@/components/ThemedText";
+import { ThemedView } from "@/components/ThemedView";
+import { useSettingsStore } from "@/stores/settingsStore";
+import { useRemoteControlStore } from "@/stores/remoteControlStore";
+
+interface RemoteInputSectionProps {
+ onChanged: () => void;
+}
+
+export const RemoteInputSection: React.FC = ({ onChanged }) => {
+ const { remoteInputEnabled, setRemoteInputEnabled } = useSettingsStore();
+ const { isServerRunning, serverUrl, error } = useRemoteControlStore();
+
+ const handleToggle = async (enabled: boolean) => {
+ setRemoteInputEnabled(enabled);
+ onChanged();
+ };
+
+ return (
+
+
+
+ 启用远程输入
+
+
+
+
+ {remoteInputEnabled && (
+
+
+ 服务状态:
+
+ {isServerRunning ? "运行中" : "已停止"}
+
+
+
+ {serverUrl && (
+
+ 访问地址:
+ {serverUrl}
+
+ )}
+
+ {error && (
+
+ 错误:
+ {error}
+
+ )}
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ section: {
+ padding: 20,
+ marginBottom: 16,
+ borderRadius: 12,
+ borderWidth: 1,
+ borderColor: "#333",
+ },
+ sectionTitle: {
+ fontSize: 20,
+ fontWeight: "bold",
+ marginBottom: 16,
+ },
+ settingItem: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ paddingVertical: 12,
+ },
+ settingInfo: {
+ flex: 1,
+ },
+ settingName: {
+ fontSize: 16,
+ fontWeight: "bold",
+ marginBottom: 4,
+ },
+ settingDescription: {
+ fontSize: 14,
+ color: "#888",
+ },
+ statusContainer: {
+ marginTop: 16,
+ padding: 16,
+ backgroundColor: "#2a2a2c",
+ borderRadius: 8,
+ },
+ statusItem: {
+ flexDirection: "row",
+ marginBottom: 8,
+ },
+ statusLabel: {
+ fontSize: 14,
+ color: "#ccc",
+ minWidth: 80,
+ },
+ statusValue: {
+ fontSize: 14,
+ flex: 1,
+ },
+ actionButtons: {
+ flexDirection: "row",
+ gap: 12,
+ marginTop: 12,
+ },
+ actionButton: {
+ flex: 1,
+ },
+});
diff --git a/docs/SETTINGS_REFACTOR_PLAN.md b/docs/SETTINGS_REFACTOR_PLAN.md
new file mode 100644
index 0000000..f1a0a02
--- /dev/null
+++ b/docs/SETTINGS_REFACTOR_PLAN.md
@@ -0,0 +1,131 @@
+# 设置页面重构方案
+
+## 目标
+1. 将设置从弹窗模式改为独立页面
+2. 新增直播源配置功能
+3. 新增远程输入开关配置
+4. 新增播放源启用配置
+
+## 现有架构分析
+
+### 当前设置相关文件:
+- `stores/settingsStore.ts` - 设置状态管理,目前只有API地址配置
+- `components/SettingsModal.tsx` - 设置弹窗组件
+- `stores/remoteControlStore.ts` - 远程控制状态管理
+
+### 现有功能:
+- API基础地址配置
+- 远程控制服务器(但未集成到设置中)
+
+## 重构方案
+
+### 1. 创建独立设置页面
+- 新建 `app/settings.tsx` 页面
+- 使用 Expo Router 的文件路由系统
+- 删除现有的 `SettingsModal.tsx` 组件
+
+### 2. 扩展设置Store
+在 `settingsStore.ts` 中新增以下配置项:
+```typescript
+interface SettingsState {
+ // 现有配置
+ apiBaseUrl: string;
+
+ // 新增配置项
+ liveStreamSources: LiveStreamSource[]; // 直播源配置
+ remoteInputEnabled: boolean; // 远程输入开关
+ playbackSourceConfig: PlaybackSourceConfig; // 播放源配置
+}
+
+interface LiveStreamSource {
+ id: string;
+ name: string;
+ url: string;
+ enabled: boolean;
+}
+
+interface PlaybackSourceConfig {
+ primarySource: string;
+ fallbackSources: string[];
+ enabledSources: string[];
+}
+```
+
+### 3. 设置页面UI结构
+```
+设置页面 (app/settings.tsx)
+├── API 配置区域
+│ └── API 基础地址输入框
+├── 直播源配置区域
+│ ├── 直播源列表
+│ ├── 添加直播源按钮
+│ └── 编辑/删除直播源功能
+├── 远程输入配置区域
+│ └── 远程输入开关
+└── 播放源配置区域
+ ├── 主播放源选择
+ ├── 备用播放源配置
+ └── 启用的播放源选择
+```
+
+### 4. 组件设计
+- 使用 TV 适配的组件和样式
+- 实现焦点管理和遥控器导航
+- 遵循现有的设计规范(ThemedView, ThemedText, StyledButton)
+
+### 5. 导航集成
+- 在主页面添加设置入口
+- 使用 Expo Router 的 router.push('/settings') 进行导航
+
+## 实施步骤
+
+1. **扩展 settingsStore.ts**
+ - 添加新的状态接口
+ - 实现新配置项的增删改查方法
+ - 集成本地存储
+
+2. **创建设置页面**
+ - 新建 `app/settings.tsx`
+ - 实现基础页面结构和导航
+
+3. **实现配置组件**
+ - API 配置组件(复用现有逻辑)
+ - 直播源配置组件
+ - 远程输入开关组件
+ - 播放源配置组件
+
+4. **集成远程控制**
+ - 将远程控制功能集成到设置页面
+ - 统一管理所有设置项
+
+5. **更新导航**
+ - 在主页面添加设置入口
+ - 移除现有的设置弹窗触发逻辑
+
+6. **测试验证**
+ - 测试所有配置项的保存和加载
+ - 验证TV平台的交互体验
+ - 确保配置项生效
+
+## 技术考虑
+
+### TV平台适配
+- 使用 `useTVRemoteHandler` 处理遥控器事件
+- 实现合适的焦点管理
+- 确保所有交互元素可通过遥控器操作
+
+### 数据持久化
+- 使用现有的 `SettingsManager` 进行本地存储
+- 确保新配置项能正确保存和恢复
+
+### 向后兼容
+- 保持现有API配置功能不变
+- 为新配置项提供默认值
+- 处理旧版本设置数据的迁移
+
+## 预期收益
+
+1. **更好的用户体验**:独立页面提供更多空间展示配置选项
+2. **功能扩展性**:为未来添加更多配置项提供良好基础
+3. **代码组织**:将设置相关功能集中管理
+4. **TV平台适配**:更好的遥控器交互体验
\ No newline at end of file
diff --git a/services/remoteControlService.ts b/services/remoteControlService.ts
index cbde133..94f196a 100644
--- a/services/remoteControlService.ts
+++ b/services/remoteControlService.ts
@@ -119,11 +119,6 @@ class RemoteControlService {
public async startServer(): Promise {
console.log('[RemoteControl] Attempting to start server...');
- if (this.httpServer.getIsRunning()) {
- console.log('[RemoteControl] Server is already running.');
- throw new Error('Server is already running.');
- }
-
try {
const url = await this.httpServer.start();
console.log(`[RemoteControl] Server started successfully at: ${url}`);
diff --git a/services/tcpHttpServer.ts b/services/tcpHttpServer.ts
index 174ba82..515dcd9 100644
--- a/services/tcpHttpServer.ts
+++ b/services/tcpHttpServer.ts
@@ -96,10 +96,6 @@ class TCPHttpServer {
}
public async start(): Promise {
- if (this.isRunning) {
- throw new Error('Server is already running');
- }
-
const netState = await NetInfo.fetch();
let ipAddress: string | null = null;
@@ -111,6 +107,11 @@ class TCPHttpServer {
throw new Error('无法获取IP地址,请确认设备已连接到WiFi或以太网。');
}
+ if (this.isRunning) {
+ console.log('[TCPHttpServer] Server is already running.');
+ return `http://${ipAddress}:${PORT}`;
+ }
+
return new Promise((resolve, reject) => {
try {
this.server = TcpSocket.createServer((socket: TcpSocket.Socket) => {
diff --git a/stores/settingsStore.ts b/stores/settingsStore.ts
index 5a2df24..4d6ba0e 100644
--- a/stores/settingsStore.ts
+++ b/stores/settingsStore.ts
@@ -3,11 +3,33 @@ import { SettingsManager } from '@/services/storage';
import { api } from '@/services/api';
import useHomeStore from './homeStore';
+export interface LiveStreamSource {
+ id: string;
+ name: string;
+ url: string;
+ enabled: boolean;
+}
+
+export interface PlaybackSourceConfig {
+ primarySource: string;
+ fallbackSources: string[];
+ enabledSources: string[];
+}
+
interface SettingsState {
apiBaseUrl: string;
+ liveStreamSources: LiveStreamSource[];
+ remoteInputEnabled: boolean;
+ playbackSourceConfig: PlaybackSourceConfig;
isModalVisible: boolean;
loadSettings: () => Promise;
setApiBaseUrl: (url: string) => void;
+ setLiveStreamSources: (sources: LiveStreamSource[]) => void;
+ addLiveStreamSource: (source: Omit) => void;
+ removeLiveStreamSource: (id: string) => void;
+ updateLiveStreamSource: (id: string, updates: Partial) => void;
+ setRemoteInputEnabled: (enabled: boolean) => void;
+ setPlaybackSourceConfig: (config: PlaybackSourceConfig) => void;
saveSettings: () => Promise;
showModal: () => void;
hideModal: () => void;
@@ -15,16 +37,60 @@ interface SettingsState {
export const useSettingsStore = create((set, get) => ({
apiBaseUrl: 'https://orion-tv.edu.deal',
+ liveStreamSources: [],
+ remoteInputEnabled: false,
+ playbackSourceConfig: {
+ primarySource: 'default',
+ fallbackSources: [],
+ enabledSources: ['default'],
+ },
isModalVisible: false,
loadSettings: async () => {
const settings = await SettingsManager.get();
- set({ apiBaseUrl: settings.apiBaseUrl });
+ set({
+ apiBaseUrl: settings.apiBaseUrl,
+ liveStreamSources: settings.liveStreamSources || [],
+ remoteInputEnabled: settings.remoteInputEnabled || false,
+ playbackSourceConfig: settings.playbackSourceConfig || {
+ primarySource: 'default',
+ fallbackSources: [],
+ enabledSources: ['default'],
+ },
+ });
api.setBaseUrl(settings.apiBaseUrl);
},
setApiBaseUrl: (url) => set({ apiBaseUrl: url }),
+ setLiveStreamSources: (sources) => set({ liveStreamSources: sources }),
+ addLiveStreamSource: (source) => {
+ const { liveStreamSources } = get();
+ const newSource = {
+ ...source,
+ id: Date.now().toString(),
+ };
+ set({ liveStreamSources: [...liveStreamSources, newSource] });
+ },
+ removeLiveStreamSource: (id) => {
+ const { liveStreamSources } = get();
+ set({ liveStreamSources: liveStreamSources.filter(s => s.id !== id) });
+ },
+ updateLiveStreamSource: (id, updates) => {
+ const { liveStreamSources } = get();
+ set({
+ liveStreamSources: liveStreamSources.map(s =>
+ s.id === id ? { ...s, ...updates } : s
+ )
+ });
+ },
+ setRemoteInputEnabled: (enabled) => set({ remoteInputEnabled: enabled }),
+ setPlaybackSourceConfig: (config) => set({ playbackSourceConfig: config }),
saveSettings: async () => {
- const { apiBaseUrl } = get();
- await SettingsManager.save({ apiBaseUrl });
+ const { apiBaseUrl, liveStreamSources, remoteInputEnabled, playbackSourceConfig } = get();
+ await SettingsManager.save({
+ apiBaseUrl,
+ liveStreamSources,
+ remoteInputEnabled,
+ playbackSourceConfig,
+ });
api.setBaseUrl(apiBaseUrl);
set({ isModalVisible: false });
useHomeStore.getState().fetchInitialData();