mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-03-02 08:54:43 +08:00
Merge branch 'v1.3.0' of github.com:zimplexing/OrionTV into v1.3.0
This commit is contained in:
@@ -128,7 +128,7 @@ export default function HomeScreen() {
|
|||||||
|
|
||||||
// TV端和平板端的顶部导航
|
// TV端和平板端的顶部导航
|
||||||
const renderHeader = () => {
|
const renderHeader = () => {
|
||||||
if (deviceType === 'mobile') {
|
if (deviceType === "mobile") {
|
||||||
// 移动端不显示顶部导航,使用底部Tab导航
|
// 移动端不显示顶部导航,使用底部Tab导航
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ export default function HomeScreen() {
|
|||||||
const dynamicStyles = StyleSheet.create({
|
const dynamicStyles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingTop: deviceType === 'mobile' ? insets.top : 40,
|
paddingTop: deviceType === "mobile" ? 0 : 40,
|
||||||
},
|
},
|
||||||
headerContainer: {
|
headerContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -181,7 +181,7 @@ export default function HomeScreen() {
|
|||||||
marginBottom: spacing,
|
marginBottom: spacing,
|
||||||
},
|
},
|
||||||
headerTitle: {
|
headerTitle: {
|
||||||
fontSize: deviceType === 'mobile' ? 24 : deviceType === 'tablet' ? 28 : 32,
|
fontSize: deviceType === "mobile" ? 24 : deviceType === "tablet" ? 28 : 32,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
paddingTop: 16,
|
paddingTop: 16,
|
||||||
},
|
},
|
||||||
@@ -200,13 +200,13 @@ export default function HomeScreen() {
|
|||||||
paddingHorizontal: spacing,
|
paddingHorizontal: spacing,
|
||||||
},
|
},
|
||||||
categoryButton: {
|
categoryButton: {
|
||||||
paddingHorizontal: deviceType === 'tv' ? spacing / 4 : spacing / 2,
|
paddingHorizontal: deviceType === "tv" ? spacing / 4 : spacing / 2,
|
||||||
paddingVertical: deviceType === 'tv' ? spacing / 4 : spacing / 2,
|
paddingVertical: spacing / 2,
|
||||||
borderRadius: deviceType === 'mobile' ? 6 : 8,
|
borderRadius: deviceType === "mobile" ? 6 : 8,
|
||||||
marginHorizontal: deviceType === 'tv' ? spacing / 4 : spacing / 2,
|
marginHorizontal: deviceType === "tv" ? spacing / 4 : spacing / 2, // TV端使用更小的间距
|
||||||
},
|
},
|
||||||
categoryText: {
|
categoryText: {
|
||||||
fontSize: deviceType === 'mobile' ? 14 : 16,
|
fontSize: deviceType === "mobile" ? 14 : 16,
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
},
|
},
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
@@ -217,8 +217,8 @@ export default function HomeScreen() {
|
|||||||
const content = (
|
const content = (
|
||||||
<ThemedView style={[commonStyles.container, dynamicStyles.container]}>
|
<ThemedView style={[commonStyles.container, dynamicStyles.container]}>
|
||||||
{/* 状态栏 */}
|
{/* 状态栏 */}
|
||||||
{deviceType === 'mobile' && <StatusBar barStyle="light-content" />}
|
{deviceType === "mobile" && <StatusBar barStyle="light-content" />}
|
||||||
|
|
||||||
{/* 顶部导航 */}
|
{/* 顶部导航 */}
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
|
|
||||||
@@ -291,13 +291,9 @@ export default function HomeScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 根据设备类型决定是否包装在响应式导航中
|
// 根据设备类型决定是否包装在响应式导航中
|
||||||
if (deviceType === 'tv') {
|
if (deviceType === "tv") {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <ResponsiveNavigation>{content}</ResponsiveNavigation>;
|
||||||
<ResponsiveNavigation>
|
}
|
||||||
{content}
|
|
||||||
</ResponsiveNavigation>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function SearchScreen() {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const textInputRef = useRef<TextInput>(null);
|
const textInputRef = useRef<TextInput>(null);
|
||||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
const { showModal: showRemoteModal, lastMessage } = useRemoteControlStore();
|
const { showModal: showRemoteModal, lastMessage, targetPage, clearMessage } = useRemoteControlStore();
|
||||||
const { remoteInputEnabled } = useSettingsStore();
|
const { remoteInputEnabled } = useSettingsStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -36,14 +36,15 @@ export default function SearchScreen() {
|
|||||||
const { deviceType, spacing } = responsiveConfig;
|
const { deviceType, spacing } = responsiveConfig;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastMessage) {
|
if (lastMessage && targetPage === 'search') {
|
||||||
console.log("Received remote input:", 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);
|
||||||
|
clearMessage(); // Clear the message after processing
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [lastMessage]);
|
}, [lastMessage, targetPage]);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// // Focus the text input when the screen loads
|
// // Focus the text input when the screen loads
|
||||||
@@ -87,10 +88,10 @@ export default function SearchScreen() {
|
|||||||
]);
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showRemoteModal();
|
showRemoteModal('search');
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = ({ item, index }: { item: SearchResult; index: number }) => (
|
const renderItem = ({ item }: { item: SearchResult; index: number }) => (
|
||||||
<VideoCard
|
<VideoCard
|
||||||
id={item.id.toString()}
|
id={item.id.toString()}
|
||||||
source={item.source}
|
source={item.source}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { DeviceUtils } from "@/utils/DeviceUtils";
|
|||||||
|
|
||||||
export default function SettingsScreen() {
|
export default function SettingsScreen() {
|
||||||
const { loadSettings, saveSettings, setApiBaseUrl, setM3uUrl } = useSettingsStore();
|
const { loadSettings, saveSettings, setApiBaseUrl, setM3uUrl } = useSettingsStore();
|
||||||
const { lastMessage } = useRemoteControlStore();
|
const { lastMessage, targetPage, clearMessage } = useRemoteControlStore();
|
||||||
const backgroundColor = useThemeColor({}, "background");
|
const backgroundColor = useThemeColor({}, "background");
|
||||||
|
|
||||||
// 响应式布局配置
|
// 响应式布局配置
|
||||||
@@ -44,12 +44,13 @@ export default function SettingsScreen() {
|
|||||||
}, [loadSettings]);
|
}, [loadSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastMessage) {
|
if (lastMessage && !targetPage) {
|
||||||
const realMessage = lastMessage.split("_")[0];
|
const realMessage = lastMessage.split("_")[0];
|
||||||
handleRemoteInput(realMessage);
|
handleRemoteInput(realMessage);
|
||||||
|
clearMessage(); // Clear the message after processing
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [lastMessage]);
|
}, [lastMessage, targetPage]);
|
||||||
|
|
||||||
const handleRemoteInput = (message: string) => {
|
const handleRemoteInput = (message: string) => {
|
||||||
// Handle remote input based on currently focused section
|
// Handle remote input based on currently focused section
|
||||||
@@ -133,10 +134,8 @@ export default function SettingsScreen() {
|
|||||||
// ),
|
// ),
|
||||||
// key: "videoSource",
|
// key: "videoSource",
|
||||||
// },
|
// },
|
||||||
Platform.OS === 'android' && {
|
Platform.OS === "android" && {
|
||||||
component: (
|
component: <UpdateSection />,
|
||||||
<UpdateSection />
|
|
||||||
),
|
|
||||||
key: "update",
|
key: "update",
|
||||||
},
|
},
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
@@ -144,8 +143,8 @@ export default function SettingsScreen() {
|
|||||||
// TV遥控器事件处理 - 仅在TV设备上启用
|
// TV遥控器事件处理 - 仅在TV设备上启用
|
||||||
const handleTVEvent = React.useCallback(
|
const handleTVEvent = React.useCallback(
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
if (deviceType !== 'tv') return;
|
if (deviceType !== "tv") return;
|
||||||
|
|
||||||
if (event.eventType === "down") {
|
if (event.eventType === "down") {
|
||||||
const nextIndex = Math.min(currentFocusIndex + 1, sections.length);
|
const nextIndex = Math.min(currentFocusIndex + 1, sections.length);
|
||||||
setCurrentFocusIndex(nextIndex);
|
setCurrentFocusIndex(nextIndex);
|
||||||
@@ -160,18 +159,15 @@ export default function SettingsScreen() {
|
|||||||
[currentFocusIndex, sections.length, deviceType]
|
[currentFocusIndex, sections.length, deviceType]
|
||||||
);
|
);
|
||||||
|
|
||||||
useTVEventHandler(deviceType === 'tv' ? handleTVEvent : () => {});
|
useTVEventHandler(deviceType === "tv" ? handleTVEvent : () => {});
|
||||||
|
|
||||||
// 动态样式
|
// 动态样式
|
||||||
const dynamicStyles = createResponsiveStyles(deviceType, spacing);
|
const dynamicStyles = createResponsiveStyles(deviceType, spacing);
|
||||||
|
|
||||||
const renderSettingsContent = () => (
|
const renderSettingsContent = () => (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView style={{ flex: 1, backgroundColor }} behavior={Platform.OS === "ios" ? "padding" : "height"}>
|
||||||
style={{ flex: 1, backgroundColor }}
|
|
||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
||||||
>
|
|
||||||
<ThemedView style={[commonStyles.container, dynamicStyles.container]}>
|
<ThemedView style={[commonStyles.container, dynamicStyles.container]}>
|
||||||
{deviceType === 'tv' && (
|
{deviceType === "tv" && (
|
||||||
<View style={dynamicStyles.header}>
|
<View style={dynamicStyles.header}>
|
||||||
<ThemedText style={dynamicStyles.title}>设置</ThemedText>
|
<ThemedText style={dynamicStyles.title}>设置</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
@@ -186,7 +182,7 @@ export default function SettingsScreen() {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
keyExtractor={(item) => item ? item.key : 'default'}
|
keyExtractor={(item) => (item ? item.key : "default")}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={dynamicStyles.listContent}
|
contentContainerStyle={dynamicStyles.listContent}
|
||||||
/>
|
/>
|
||||||
@@ -198,10 +194,7 @@ export default function SettingsScreen() {
|
|||||||
onPress={handleSave}
|
onPress={handleSave}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
disabled={!hasChanges || isLoading}
|
disabled={!hasChanges || isLoading}
|
||||||
style={[
|
style={[dynamicStyles.saveButton, (!hasChanges || isLoading) && dynamicStyles.disabledButton]}
|
||||||
dynamicStyles.saveButton,
|
|
||||||
(!hasChanges || isLoading) && dynamicStyles.disabledButton
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
@@ -209,7 +202,7 @@ export default function SettingsScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 根据设备类型决定是否包装在响应式导航中
|
// 根据设备类型决定是否包装在响应式导航中
|
||||||
if (deviceType === 'tv') {
|
if (deviceType === "tv") {
|
||||||
return renderSettingsContent();
|
return renderSettingsContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,9 +215,9 @@ export default function SettingsScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createResponsiveStyles = (deviceType: string, spacing: number) => {
|
const createResponsiveStyles = (deviceType: string, spacing: number) => {
|
||||||
const isMobile = deviceType === 'mobile';
|
const isMobile = deviceType === "mobile";
|
||||||
const isTablet = deviceType === 'tablet';
|
const isTablet = deviceType === "tablet";
|
||||||
const isTV = deviceType === 'tv';
|
const isTV = deviceType === "tv";
|
||||||
const minTouchTarget = DeviceUtils.getMinTouchTargetSize();
|
const minTouchTarget = DeviceUtils.getMinTouchTargetSize();
|
||||||
|
|
||||||
return StyleSheet.create({
|
return StyleSheet.create({
|
||||||
@@ -243,7 +236,7 @@ const createResponsiveStyles = (deviceType: string, spacing: number) => {
|
|||||||
fontSize: isMobile ? 24 : isTablet ? 28 : 32,
|
fontSize: isMobile ? 24 : isTablet ? 28 : 32,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
paddingTop: spacing,
|
paddingTop: spacing,
|
||||||
color: 'white',
|
color: "white",
|
||||||
},
|
},
|
||||||
scrollView: {
|
scrollView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -257,7 +250,7 @@ const createResponsiveStyles = (deviceType: string, spacing: number) => {
|
|||||||
},
|
},
|
||||||
saveButton: {
|
saveButton: {
|
||||||
minHeight: isMobile ? minTouchTarget : isTablet ? 50 : 50,
|
minHeight: isMobile ? minTouchTarget : isTablet ? 50 : 50,
|
||||||
width: isMobile ? '100%' : isTablet ? 140 : 120,
|
width: isMobile ? "100%" : isTablet ? 140 : 120,
|
||||||
maxWidth: isMobile ? 280 : undefined,
|
maxWidth: isMobile ? 280 : undefined,
|
||||||
},
|
},
|
||||||
disabledButton: {
|
disabledButton: {
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import {
|
import { Modal, View, StyleSheet, ActivityIndicator, Platform } from "react-native";
|
||||||
Modal,
|
import { useUpdateStore } from "../stores/updateStore";
|
||||||
View,
|
import { Colors } from "../constants/Colors";
|
||||||
Text,
|
import { StyledButton } from "./StyledButton";
|
||||||
TouchableOpacity,
|
import { ThemedText } from "./ThemedText";
|
||||||
StyleSheet,
|
|
||||||
ActivityIndicator,
|
|
||||||
Platform,
|
|
||||||
} from 'react-native';
|
|
||||||
import { useUpdateStore } from '../stores/updateStore';
|
|
||||||
import { Colors } from '../constants/Colors';
|
|
||||||
|
|
||||||
export function UpdateModal() {
|
export function UpdateModal() {
|
||||||
const {
|
const {
|
||||||
@@ -26,9 +20,9 @@ export function UpdateModal() {
|
|||||||
downloadedPath,
|
downloadedPath,
|
||||||
} = useUpdateStore();
|
} = useUpdateStore();
|
||||||
|
|
||||||
const updateButtonRef = React.useRef<TouchableOpacity>(null);
|
const updateButtonRef = React.useRef<View>(null);
|
||||||
const laterButtonRef = React.useRef<TouchableOpacity>(null);
|
const laterButtonRef = React.useRef<View>(null);
|
||||||
const skipButtonRef = React.useRef<TouchableOpacity>(null);
|
const skipButtonRef = React.useRef<View>(null);
|
||||||
|
|
||||||
async function handleUpdate() {
|
async function handleUpdate() {
|
||||||
if (!downloading && !downloadedPath) {
|
if (!downloading && !downloadedPath) {
|
||||||
@@ -61,86 +55,59 @@ export function UpdateModal() {
|
|||||||
if (downloading) {
|
if (downloading) {
|
||||||
return `下载中 ${downloadProgress}%`;
|
return `下载中 ${downloadProgress}%`;
|
||||||
} else if (downloadedPath) {
|
} else if (downloadedPath) {
|
||||||
return '立即安装';
|
return "立即安装";
|
||||||
} else {
|
} else {
|
||||||
return '立即更新';
|
return "立即更新";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal visible={showUpdateModal} transparent animationType="fade" onRequestClose={handleLater}>
|
||||||
visible={showUpdateModal}
|
|
||||||
transparent
|
|
||||||
animationType="fade"
|
|
||||||
onRequestClose={handleLater}
|
|
||||||
>
|
|
||||||
<View style={styles.overlay}>
|
<View style={styles.overlay}>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Text style={styles.title}>发现新版本</Text>
|
<ThemedText style={styles.title}>发现新版本</ThemedText>
|
||||||
|
|
||||||
<View style={styles.versionInfo}>
|
<View style={styles.versionInfo}>
|
||||||
<Text style={styles.versionText}>
|
<ThemedText style={styles.versionText}>当前版本: v{currentVersion}</ThemedText>
|
||||||
当前版本: v{currentVersion}
|
<ThemedText style={styles.arrow}>→</ThemedText>
|
||||||
</Text>
|
<ThemedText style={[styles.versionText, styles.newVersion]}>新版本: v{remoteVersion}</ThemedText>
|
||||||
<Text style={styles.arrow}>→</Text>
|
|
||||||
<Text style={[styles.versionText, styles.newVersion]}>
|
|
||||||
新版本: v{remoteVersion}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{downloading && (
|
{downloading && (
|
||||||
<View style={styles.progressContainer}>
|
<View style={styles.progressContainer}>
|
||||||
<View style={styles.progressBar}>
|
<View style={styles.progressBar}>
|
||||||
<View
|
<View style={[styles.progressFill, { width: `${downloadProgress}%` }]} />
|
||||||
style={[
|
|
||||||
styles.progressFill,
|
|
||||||
{ width: `${downloadProgress}%` },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.progressText}>{downloadProgress}%</Text>
|
<ThemedText style={styles.progressText}>{downloadProgress}%</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && <ThemedText style={styles.errorText}>{error}</ThemedText>}
|
||||||
<Text style={styles.errorText}>{error}</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<TouchableOpacity
|
<StyledButton
|
||||||
ref={updateButtonRef}
|
ref={updateButtonRef}
|
||||||
style={[styles.button, styles.primaryButton]}
|
|
||||||
onPress={handleUpdate}
|
onPress={handleUpdate}
|
||||||
disabled={downloading && !downloadedPath}
|
disabled={downloading && !downloadedPath}
|
||||||
|
variant="primary"
|
||||||
|
style={styles.button}
|
||||||
>
|
>
|
||||||
{downloading && !downloadedPath ? (
|
{downloading && !downloadedPath ? (
|
||||||
<ActivityIndicator color="#fff" />
|
<ActivityIndicator color="#fff" />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.buttonText}>{getButtonText()}</Text>
|
<ThemedText style={styles.buttonText}>{getButtonText()}</ThemedText>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</StyledButton>
|
||||||
|
|
||||||
{!downloading && !downloadedPath && (
|
{!downloading && !downloadedPath && (
|
||||||
<>
|
<>
|
||||||
<TouchableOpacity
|
<StyledButton ref={laterButtonRef} onPress={handleLater} variant="primary" style={styles.button}>
|
||||||
ref={laterButtonRef}
|
<ThemedText style={[styles.buttonText]}>稍后再说</ThemedText>
|
||||||
style={[styles.button, styles.secondaryButton]}
|
</StyledButton>
|
||||||
onPress={handleLater}
|
|
||||||
>
|
|
||||||
<Text style={[styles.buttonText, styles.secondaryButtonText]}>
|
|
||||||
稍后再说
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
<StyledButton ref={skipButtonRef} onPress={handleSkip} variant="primary" style={styles.button}>
|
||||||
ref={skipButtonRef}
|
<ThemedText style={[styles.buttonText]}>跳过此版本</ThemedText>
|
||||||
style={[styles.button, styles.textButton]}
|
</StyledButton>
|
||||||
onPress={handleSkip}
|
|
||||||
>
|
|
||||||
<Text style={[styles.buttonText, styles.textButtonText]}>
|
|
||||||
跳过此版本
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -153,27 +120,28 @@ export function UpdateModal() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
overlay: {
|
overlay: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: Colors.dark.background,
|
backgroundColor: Colors.dark.background,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
padding: 24,
|
padding: 24,
|
||||||
width: Platform.isTV ? 500 : '90%',
|
width: Platform.isTV ? 500 : "90%",
|
||||||
maxWidth: 500,
|
maxWidth: 500,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: Platform.isTV ? 28 : 24,
|
fontSize: Platform.isTV ? 28 : 24,
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
color: Colors.dark.text,
|
color: Colors.dark.text,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
|
paddingTop: 12,
|
||||||
},
|
},
|
||||||
versionInfo: {
|
versionInfo: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
},
|
},
|
||||||
versionText: {
|
versionText: {
|
||||||
@@ -181,8 +149,8 @@ const styles = StyleSheet.create({
|
|||||||
color: Colors.dark.text,
|
color: Colors.dark.text,
|
||||||
},
|
},
|
||||||
newVersion: {
|
newVersion: {
|
||||||
color: Colors.dark.primary || '#00bb5e',
|
color: Colors.dark.primary || "#00bb5e",
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
},
|
},
|
||||||
arrow: {
|
arrow: {
|
||||||
fontSize: Platform.isTV ? 20 : 18,
|
fontSize: Platform.isTV ? 20 : 18,
|
||||||
@@ -190,64 +158,44 @@ const styles = StyleSheet.create({
|
|||||||
marginHorizontal: 12,
|
marginHorizontal: 12,
|
||||||
},
|
},
|
||||||
progressContainer: {
|
progressContainer: {
|
||||||
width: '100%',
|
width: "100%",
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
progressBar: {
|
progressBar: {
|
||||||
height: 6,
|
height: 6,
|
||||||
backgroundColor: Colors.dark.border,
|
backgroundColor: Colors.dark.border,
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
progressFill: {
|
progressFill: {
|
||||||
height: '100%',
|
height: "100%",
|
||||||
backgroundColor: Colors.dark.primary || '#00bb5e',
|
backgroundColor: Colors.dark.primary || "#00bb5e",
|
||||||
},
|
},
|
||||||
progressText: {
|
progressText: {
|
||||||
fontSize: Platform.isTV ? 16 : 14,
|
fontSize: Platform.isTV ? 16 : 14,
|
||||||
color: Colors.dark.text,
|
color: Colors.dark.text,
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
fontSize: Platform.isTV ? 16 : 14,
|
fontSize: Platform.isTV ? 16 : 14,
|
||||||
color: '#ff4444',
|
color: "#ff4444",
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
width: '100%',
|
width: "100%",
|
||||||
gap: 12,
|
gap: 12,
|
||||||
|
justifyContent: "center", // 居中对齐
|
||||||
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
paddingVertical: Platform.isTV ? 14 : 12,
|
width: "80%",
|
||||||
paddingHorizontal: 24,
|
|
||||||
borderRadius: 8,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
minHeight: Platform.isTV ? 56 : 48,
|
|
||||||
},
|
|
||||||
primaryButton: {
|
|
||||||
backgroundColor: Colors.dark.primary || '#00bb5e',
|
|
||||||
},
|
|
||||||
secondaryButton: {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: Colors.dark.border,
|
|
||||||
},
|
|
||||||
textButton: {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
buttonText: {
|
buttonText: {
|
||||||
fontSize: Platform.isTV ? 18 : 16,
|
fontSize: Platform.isTV ? 18 : 16,
|
||||||
fontWeight: '600',
|
fontWeight: "600",
|
||||||
color: '#fff',
|
color: "#fff",
|
||||||
},
|
},
|
||||||
secondaryButtonText: {
|
});
|
||||||
color: Colors.dark.text,
|
|
||||||
},
|
|
||||||
textButtonText: {
|
|
||||||
color: Colors.dark.text,
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -6,15 +6,8 @@ import { useUpdateStore } from "@/stores/updateStore";
|
|||||||
import { UPDATE_CONFIG } from "@/constants/UpdateConfig";
|
import { UPDATE_CONFIG } from "@/constants/UpdateConfig";
|
||||||
|
|
||||||
export function UpdateSection() {
|
export function UpdateSection() {
|
||||||
const {
|
const { currentVersion, remoteVersion, updateAvailable, downloading, downloadProgress, checkForUpdate } =
|
||||||
currentVersion,
|
useUpdateStore();
|
||||||
remoteVersion,
|
|
||||||
updateAvailable,
|
|
||||||
downloading,
|
|
||||||
downloadProgress,
|
|
||||||
checkForUpdate,
|
|
||||||
setShowUpdateModal,
|
|
||||||
} = useUpdateStore();
|
|
||||||
|
|
||||||
const [checking, setChecking] = React.useState(false);
|
const [checking, setChecking] = React.useState(false);
|
||||||
|
|
||||||
@@ -30,7 +23,7 @@ export function UpdateSection() {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.sectionContainer}>
|
<View style={styles.sectionContainer}>
|
||||||
<ThemedText style={styles.sectionTitle}>应用更新</ThemedText>
|
<ThemedText style={styles.sectionTitle}>应用更新</ThemedText>
|
||||||
|
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<ThemedText style={styles.label}>当前版本</ThemedText>
|
<ThemedText style={styles.label}>当前版本</ThemedText>
|
||||||
<ThemedText style={styles.value}>v{currentVersion}</ThemedText>
|
<ThemedText style={styles.value}>v{currentVersion}</ThemedText>
|
||||||
@@ -39,9 +32,7 @@ export function UpdateSection() {
|
|||||||
{updateAvailable && (
|
{updateAvailable && (
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<ThemedText style={styles.label}>最新版本</ThemedText>
|
<ThemedText style={styles.label}>最新版本</ThemedText>
|
||||||
<ThemedText style={[styles.value, styles.newVersion]}>
|
<ThemedText style={[styles.value, styles.newVersion]}>v{remoteVersion}</ThemedText>
|
||||||
v{remoteVersion}
|
|
||||||
</ThemedText>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -53,26 +44,13 @@ export function UpdateSection() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<StyledButton
|
<StyledButton onPress={handleCheckUpdate} disabled={checking || downloading} style={styles.button}>
|
||||||
onPress={handleCheckUpdate}
|
|
||||||
disabled={checking || downloading}
|
|
||||||
style={styles.button}
|
|
||||||
>
|
|
||||||
{checking ? (
|
{checking ? (
|
||||||
<ActivityIndicator color="#fff" size="small" />
|
<ActivityIndicator color="#fff" size="small" />
|
||||||
) : (
|
) : (
|
||||||
<ThemedText style={styles.buttonText}>检查更新</ThemedText>
|
<ThemedText style={styles.buttonText}>检查更新</ThemedText>
|
||||||
)}
|
)}
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
|
||||||
{updateAvailable && !downloading && (
|
|
||||||
<StyledButton
|
|
||||||
onPress={() => setShowUpdateModal(true)}
|
|
||||||
style={[styles.button, styles.updateButton]}
|
|
||||||
>
|
|
||||||
<ThemedText style={styles.buttonText}>立即更新</ThemedText>
|
|
||||||
</StyledButton>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{UPDATE_CONFIG.AUTO_CHECK && (
|
{UPDATE_CONFIG.AUTO_CHECK && (
|
||||||
@@ -99,6 +77,7 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: Platform.isTV ? 24 : 20,
|
fontSize: Platform.isTV ? 24 : 20,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
|
paddingTop: 8,
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -121,12 +100,16 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: 12,
|
gap: 12,
|
||||||
marginTop: 16,
|
marginTop: 16,
|
||||||
|
justifyContent: "center", // 居中对齐
|
||||||
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
flex: 1,
|
width: "90%",
|
||||||
},
|
...(Platform.isTV && {
|
||||||
updateButton: {
|
// TV平台焦点样式
|
||||||
backgroundColor: "#00bb5e",
|
borderWidth: 2,
|
||||||
|
borderColor: "transparent",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
buttonText: {
|
buttonText: {
|
||||||
color: "#ffffff",
|
color: "#ffffff",
|
||||||
@@ -139,4 +122,4 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const UPDATE_CONFIG = {
|
|||||||
|
|
||||||
// GitHub相关URL
|
// GitHub相关URL
|
||||||
GITHUB_RAW_URL: 'https://raw.githubusercontent.com/zimplexing/OrionTV/refs/heads/master/package.json',
|
GITHUB_RAW_URL: 'https://raw.githubusercontent.com/zimplexing/OrionTV/refs/heads/master/package.json',
|
||||||
GITHUB_RELEASE_URL_TEMPLATE: 'https://github.com/zimplexing/OrionTV/releases/download/v{version}/app-release.apk',
|
GITHUB_RELEASE_URL_TEMPLATE: 'https://github.com/zimplexing/OrionTV/releases/download/v{version}/orionTV.{version}.apk',
|
||||||
|
|
||||||
// 是否显示更新日志
|
// 是否显示更新日志
|
||||||
SHOW_RELEASE_NOTES: true,
|
SHOW_RELEASE_NOTES: true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "OrionTV",
|
"name": "OrionTV",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
"version": "1.2.11",
|
"version": "1.2.9",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
"start": "EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||||
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
"start-tv": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import ReactNativeBlobUtil from "react-native-blob-util";
|
import ReactNativeBlobUtil from "react-native-blob-util";
|
||||||
import FileViewer from "react-native-file-viewer";
|
import FileViewer from "react-native-file-viewer";
|
||||||
import { version as currentVersion } from "../package.json";
|
import { version as currentVersion } from "../package.json";
|
||||||
|
import { UPDATE_CONFIG } from "../constants/UpdateConfig";
|
||||||
|
|
||||||
interface VersionInfo {
|
interface VersionInfo {
|
||||||
version: string;
|
version: string;
|
||||||
@@ -18,75 +19,168 @@ class UpdateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkVersion(): Promise<VersionInfo> {
|
async checkVersion(): Promise<VersionInfo> {
|
||||||
try {
|
let retries = 0;
|
||||||
const response = await fetch(
|
const maxRetries = 3;
|
||||||
"https://raw.githubusercontent.com/zimplexing/OrionTV/refs/heads/master/package.json"
|
|
||||||
);
|
while (retries < maxRetries) {
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
|
||||||
|
|
||||||
|
const response = await fetch(UPDATE_CONFIG.GITHUB_RAW_URL, {
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to fetch version info");
|
throw new Error(`HTTP ${response.status}: Failed to fetch version info`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remotePackage = await response.json();
|
||||||
|
const remoteVersion = remotePackage.version;
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: remoteVersion,
|
||||||
|
downloadUrl: UPDATE_CONFIG.GITHUB_RELEASE_URL_TEMPLATE.replace(/{version}/g, remoteVersion),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
retries++;
|
||||||
|
console.error(`Error checking version (attempt ${retries}/${maxRetries}):`, error);
|
||||||
|
|
||||||
|
if (retries === maxRetries) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待一段时间后重试
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000 * retries));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Maximum retry attempts exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
const remotePackage = await response.json();
|
// 清理旧的APK文件
|
||||||
const remoteVersion = remotePackage.version;
|
private async cleanOldApkFiles(): Promise<void> {
|
||||||
|
try {
|
||||||
return {
|
const { dirs } = ReactNativeBlobUtil.fs;
|
||||||
version: remoteVersion,
|
// 使用DocumentDir而不是DownloadDir
|
||||||
downloadUrl: `https://github.com/zimplexing/OrionTV/releases/download/v${remoteVersion}/orionTV.
|
const files = await ReactNativeBlobUtil.fs.ls(dirs.DocumentDir);
|
||||||
${remoteVersion}.apk`,
|
|
||||||
};
|
// 查找所有OrionTV APK文件
|
||||||
|
const apkFiles = files.filter(file => file.startsWith('OrionTV_v') && file.endsWith('.apk'));
|
||||||
|
|
||||||
|
// 保留最新的2个文件,删除其他的
|
||||||
|
if (apkFiles.length > 2) {
|
||||||
|
const sortedFiles = apkFiles.sort((a, b) => {
|
||||||
|
// 从文件名中提取时间戳进行排序
|
||||||
|
const timeA = a.match(/OrionTV_v(\d+)\.apk/)?.[1] || '0';
|
||||||
|
const timeB = b.match(/OrionTV_v(\d+)\.apk/)?.[1] || '0';
|
||||||
|
return parseInt(timeB) - parseInt(timeA);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除旧文件
|
||||||
|
const filesToDelete = sortedFiles.slice(2);
|
||||||
|
for (const file of filesToDelete) {
|
||||||
|
try {
|
||||||
|
await ReactNativeBlobUtil.fs.unlink(`${dirs.DocumentDir}/${file}`);
|
||||||
|
console.log(`Cleaned old APK file: ${file}`);
|
||||||
|
} catch (deleteError) {
|
||||||
|
console.warn(`Failed to delete old APK file ${file}:`, deleteError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error checking version:", error);
|
console.warn('Failed to clean old APK files:', error);
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadApk(url: string, onProgress?: (progress: number) => void): Promise<string> {
|
async downloadApk(url: string, onProgress?: (progress: number) => void): Promise<string> {
|
||||||
try {
|
let retries = 0;
|
||||||
const { dirs } = ReactNativeBlobUtil.fs;
|
const maxRetries = 3;
|
||||||
const fileName = `OrionTV_v${new Date().getTime()}.apk`;
|
|
||||||
const filePath = `${dirs.DownloadDir}/${fileName}`;
|
// 清理旧文件
|
||||||
|
await this.cleanOldApkFiles();
|
||||||
|
|
||||||
|
while (retries < maxRetries) {
|
||||||
|
try {
|
||||||
|
const { dirs } = ReactNativeBlobUtil.fs;
|
||||||
|
const timestamp = new Date().getTime();
|
||||||
|
const fileName = `OrionTV_v${timestamp}.apk`;
|
||||||
|
// 使用应用的外部文件目录,而不是系统下载目录
|
||||||
|
const filePath = `${dirs.DocumentDir}/${fileName}`;
|
||||||
|
|
||||||
const task = ReactNativeBlobUtil.config({
|
const task = ReactNativeBlobUtil.config({
|
||||||
fileCache: true,
|
fileCache: true,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
addAndroidDownloads: {
|
timeout: UPDATE_CONFIG.DOWNLOAD_TIMEOUT,
|
||||||
useDownloadManager: true,
|
// 移除 addAndroidDownloads 配置,避免使用系统下载管理器
|
||||||
notification: true,
|
// addAndroidDownloads: {
|
||||||
title: "OrionTV 更新下载中",
|
// useDownloadManager: true,
|
||||||
description: "正在下载新版本...",
|
// notification: true,
|
||||||
mime: "application/vnd.android.package-archive",
|
// title: UPDATE_CONFIG.NOTIFICATION.TITLE,
|
||||||
mediaScannable: true,
|
// description: UPDATE_CONFIG.NOTIFICATION.DOWNLOADING_TEXT,
|
||||||
},
|
// mime: "application/vnd.android.package-archive",
|
||||||
}).fetch("GET", url);
|
// mediaScannable: true,
|
||||||
|
// },
|
||||||
|
}).fetch("GET", url);
|
||||||
|
|
||||||
// 监听下载进度
|
// 监听下载进度
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
task.progress((received: string, total: string) => {
|
task.progress((received: string, total: string) => {
|
||||||
const receivedNum = parseInt(received, 10);
|
const receivedNum = parseInt(received, 10);
|
||||||
const totalNum = parseInt(total, 10);
|
const totalNum = parseInt(total, 10);
|
||||||
const progress = Math.floor((receivedNum / totalNum) * 100);
|
const progress = Math.floor((receivedNum / totalNum) * 100);
|
||||||
onProgress(progress);
|
onProgress(progress);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await task;
|
||||||
|
console.log(`APK downloaded successfully: ${filePath}`);
|
||||||
|
return res.path();
|
||||||
|
} catch (error) {
|
||||||
|
retries++;
|
||||||
|
console.error(`Error downloading APK (attempt ${retries}/${maxRetries}):`, error);
|
||||||
|
|
||||||
|
if (retries === maxRetries) {
|
||||||
|
throw new Error(`Download failed after ${maxRetries} attempts: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待一段时间后重试
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000 * retries));
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await task;
|
|
||||||
return res.path();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error downloading APK:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error("Maximum retry attempts exceeded for download");
|
||||||
}
|
}
|
||||||
|
|
||||||
async installApk(filePath: string): Promise<void> {
|
async installApk(filePath: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
// 首先检查文件是否存在
|
||||||
|
const exists = await ReactNativeBlobUtil.fs.exists(filePath);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(`APK file not found: ${filePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用FileViewer打开APK文件进行安装
|
||||||
|
// 这会触发Android的包安装器
|
||||||
await FileViewer.open(filePath, {
|
await FileViewer.open(filePath, {
|
||||||
showOpenWithDialog: false,
|
showOpenWithDialog: true, // 显示选择应用对话框
|
||||||
showAppsSuggestions: false,
|
showAppsSuggestions: true, // 显示应用建议
|
||||||
displayName: "OrionTV Update",
|
displayName: "OrionTV Update",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error installing APK:", error);
|
console.error("Error installing APK:", error);
|
||||||
|
|
||||||
|
// 提供更详细的错误信息
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message.includes('No app found')) {
|
||||||
|
throw new Error('未找到可安装APK的应用,请确保允许安装未知来源的应用');
|
||||||
|
} else if (error.message.includes('permission')) {
|
||||||
|
throw new Error('没有安装权限,请在设置中允许此应用安装未知来源的应用');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ interface RemoteControlState {
|
|||||||
startServer: () => Promise<void>;
|
startServer: () => Promise<void>;
|
||||||
stopServer: () => void;
|
stopServer: () => void;
|
||||||
isModalVisible: boolean;
|
isModalVisible: boolean;
|
||||||
showModal: () => void;
|
showModal: (targetPage?: string) => void;
|
||||||
hideModal: () => void;
|
hideModal: () => void;
|
||||||
lastMessage: string | null;
|
lastMessage: string | null;
|
||||||
setMessage: (message: string) => void;
|
targetPage: string | null;
|
||||||
|
setMessage: (message: string, targetPage?: string) => void;
|
||||||
|
clearMessage: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
|
export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
|
||||||
@@ -20,6 +22,7 @@ export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
|
|||||||
error: null,
|
error: null,
|
||||||
isModalVisible: false,
|
isModalVisible: false,
|
||||||
lastMessage: null,
|
lastMessage: null,
|
||||||
|
targetPage: null,
|
||||||
|
|
||||||
startServer: async () => {
|
startServer: async () => {
|
||||||
if (get().isServerRunning) {
|
if (get().isServerRunning) {
|
||||||
@@ -28,7 +31,9 @@ export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
|
|||||||
remoteControlService.init({
|
remoteControlService.init({
|
||||||
onMessage: (message: string) => {
|
onMessage: (message: string) => {
|
||||||
console.log('[RemoteControlStore] Received message:', message);
|
console.log('[RemoteControlStore] Received message:', message);
|
||||||
set({ lastMessage: message });
|
const currentState = get();
|
||||||
|
// Use the current targetPage from the store
|
||||||
|
set({ lastMessage: message, targetPage: currentState.targetPage });
|
||||||
},
|
},
|
||||||
onHandshake: () => {
|
onHandshake: () => {
|
||||||
console.log('[RemoteControlStore] Handshake successful');
|
console.log('[RemoteControlStore] Handshake successful');
|
||||||
@@ -53,10 +58,14 @@ export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showModal: () => set({ isModalVisible: true }),
|
showModal: (targetPage?: string) => set({ isModalVisible: true, targetPage }),
|
||||||
hideModal: () => set({ isModalVisible: false }),
|
hideModal: () => set({ isModalVisible: false, targetPage: null }),
|
||||||
|
|
||||||
setMessage: (message: string) => {
|
setMessage: (message: string, targetPage?: string) => {
|
||||||
set({ lastMessage: `${message}_${Date.now()}` });
|
set({ lastMessage: `${message}_${Date.now()}`, targetPage });
|
||||||
|
},
|
||||||
|
|
||||||
|
clearMessage: () => {
|
||||||
|
set({ lastMessage: null, targetPage: null });
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
Reference in New Issue
Block a user