Initial commit: RR3 OTA Update System
Complete standalone package for adding OTA (Over-The-Air) auto-updates to Real Racing 3 APK mods. Features: - Manifest-based version control (versions.json) - Prevents unwanted major version jumps - Material Design WebView UI - WiFi/mobile data download options - Preserves user data during updates - Multi-version channel support Package Contents: - UpdateManager.java - Source code implementation - UpdateManager.smali - Compiled smali for APK integration - community_update_checker.html - Material Design UI - README.md - Complete documentation - INTEGRATION-GUIDE.md - Step-by-step integration instructions Ready for integration into any RR3 APK version (v15, v14, v13, etc.) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
752
community_update_checker.html
Normal file
752
community_update_checker.html
Normal file
@@ -0,0 +1,752 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>RR3 Update Available</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
||||
color: #ffffff;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
animation: fadeIn 0.5s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 8px 20px;
|
||||
border-radius: 25px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.network-preference {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.network-toggle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 15px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.network-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.network-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.network-info h4 {
|
||||
font-size: 16px;
|
||||
margin-bottom: 4px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.network-info p {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
width: 56px;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transition: 0.3s;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.network-status {
|
||||
padding: 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.network-status-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.network-status-value {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.network-status-value.wifi {
|
||||
color: #2ed573;
|
||||
}
|
||||
|
||||
.network-status-value.mobile {
|
||||
color: #ffa502;
|
||||
}
|
||||
|
||||
.data-warning {
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: rgba(255, 165, 0, 0.2);
|
||||
border: 2px solid #ffa502;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: #ffa502;
|
||||
line-height: 1.5;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.data-warning.show {
|
||||
display: block;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.changelog {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.changelog h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.changelog-content {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.changelog-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.changelog-content::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.changelog-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.changelog-content ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.changelog-content li {
|
||||
padding: 8px 0;
|
||||
padding-left: 25px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.changelog-content li:before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
color: #4CAF50;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin: 20px 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-container.show {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4CAF50 0%, #8BC34A 100%);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn-secondary:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #2ed573 0%, #24a353 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<h1>🎉 Update Available!</h1>
|
||||
<span class="version-badge" id="versionBadge">v15.0.1-community-alpha</span>
|
||||
</div>
|
||||
|
||||
<!-- Network Preference Card -->
|
||||
<div class="card network-preference">
|
||||
<div class="network-toggle-container">
|
||||
<div class="network-label">
|
||||
<span class="network-icon">📶</span>
|
||||
<div class="network-info">
|
||||
<h4>WiFi Only</h4>
|
||||
<p>Download updates only on WiFi (saves mobile data)</p>
|
||||
</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="wifiOnlyToggle" onchange="toggleWifiOnly()">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="network-status">
|
||||
<span class="network-status-label">Network:</span>
|
||||
<span class="network-status-value" id="networkStatus">Loading...</span>
|
||||
</div>
|
||||
|
||||
<div class="data-warning" id="dataWarning">
|
||||
⚠️ <strong>You're on mobile data.</strong> This download is approximately <span id="warningSize">120 MB</span> and may incur data charges from your carrier.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Changelog Card -->
|
||||
<div class="card changelog">
|
||||
<h3>📝 What's New</h3>
|
||||
<div class="changelog-content" id="changelogContent">
|
||||
<ul>
|
||||
<li>Loading changelog...</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File Info Card -->
|
||||
<div class="card file-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">📦 File Size</span>
|
||||
<span class="info-value" id="fileSize">Loading...</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">📅 Release Date</span>
|
||||
<span class="info-value" id="releaseDate">Loading...</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">🚀 Current Version</span>
|
||||
<span class="info-value" id="currentVersion">15.0.0-community-alpha</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Section -->
|
||||
<div class="progress-container" id="progressContainer">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<div class="status-text" id="statusText">Preparing download...</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="button-group">
|
||||
<button class="btn btn-primary" id="downloadBtn" onclick="downloadUpdate()">
|
||||
<span class="btn-icon">📥</span>
|
||||
<span id="btnText">Download Now</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="skipUpdate()">
|
||||
Skip This Version
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let updateInfo = null;
|
||||
let progressInterval = null;
|
||||
let downloadUrl = '';
|
||||
let version = '';
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
try {
|
||||
loadNetworkPreference();
|
||||
updateNetworkStatus();
|
||||
loadUpdateInfo();
|
||||
getCurrentVersion();
|
||||
} catch (e) {
|
||||
console.error('Initialization error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load network preference
|
||||
function loadNetworkPreference() {
|
||||
try {
|
||||
const wifiOnly = UpdateManager.isWifiOnlyEnabled();
|
||||
document.getElementById('wifiOnlyToggle').checked = wifiOnly;
|
||||
updateNetworkUI(wifiOnly);
|
||||
} catch (e) {
|
||||
console.error('Failed to load network preference:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle WiFi-only mode
|
||||
function toggleWifiOnly() {
|
||||
const toggle = document.getElementById('wifiOnlyToggle');
|
||||
const enabled = toggle.checked;
|
||||
|
||||
try {
|
||||
UpdateManager.setWifiOnlyEnabled(enabled);
|
||||
updateNetworkUI(enabled);
|
||||
updateNetworkStatus();
|
||||
} catch (e) {
|
||||
console.error('Failed to set network preference:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Update network UI based on preference
|
||||
function updateNetworkUI(wifiOnly) {
|
||||
const networkStatus = document.getElementById('networkStatus');
|
||||
|
||||
if (wifiOnly) {
|
||||
networkStatus.textContent = 'WiFi Only ✅';
|
||||
networkStatus.className = 'network-status-value wifi';
|
||||
} else {
|
||||
networkStatus.textContent = 'WiFi or Mobile ⚡';
|
||||
networkStatus.className = 'network-status-value mobile';
|
||||
}
|
||||
|
||||
checkDataWarning();
|
||||
}
|
||||
|
||||
// Update network status
|
||||
function updateNetworkStatus() {
|
||||
// Network status is updated by updateNetworkUI
|
||||
checkDataWarning();
|
||||
}
|
||||
|
||||
// Check if should show data warning
|
||||
function checkDataWarning() {
|
||||
const dataWarning = document.getElementById('dataWarning');
|
||||
const wifiOnly = document.getElementById('wifiOnlyToggle').checked;
|
||||
|
||||
// Show warning if NOT WiFi-only AND update info loaded
|
||||
if (!wifiOnly && updateInfo && updateInfo.fileSize) {
|
||||
const sizeMB = (updateInfo.fileSize / 1024 / 1024).toFixed(1);
|
||||
document.getElementById('warningSize').textContent = sizeMB + ' MB';
|
||||
dataWarning.classList.add('show');
|
||||
} else {
|
||||
dataWarning.classList.remove('show');
|
||||
}
|
||||
}
|
||||
|
||||
// Get current version
|
||||
function getCurrentVersion() {
|
||||
try {
|
||||
const current = UpdateManager.getCurrentVersion();
|
||||
document.getElementById('currentVersion').textContent = current;
|
||||
} catch (e) {
|
||||
console.error('Failed to get current version:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load update info
|
||||
function loadUpdateInfo() {
|
||||
try {
|
||||
const jsonStr = UpdateManager.checkForUpdates();
|
||||
updateInfo = JSON.parse(jsonStr);
|
||||
|
||||
if (updateInfo.error) {
|
||||
showError('Failed to check for updates: ' + updateInfo.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!updateInfo.hasUpdate) {
|
||||
showError('No update available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI with info
|
||||
version = updateInfo.version;
|
||||
downloadUrl = updateInfo.downloadUrl;
|
||||
|
||||
document.getElementById('versionBadge').textContent = 'v' + version;
|
||||
document.getElementById('fileSize').textContent = formatBytes(updateInfo.fileSize);
|
||||
document.getElementById('releaseDate').textContent = formatDate(updateInfo.releaseDate);
|
||||
|
||||
// Update changelog
|
||||
if (updateInfo.changelog) {
|
||||
displayChangelog(updateInfo.changelog);
|
||||
}
|
||||
|
||||
checkDataWarning();
|
||||
|
||||
} catch (e) {
|
||||
console.error('Failed to load update info:', e);
|
||||
showError('Error loading update information');
|
||||
}
|
||||
}
|
||||
|
||||
// Display changelog
|
||||
function displayChangelog(changelog) {
|
||||
const container = document.getElementById('changelogContent');
|
||||
|
||||
// Parse markdown-style changelog
|
||||
const lines = changelog.split('\n');
|
||||
let html = '<ul>';
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
if (line.startsWith('- ') || line.startsWith('* ')) {
|
||||
html += '<li>' + line.substring(2) + '</li>';
|
||||
} else if (line.length > 0 && !line.startsWith('#')) {
|
||||
html += '<li>' + line + '</li>';
|
||||
}
|
||||
}
|
||||
|
||||
html += '</ul>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// Download update
|
||||
function downloadUpdate() {
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
|
||||
// Check if network is suitable
|
||||
try {
|
||||
const suitable = UpdateManager.isNetworkSuitableForDownload();
|
||||
if (!suitable) {
|
||||
alert('⚠️ WiFi connection required\n\nConnect to WiFi or disable "WiFi Only" mode to download on mobile data.');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to check network:', e);
|
||||
}
|
||||
|
||||
// Disable button and show progress
|
||||
downloadBtn.disabled = true;
|
||||
downloadBtn.innerHTML = '<span class="loading"></span><span>Downloading...</span>';
|
||||
|
||||
document.getElementById('progressContainer').classList.add('show');
|
||||
document.getElementById('statusText').textContent = 'Starting download...';
|
||||
|
||||
// Start download
|
||||
try {
|
||||
UpdateManager.downloadUpdate(downloadUrl, version);
|
||||
|
||||
// Monitor progress
|
||||
progressInterval = setInterval(updateProgress, 500);
|
||||
|
||||
} catch (e) {
|
||||
console.error('Failed to start download:', e);
|
||||
showError('Failed to start download: ' + e.message);
|
||||
resetDownloadButton();
|
||||
}
|
||||
}
|
||||
|
||||
// Update download progress
|
||||
function updateProgress() {
|
||||
try {
|
||||
const progress = UpdateManager.getDownloadProgress();
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const statusText = document.getElementById('statusText');
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
|
||||
if (progress >= 100) {
|
||||
// Download complete
|
||||
clearInterval(progressInterval);
|
||||
progressFill.style.width = '100%';
|
||||
statusText.textContent = '✅ Download complete! Tap button to install.';
|
||||
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.className = 'btn btn-success';
|
||||
downloadBtn.innerHTML = '<span class="btn-icon">✅</span><span>Tap to Install</span>';
|
||||
downloadBtn.onclick = installUpdate;
|
||||
|
||||
} else if (progress < 0) {
|
||||
// Download paused (no suitable network)
|
||||
progressFill.style.width = '0%';
|
||||
statusText.textContent = '⏸️ Download paused. Waiting for WiFi connection...';
|
||||
downloadBtn.innerHTML = '<span class="btn-icon">⏸️</span><span>Paused (No WiFi)</span>';
|
||||
|
||||
} else if (progress === 0) {
|
||||
// Still preparing
|
||||
statusText.textContent = 'Preparing download...';
|
||||
|
||||
} else {
|
||||
// Downloading
|
||||
progressFill.style.width = progress + '%';
|
||||
statusText.textContent = `Downloading... ${progress.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error('Progress check error:', e);
|
||||
clearInterval(progressInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// Install update
|
||||
function installUpdate() {
|
||||
try {
|
||||
document.getElementById('statusText').textContent = 'Opening installer...';
|
||||
UpdateManager.installUpdate();
|
||||
} catch (e) {
|
||||
console.error('Failed to install:', e);
|
||||
showError('Failed to open installer: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this version
|
||||
function skipUpdate() {
|
||||
if (confirm('Skip this version?\n\nYou can always check for updates manually later.')) {
|
||||
try {
|
||||
UpdateManager.skipThisVersion(version);
|
||||
window.history.back();
|
||||
} catch (e) {
|
||||
console.error('Failed to skip version:', e);
|
||||
window.history.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset download button
|
||||
function resetDownloadButton() {
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.innerHTML = '<span class="btn-icon">📥</span><span>Download Now</span>';
|
||||
document.getElementById('progressContainer').classList.remove('show');
|
||||
}
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
const statusText = document.getElementById('statusText');
|
||||
statusText.textContent = '❌ ' + message;
|
||||
statusText.style.color = '#ff6b6b';
|
||||
document.getElementById('progressContainer').classList.add('show');
|
||||
}
|
||||
|
||||
// Format bytes to human readable
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Format date
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return 'Unknown';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
window.addEventListener('load', init);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user