mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 11:44:44 +08:00
feat: Implement automatic update checking and user notifications for new versions
This commit is contained in:
253
components/UpdateModal.tsx
Normal file
253
components/UpdateModal.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Modal,
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { useUpdateStore } from '../stores/updateStore';
|
||||
import { Colors } from '../constants/Colors';
|
||||
|
||||
export function UpdateModal() {
|
||||
const {
|
||||
showUpdateModal,
|
||||
currentVersion,
|
||||
remoteVersion,
|
||||
downloading,
|
||||
downloadProgress,
|
||||
error,
|
||||
setShowUpdateModal,
|
||||
startDownload,
|
||||
installUpdate,
|
||||
skipThisVersion,
|
||||
downloadedPath,
|
||||
} = useUpdateStore();
|
||||
|
||||
const updateButtonRef = React.useRef<TouchableOpacity>(null);
|
||||
const laterButtonRef = React.useRef<TouchableOpacity>(null);
|
||||
const skipButtonRef = React.useRef<TouchableOpacity>(null);
|
||||
|
||||
async function handleUpdate() {
|
||||
if (!downloading && !downloadedPath) {
|
||||
// 开始下载
|
||||
await startDownload();
|
||||
} else if (downloadedPath) {
|
||||
// 已下载完成,安装
|
||||
await installUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
function handleLater() {
|
||||
setShowUpdateModal(false);
|
||||
}
|
||||
|
||||
async function handleSkip() {
|
||||
await skipThisVersion();
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (showUpdateModal && Platform.isTV) {
|
||||
// TV平台自动聚焦到更新按钮
|
||||
setTimeout(() => {
|
||||
updateButtonRef.current?.focus();
|
||||
}, 100);
|
||||
}
|
||||
}, [showUpdateModal]);
|
||||
|
||||
const getButtonText = () => {
|
||||
if (downloading) {
|
||||
return `下载中 ${downloadProgress}%`;
|
||||
} else if (downloadedPath) {
|
||||
return '立即安装';
|
||||
} else {
|
||||
return '立即更新';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={showUpdateModal}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={handleLater}
|
||||
>
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>发现新版本</Text>
|
||||
|
||||
<View style={styles.versionInfo}>
|
||||
<Text style={styles.versionText}>
|
||||
当前版本: v{currentVersion}
|
||||
</Text>
|
||||
<Text style={styles.arrow}>→</Text>
|
||||
<Text style={[styles.versionText, styles.newVersion]}>
|
||||
新版本: v{remoteVersion}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{downloading && (
|
||||
<View style={styles.progressContainer}>
|
||||
<View style={styles.progressBar}>
|
||||
<View
|
||||
style={[
|
||||
styles.progressFill,
|
||||
{ width: `${downloadProgress}%` },
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.progressText}>{downloadProgress}%</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
)}
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableOpacity
|
||||
ref={updateButtonRef}
|
||||
style={[styles.button, styles.primaryButton]}
|
||||
onPress={handleUpdate}
|
||||
disabled={downloading && !downloadedPath}
|
||||
>
|
||||
{downloading && !downloadedPath ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
<Text style={styles.buttonText}>{getButtonText()}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
{!downloading && !downloadedPath && (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
ref={laterButtonRef}
|
||||
style={[styles.button, styles.secondaryButton]}
|
||||
onPress={handleLater}
|
||||
>
|
||||
<Text style={[styles.buttonText, styles.secondaryButtonText]}>
|
||||
稍后再说
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
ref={skipButtonRef}
|
||||
style={[styles.button, styles.textButton]}
|
||||
onPress={handleSkip}
|
||||
>
|
||||
<Text style={[styles.buttonText, styles.textButtonText]}>
|
||||
跳过此版本
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
container: {
|
||||
backgroundColor: Colors.dark.background,
|
||||
borderRadius: 12,
|
||||
padding: 24,
|
||||
width: Platform.isTV ? 500 : '90%',
|
||||
maxWidth: 500,
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: Platform.isTV ? 28 : 24,
|
||||
fontWeight: 'bold',
|
||||
color: Colors.dark.text,
|
||||
marginBottom: 20,
|
||||
},
|
||||
versionInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 24,
|
||||
},
|
||||
versionText: {
|
||||
fontSize: Platform.isTV ? 18 : 16,
|
||||
color: Colors.dark.text,
|
||||
},
|
||||
newVersion: {
|
||||
color: Colors.dark.primary || '#00bb5e',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
arrow: {
|
||||
fontSize: Platform.isTV ? 20 : 18,
|
||||
color: Colors.dark.text,
|
||||
marginHorizontal: 12,
|
||||
},
|
||||
progressContainer: {
|
||||
width: '100%',
|
||||
marginBottom: 20,
|
||||
},
|
||||
progressBar: {
|
||||
height: 6,
|
||||
backgroundColor: Colors.dark.border,
|
||||
borderRadius: 3,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 8,
|
||||
},
|
||||
progressFill: {
|
||||
height: '100%',
|
||||
backgroundColor: Colors.dark.primary || '#00bb5e',
|
||||
},
|
||||
progressText: {
|
||||
fontSize: Platform.isTV ? 16 : 14,
|
||||
color: Colors.dark.text,
|
||||
textAlign: 'center',
|
||||
},
|
||||
errorText: {
|
||||
fontSize: Platform.isTV ? 16 : 14,
|
||||
color: '#ff4444',
|
||||
marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
buttonContainer: {
|
||||
width: '100%',
|
||||
gap: 12,
|
||||
},
|
||||
button: {
|
||||
paddingVertical: Platform.isTV ? 14 : 12,
|
||||
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: {
|
||||
fontSize: Platform.isTV ? 18 : 16,
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
},
|
||||
secondaryButtonText: {
|
||||
color: Colors.dark.text,
|
||||
},
|
||||
textButtonText: {
|
||||
color: Colors.dark.text,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
});
|
||||
133
components/settings/UpdateSection.tsx
Normal file
133
components/settings/UpdateSection.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import React from "react";
|
||||
import { View, StyleSheet, Platform, ActivityIndicator } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
import { StyledButton } from "../StyledButton";
|
||||
import { useUpdateStore } from "@/stores/updateStore";
|
||||
import { UPDATE_CONFIG } from "@/constants/UpdateConfig";
|
||||
|
||||
export function UpdateSection() {
|
||||
const {
|
||||
currentVersion,
|
||||
remoteVersion,
|
||||
updateAvailable,
|
||||
downloading,
|
||||
downloadProgress,
|
||||
checkForUpdate,
|
||||
setShowUpdateModal,
|
||||
} = useUpdateStore();
|
||||
|
||||
const [checking, setChecking] = React.useState(false);
|
||||
|
||||
const handleCheckUpdate = async () => {
|
||||
setChecking(true);
|
||||
try {
|
||||
await checkForUpdate(false);
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.sectionContainer}>
|
||||
<ThemedText style={styles.sectionTitle}>应用更新</ThemedText>
|
||||
|
||||
<View style={styles.row}>
|
||||
<ThemedText style={styles.label}>当前版本</ThemedText>
|
||||
<ThemedText style={styles.value}>v{currentVersion}</ThemedText>
|
||||
</View>
|
||||
|
||||
{updateAvailable && (
|
||||
<View style={styles.row}>
|
||||
<ThemedText style={styles.label}>最新版本</ThemedText>
|
||||
<ThemedText style={[styles.value, styles.newVersion]}>
|
||||
v{remoteVersion}
|
||||
</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{downloading && (
|
||||
<View style={styles.row}>
|
||||
<ThemedText style={styles.label}>下载进度</ThemedText>
|
||||
<ThemedText style={styles.value}>{downloadProgress}%</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
<StyledButton
|
||||
title={checking ? "检查中..." : "检查更新"}
|
||||
onPress={handleCheckUpdate}
|
||||
disabled={checking || downloading}
|
||||
style={styles.button}
|
||||
>
|
||||
{checking && <ActivityIndicator color="#fff" size="small" />}
|
||||
</StyledButton>
|
||||
|
||||
{updateAvailable && !downloading && (
|
||||
<StyledButton
|
||||
title="立即更新"
|
||||
onPress={() => setShowUpdateModal(true)}
|
||||
style={[styles.button, styles.updateButton]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{UPDATE_CONFIG.AUTO_CHECK && (
|
||||
<ThemedText style={styles.hint}>
|
||||
自动检查更新已开启,每{UPDATE_CONFIG.CHECK_INTERVAL / (60 * 60 * 1000)}小时检查一次
|
||||
</ThemedText>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
sectionContainer: {
|
||||
marginBottom: 24,
|
||||
padding: 16,
|
||||
backgroundColor: Platform.select({
|
||||
ios: "rgba(255, 255, 255, 0.05)",
|
||||
android: "rgba(255, 255, 255, 0.05)",
|
||||
default: "transparent",
|
||||
}),
|
||||
borderRadius: 8,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: Platform.isTV ? 24 : 20,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 16,
|
||||
},
|
||||
row: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 12,
|
||||
},
|
||||
label: {
|
||||
fontSize: Platform.isTV ? 18 : 16,
|
||||
color: "#999",
|
||||
},
|
||||
value: {
|
||||
fontSize: Platform.isTV ? 18 : 16,
|
||||
},
|
||||
newVersion: {
|
||||
color: "#00bb5e",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
buttonContainer: {
|
||||
flexDirection: "row",
|
||||
gap: 12,
|
||||
marginTop: 16,
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
},
|
||||
updateButton: {
|
||||
backgroundColor: "#00bb5e",
|
||||
},
|
||||
hint: {
|
||||
fontSize: Platform.isTV ? 14 : 12,
|
||||
color: "#666",
|
||||
marginTop: 12,
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user