mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
feat: Enable remote input functionality and enhance settings management for remote control
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import { View, TextInput, StyleSheet, FlatList, ActivityIndicator, Text, Keyboard } from "react-native";
|
import { View, TextInput, StyleSheet, FlatList, ActivityIndicator, Alert, Keyboard } from "react-native";
|
||||||
import { ThemedView } from "@/components/ThemedView";
|
import { ThemedView } from "@/components/ThemedView";
|
||||||
import { ThemedText } from "@/components/ThemedText";
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
import VideoCard from "@/components/VideoCard.tv";
|
import VideoCard from "@/components/VideoCard.tv";
|
||||||
@@ -8,6 +8,8 @@ import { Search, QrCode } from "lucide-react-native";
|
|||||||
import { StyledButton } from "@/components/StyledButton";
|
import { StyledButton } from "@/components/StyledButton";
|
||||||
import { useRemoteControlStore } from "@/stores/remoteControlStore";
|
import { useRemoteControlStore } from "@/stores/remoteControlStore";
|
||||||
import { RemoteControlModal } from "@/components/RemoteControlModal";
|
import { RemoteControlModal } from "@/components/RemoteControlModal";
|
||||||
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
|
import { useRouter } from "expo-router";
|
||||||
|
|
||||||
export default function SearchScreen() {
|
export default function SearchScreen() {
|
||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
@@ -18,13 +20,17 @@ export default function SearchScreen() {
|
|||||||
const colorScheme = "dark"; // Replace with useColorScheme() if needed
|
const colorScheme = "dark"; // Replace with useColorScheme() if needed
|
||||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
const { showModal: showRemoteModal, lastMessage } = useRemoteControlStore();
|
const { showModal: showRemoteModal, lastMessage } = useRemoteControlStore();
|
||||||
|
const { remoteInputEnabled } = useSettingsStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastMessage) {
|
if (lastMessage) {
|
||||||
|
console.log("Received remote input:", lastMessage);
|
||||||
const realMessage = lastMessage.split("_")[0];
|
const realMessage = lastMessage.split("_")[0];
|
||||||
setKeyword(realMessage);
|
setKeyword(realMessage);
|
||||||
handleSearch(realMessage);
|
handleSearch(realMessage);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [lastMessage]);
|
}, [lastMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -61,6 +67,17 @@ export default function SearchScreen() {
|
|||||||
|
|
||||||
const onSearchPress = () => handleSearch();
|
const onSearchPress = () => handleSearch();
|
||||||
|
|
||||||
|
const handleQrPress = () => {
|
||||||
|
if (!remoteInputEnabled) {
|
||||||
|
Alert.alert("远程输入未启用", "请先在设置页面中启用远程输入功能", [
|
||||||
|
{ text: "取消", style: "cancel" },
|
||||||
|
{ text: "去设置", onPress: () => router.push("/settings") },
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showRemoteModal();
|
||||||
|
};
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: SearchResult }) => (
|
const renderItem = ({ item }: { item: SearchResult }) => (
|
||||||
<VideoCard
|
<VideoCard
|
||||||
id={item.id.toString()}
|
id={item.id.toString()}
|
||||||
@@ -98,7 +115,7 @@ export default function SearchScreen() {
|
|||||||
<StyledButton style={styles.searchButton} onPress={onSearchPress}>
|
<StyledButton style={styles.searchButton} onPress={onSearchPress}>
|
||||||
<Search size={24} color={colorScheme === "dark" ? "white" : "black"} />
|
<Search size={24} color={colorScheme === "dark" ? "white" : "black"} />
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
<StyledButton style={styles.qrButton} onPress={showRemoteModal}>
|
<StyledButton style={styles.qrButton} onPress={handleQrPress}>
|
||||||
<QrCode size={24} color={colorScheme === "dark" ? "white" : "black"} />
|
<QrCode size={24} color={colorScheme === "dark" ? "white" : "black"} />
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import { ThemedView } from "@/components/ThemedView";
|
|||||||
import { StyledButton } from "@/components/StyledButton";
|
import { StyledButton } from "@/components/StyledButton";
|
||||||
import { useThemeColor } from "@/hooks/useThemeColor";
|
import { useThemeColor } from "@/hooks/useThemeColor";
|
||||||
import { useSettingsStore } from "@/stores/settingsStore";
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
|
import { useRemoteControlStore } from "@/stores/remoteControlStore";
|
||||||
import { APIConfigSection } from "@/components/settings/APIConfigSection";
|
import { APIConfigSection } from "@/components/settings/APIConfigSection";
|
||||||
import { LiveStreamSection } from "@/components/settings/LiveStreamSection";
|
import { LiveStreamSection } from "@/components/settings/LiveStreamSection";
|
||||||
import { RemoteInputSection } from "@/components/settings/RemoteInputSection";
|
import { RemoteInputSection } from "@/components/settings/RemoteInputSection";
|
||||||
import { VideoSourceSection } from "@/components/settings/VideoSourceSection";
|
import { VideoSourceSection } from "@/components/settings/VideoSourceSection";
|
||||||
|
|
||||||
export default function SettingsScreen() {
|
export default function SettingsScreen() {
|
||||||
const { loadSettings, saveSettings } = useSettingsStore();
|
const { loadSettings, saveSettings, setApiBaseUrl, setM3uUrl } = useSettingsStore();
|
||||||
|
const { lastMessage } = useRemoteControlStore();
|
||||||
const backgroundColor = useThemeColor({}, "background");
|
const backgroundColor = useThemeColor({}, "background");
|
||||||
|
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
@@ -20,11 +22,32 @@ export default function SettingsScreen() {
|
|||||||
const [currentFocusIndex, setCurrentFocusIndex] = useState(0);
|
const [currentFocusIndex, setCurrentFocusIndex] = useState(0);
|
||||||
|
|
||||||
const saveButtonRef = useRef<any>(null);
|
const saveButtonRef = useRef<any>(null);
|
||||||
|
const apiSectionRef = useRef<any>(null);
|
||||||
|
const liveStreamSectionRef = useRef<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
}, [loadSettings]);
|
}, [loadSettings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastMessage) {
|
||||||
|
const realMessage = lastMessage.split("_")[0];
|
||||||
|
handleRemoteInput(realMessage);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [lastMessage]);
|
||||||
|
|
||||||
|
const handleRemoteInput = (message: string) => {
|
||||||
|
// Handle remote input based on currently focused section
|
||||||
|
if (currentFocusIndex === 1 && apiSectionRef.current) {
|
||||||
|
// API Config Section
|
||||||
|
setApiBaseUrl(message);
|
||||||
|
} else if (currentFocusIndex === 2 && liveStreamSectionRef.current) {
|
||||||
|
// Live Stream Section
|
||||||
|
setM3uUrl(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -47,11 +70,19 @@ export default function SettingsScreen() {
|
|||||||
key: "remote",
|
key: "remote",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: <APIConfigSection onChanged={markAsChanged} onFocus={() => setCurrentFocusIndex(1)} />,
|
component: (
|
||||||
|
<APIConfigSection ref={apiSectionRef} onChanged={markAsChanged} onFocus={() => setCurrentFocusIndex(1)} />
|
||||||
|
),
|
||||||
key: "api",
|
key: "api",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: <LiveStreamSection onChanged={markAsChanged} onFocus={() => setCurrentFocusIndex(2)} />,
|
component: (
|
||||||
|
<LiveStreamSection
|
||||||
|
ref={liveStreamSectionRef}
|
||||||
|
onChanged={markAsChanged}
|
||||||
|
onFocus={() => setCurrentFocusIndex(2)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
key: "livestream",
|
key: "livestream",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState, useRef, useImperativeHandle, forwardRef } from "react";
|
||||||
import { View, TextInput, StyleSheet, Pressable, Animated } from "react-native";
|
import { View, TextInput, StyleSheet, Animated } from "react-native";
|
||||||
import { useTVEventHandler } from "react-native";
|
import { useTVEventHandler } from "react-native";
|
||||||
import { ThemedText } from "@/components/ThemedText";
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
import { SettingsSection } from "./SettingsSection";
|
import { SettingsSection } from "./SettingsSection";
|
||||||
import { useSettingsStore } from "@/stores/settingsStore";
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
|
import { useRemoteControlStore } from "@/stores/remoteControlStore";
|
||||||
import { useButtonAnimation } from "@/hooks/useAnimation";
|
import { useButtonAnimation } from "@/hooks/useAnimation";
|
||||||
|
|
||||||
interface APIConfigSectionProps {
|
interface APIConfigSectionProps {
|
||||||
@@ -12,68 +13,99 @@ interface APIConfigSectionProps {
|
|||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const APIConfigSection: React.FC<APIConfigSectionProps> = ({ onChanged, onFocus, onBlur }) => {
|
export interface APIConfigSectionRef {
|
||||||
const { apiBaseUrl, setApiBaseUrl } = useSettingsStore();
|
setInputValue: (value: string) => void;
|
||||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
}
|
||||||
const [isSectionFocused, setIsSectionFocused] = useState(false);
|
|
||||||
const inputRef = useRef<TextInput>(null);
|
|
||||||
const inputAnimationStyle = useButtonAnimation(isSectionFocused, 1.01);
|
|
||||||
|
|
||||||
const handleUrlChange = (url: string) => {
|
export const APIConfigSection = forwardRef<APIConfigSectionRef, APIConfigSectionProps>(
|
||||||
setApiBaseUrl(url);
|
({ onChanged, onFocus, onBlur }, ref) => {
|
||||||
onChanged();
|
const { apiBaseUrl, setApiBaseUrl, remoteInputEnabled } = useSettingsStore();
|
||||||
};
|
const { serverUrl } = useRemoteControlStore();
|
||||||
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
|
const [isSectionFocused, setIsSectionFocused] = useState(false);
|
||||||
|
const inputRef = useRef<TextInput>(null);
|
||||||
|
const inputAnimationStyle = useButtonAnimation(isSectionFocused, 1.01);
|
||||||
|
|
||||||
const handleSectionFocus = () => {
|
const handleUrlChange = (url: string) => {
|
||||||
setIsSectionFocused(true);
|
setApiBaseUrl(url);
|
||||||
onFocus?.();
|
onChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSectionBlur = () => {
|
useImperativeHandle(ref, () => ({
|
||||||
setIsSectionFocused(false);
|
setInputValue: (value: string) => {
|
||||||
onBlur?.();
|
setApiBaseUrl(value);
|
||||||
};
|
onChanged();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// TV遥控器事件处理
|
const handleSectionFocus = () => {
|
||||||
const handleTVEvent = React.useCallback(
|
setIsSectionFocused(true);
|
||||||
(event: any) => {
|
onFocus?.();
|
||||||
if (isSectionFocused && event.eventType === "select") {
|
};
|
||||||
inputRef.current?.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isSectionFocused]
|
|
||||||
);
|
|
||||||
|
|
||||||
useTVEventHandler(handleTVEvent);
|
const handleSectionBlur = () => {
|
||||||
|
setIsSectionFocused(false);
|
||||||
|
onBlur?.();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
// TV遥控器事件处理
|
||||||
<SettingsSection focusable onFocus={handleSectionFocus} onBlur={handleSectionBlur}>
|
const handleTVEvent = React.useCallback(
|
||||||
<View style={styles.inputContainer}>
|
(event: any) => {
|
||||||
<ThemedText style={styles.sectionTitle}>API 地址</ThemedText>
|
if (isSectionFocused && event.eventType === "select") {
|
||||||
<Animated.View style={inputAnimationStyle}>
|
inputRef.current?.focus();
|
||||||
<TextInput
|
}
|
||||||
ref={inputRef}
|
},
|
||||||
style={[styles.input, isInputFocused && styles.inputFocused]}
|
[isSectionFocused]
|
||||||
value={apiBaseUrl}
|
);
|
||||||
onChangeText={handleUrlChange}
|
|
||||||
placeholder="输入 API 地址"
|
useTVEventHandler(handleTVEvent);
|
||||||
placeholderTextColor="#888"
|
|
||||||
autoCapitalize="none"
|
return (
|
||||||
autoCorrect={false}
|
<SettingsSection focusable onFocus={handleSectionFocus} onBlur={handleSectionBlur}>
|
||||||
onFocus={() => setIsInputFocused(true)}
|
<View style={styles.inputContainer}>
|
||||||
onBlur={() => setIsInputFocused(false)}
|
<View style={styles.titleContainer}>
|
||||||
/>
|
<ThemedText style={styles.sectionTitle}>API 地址</ThemedText>
|
||||||
</Animated.View>
|
{remoteInputEnabled && serverUrl && (
|
||||||
</View>
|
<ThemedText style={styles.subtitle}>用手机访问 {serverUrl},可远程输入</ThemedText>
|
||||||
</SettingsSection>
|
)}
|
||||||
);
|
</View>
|
||||||
};
|
<Animated.View style={inputAnimationStyle}>
|
||||||
|
<TextInput
|
||||||
|
ref={inputRef}
|
||||||
|
style={[styles.input, isInputFocused && styles.inputFocused]}
|
||||||
|
value={apiBaseUrl}
|
||||||
|
onChangeText={handleUrlChange}
|
||||||
|
placeholder="输入 API 地址"
|
||||||
|
placeholderTextColor="#888"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
onFocus={() => setIsInputFocused(true)}
|
||||||
|
onBlur={() => setIsInputFocused(false)}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
</SettingsSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
APIConfigSection.displayName = "APIConfigSection";
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
titleContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
marginBottom: 8,
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#888",
|
||||||
|
fontStyle: "italic",
|
||||||
},
|
},
|
||||||
inputContainer: {
|
inputContainer: {
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef, useImperativeHandle, forwardRef } from "react";
|
||||||
import { View, TextInput, StyleSheet, Animated } from 'react-native';
|
import { View, TextInput, StyleSheet, Animated } from "react-native";
|
||||||
import { useTVEventHandler } from 'react-native';
|
import { useTVEventHandler } from "react-native";
|
||||||
import { ThemedText } from '@/components/ThemedText';
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
import { SettingsSection } from './SettingsSection';
|
import { SettingsSection } from "./SettingsSection";
|
||||||
import { useSettingsStore } from '@/stores/settingsStore';
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
import { useButtonAnimation } from '@/hooks/useAnimation';
|
import { useRemoteControlStore } from "@/stores/remoteControlStore";
|
||||||
|
import { useButtonAnimation } from "@/hooks/useAnimation";
|
||||||
|
|
||||||
interface LiveStreamSectionProps {
|
interface LiveStreamSectionProps {
|
||||||
onChanged: () => void;
|
onChanged: () => void;
|
||||||
@@ -12,67 +13,98 @@ interface LiveStreamSectionProps {
|
|||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LiveStreamSection: React.FC<LiveStreamSectionProps> = ({ onChanged, onFocus, onBlur }) => {
|
export interface LiveStreamSectionRef {
|
||||||
const { m3uUrl, setM3uUrl } = useSettingsStore();
|
setInputValue: (value: string) => void;
|
||||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
}
|
||||||
const [isSectionFocused, setIsSectionFocused] = useState(false);
|
|
||||||
const inputRef = useRef<TextInput>(null);
|
|
||||||
const inputAnimationStyle = useButtonAnimation(isSectionFocused, 1.01);
|
|
||||||
|
|
||||||
const handleUrlChange = (url: string) => {
|
export const LiveStreamSection = forwardRef<LiveStreamSectionRef, LiveStreamSectionProps>(
|
||||||
setM3uUrl(url);
|
({ onChanged, onFocus, onBlur }, ref) => {
|
||||||
onChanged();
|
const { m3uUrl, setM3uUrl, remoteInputEnabled } = useSettingsStore();
|
||||||
};
|
const { serverUrl } = useRemoteControlStore();
|
||||||
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
|
const [isSectionFocused, setIsSectionFocused] = useState(false);
|
||||||
|
const inputRef = useRef<TextInput>(null);
|
||||||
|
const inputAnimationStyle = useButtonAnimation(isSectionFocused, 1.01);
|
||||||
|
|
||||||
const handleSectionFocus = () => {
|
const handleUrlChange = (url: string) => {
|
||||||
setIsSectionFocused(true);
|
setM3uUrl(url);
|
||||||
onFocus?.();
|
onChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSectionBlur = () => {
|
useImperativeHandle(ref, () => ({
|
||||||
setIsSectionFocused(false);
|
setInputValue: (value: string) => {
|
||||||
onBlur?.();
|
setM3uUrl(value);
|
||||||
};
|
onChanged();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const handleTVEvent = React.useCallback(
|
const handleSectionFocus = () => {
|
||||||
(event: any) => {
|
setIsSectionFocused(true);
|
||||||
if (isSectionFocused && event.eventType === "select") {
|
onFocus?.();
|
||||||
inputRef.current?.focus();
|
};
|
||||||
}
|
|
||||||
},
|
|
||||||
[isSectionFocused]
|
|
||||||
);
|
|
||||||
|
|
||||||
useTVEventHandler(handleTVEvent);
|
const handleSectionBlur = () => {
|
||||||
|
setIsSectionFocused(false);
|
||||||
|
onBlur?.();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const handleTVEvent = React.useCallback(
|
||||||
<SettingsSection focusable onFocus={handleSectionFocus} onBlur={handleSectionBlur}>
|
(event: any) => {
|
||||||
<View style={styles.inputContainer}>
|
if (isSectionFocused && event.eventType === "select") {
|
||||||
<ThemedText style={styles.sectionTitle}>直播源地址</ThemedText>
|
inputRef.current?.focus();
|
||||||
<Animated.View style={inputAnimationStyle}>
|
}
|
||||||
<TextInput
|
},
|
||||||
ref={inputRef}
|
[isSectionFocused]
|
||||||
style={[styles.input, isInputFocused && styles.inputFocused]}
|
);
|
||||||
value={m3uUrl}
|
|
||||||
onChangeText={handleUrlChange}
|
useTVEventHandler(handleTVEvent);
|
||||||
placeholder="输入 M3U 直播源地址"
|
|
||||||
placeholderTextColor="#888"
|
return (
|
||||||
autoCapitalize="none"
|
<SettingsSection focusable onFocus={handleSectionFocus} onBlur={handleSectionBlur}>
|
||||||
autoCorrect={false}
|
<View style={styles.inputContainer}>
|
||||||
onFocus={() => setIsInputFocused(true)}
|
<View style={styles.titleContainer}>
|
||||||
onBlur={() => setIsInputFocused(false)}
|
<ThemedText style={styles.sectionTitle}>直播源地址</ThemedText>
|
||||||
/>
|
{remoteInputEnabled && serverUrl && (
|
||||||
</Animated.View>
|
<ThemedText style={styles.subtitle}>用手机访问 {serverUrl},可远程输入</ThemedText>
|
||||||
</View>
|
)}
|
||||||
</SettingsSection>
|
</View>
|
||||||
);
|
<Animated.View style={inputAnimationStyle}>
|
||||||
};
|
<TextInput
|
||||||
|
ref={inputRef}
|
||||||
|
style={[styles.input, isInputFocused && styles.inputFocused]}
|
||||||
|
value={m3uUrl}
|
||||||
|
onChangeText={handleUrlChange}
|
||||||
|
placeholder="输入 M3U 直播源地址"
|
||||||
|
placeholderTextColor="#888"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
onFocus={() => setIsInputFocused(true)}
|
||||||
|
onBlur={() => setIsInputFocused(false)}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
</SettingsSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
LiveStreamSection.displayName = "LiveStreamSection";
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
titleContainer: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
marginBottom: 8,
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#888",
|
||||||
|
fontStyle: "italic",
|
||||||
},
|
},
|
||||||
inputContainer: {
|
inputContainer: {
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
@@ -83,16 +115,16 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
backgroundColor: '#3a3a3c',
|
backgroundColor: "#3a3a3c",
|
||||||
color: 'white',
|
color: "white",
|
||||||
borderColor: 'transparent',
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
inputFocused: {
|
inputFocused: {
|
||||||
borderColor: '#007AFF',
|
borderColor: "#007AFF",
|
||||||
shadowColor: '#007AFF',
|
shadowColor: "#007AFF",
|
||||||
shadowOffset: { width: 0, height: 0 },
|
shadowOffset: { width: 0, height: 0 },
|
||||||
shadowOpacity: 0.8,
|
shadowOpacity: 0.8,
|
||||||
shadowRadius: 10,
|
shadowRadius: 10,
|
||||||
elevation: 5,
|
elevation: 5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export class SettingsManager {
|
|||||||
static async get(): Promise<AppSettings> {
|
static async get(): Promise<AppSettings> {
|
||||||
const defaultSettings: AppSettings = {
|
const defaultSettings: AppSettings = {
|
||||||
apiBaseUrl: "https://orion-tv.edu.deal",
|
apiBaseUrl: "https://orion-tv.edu.deal",
|
||||||
remoteInputEnabled: false,
|
remoteInputEnabled: true,
|
||||||
videoSource: {
|
videoSource: {
|
||||||
enabledAll: true,
|
enabledAll: true,
|
||||||
sources: {},
|
sources: {},
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
|
|||||||
if (get().isServerRunning) {
|
if (get().isServerRunning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
remoteControlService.init({
|
||||||
|
onMessage: (message: string) => {
|
||||||
|
console.log('[RemoteControlStore] Received message:', message);
|
||||||
|
set({ lastMessage: message });
|
||||||
|
},
|
||||||
|
onHandshake: () => {
|
||||||
|
console.log('[RemoteControlStore] Handshake successful');
|
||||||
|
set({ isModalVisible: false })
|
||||||
|
},
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const url = await remoteControlService.startServer();
|
const url = await remoteControlService.startServer();
|
||||||
console.log(`[RemoteControlStore] Server started, URL: ${url}`);
|
console.log(`[RemoteControlStore] Server started, URL: ${url}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user