mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
237 lines
7.9 KiB
TypeScript
237 lines
7.9 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { Platform } from 'react-native';
|
|
import { toast, ToastPosition } from '@backpackapp-io/react-native-toast';
|
|
import UpdateService, { UpdateInfo } from '../services/updateService';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
|
interface UseUpdatePopupReturn {
|
|
showUpdatePopup: boolean;
|
|
updateInfo: UpdateInfo;
|
|
isInstalling: boolean;
|
|
checkForUpdates: () => Promise<void>;
|
|
handleUpdateNow: () => Promise<void>;
|
|
handleUpdateLater: () => void;
|
|
handleDismiss: () => void;
|
|
}
|
|
|
|
const UPDATE_POPUP_STORAGE_KEY = '@update_popup_dismissed';
|
|
const UPDATE_LATER_STORAGE_KEY = '@update_later_timestamp';
|
|
const UPDATE_LAST_CHECK_TS_KEY = '@update_last_check_ts';
|
|
const UPDATE_BADGE_KEY = '@update_badge_pending';
|
|
|
|
export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|
const [showUpdatePopup, setShowUpdatePopup] = useState(false);
|
|
const [updateInfo, setUpdateInfo] = useState<UpdateInfo>({ isAvailable: false });
|
|
const [isInstalling, setIsInstalling] = useState(false);
|
|
const [hasCheckedOnStartup, setHasCheckedOnStartup] = useState(false);
|
|
const [isAppReady, setIsAppReady] = useState(false);
|
|
|
|
const checkForUpdates = useCallback(async (forceCheck = false) => {
|
|
try {
|
|
// Skip update checks on Android to prevent OTA checks
|
|
if (Platform.OS === 'android') {
|
|
return;
|
|
}
|
|
|
|
// Check if user has dismissed the popup for this version
|
|
const dismissedVersion = await AsyncStorage.getItem(UPDATE_POPUP_STORAGE_KEY);
|
|
const currentVersion = updateInfo.manifest?.id;
|
|
|
|
if (dismissedVersion === currentVersion && !forceCheck) {
|
|
return; // User already dismissed this version
|
|
}
|
|
|
|
// Check if user chose "later" recently (within 6 hours)
|
|
const updateLaterTimestamp = await AsyncStorage.getItem(UPDATE_LATER_STORAGE_KEY);
|
|
if (updateLaterTimestamp && !forceCheck) {
|
|
const laterTime = parseInt(updateLaterTimestamp);
|
|
const now = Date.now();
|
|
const sixHours = 6 * 60 * 60 * 1000; // Reduced from 24 hours
|
|
|
|
if (now - laterTime < sixHours) {
|
|
return; // User chose "later" recently
|
|
}
|
|
}
|
|
|
|
const info = await UpdateService.checkForUpdates();
|
|
setUpdateInfo(info);
|
|
|
|
if (info.isAvailable) {
|
|
// Show popup (Android checks are handled earlier in the function)
|
|
setShowUpdatePopup(true);
|
|
}
|
|
} catch (error) {
|
|
if (__DEV__) console.error('Error checking for updates:', error);
|
|
// Don't show popup on error, just log it
|
|
}
|
|
}, [updateInfo.manifest?.id, isAppReady]);
|
|
|
|
const handleUpdateNow = useCallback(async () => {
|
|
try {
|
|
setIsInstalling(true);
|
|
setShowUpdatePopup(false);
|
|
|
|
const success = await UpdateService.downloadAndInstallUpdate();
|
|
|
|
if (success) {
|
|
// Update installed successfully - no restart alert needed
|
|
// The app will automatically reload with the new version
|
|
console.log('Update installed successfully');
|
|
} else {
|
|
toast('Unable to install the update. Please try again later or check your internet connection.', {
|
|
duration: 3000,
|
|
position: ToastPosition.TOP,
|
|
});
|
|
// Show popup again after failed installation
|
|
setShowUpdatePopup(true);
|
|
}
|
|
} catch (error) {
|
|
if (__DEV__) console.error('Error installing update:', error);
|
|
toast('An error occurred while installing the update. Please try again later.', {
|
|
duration: 3000,
|
|
position: ToastPosition.TOP,
|
|
});
|
|
// Show popup again after error
|
|
setShowUpdatePopup(true);
|
|
} finally {
|
|
setIsInstalling(false);
|
|
}
|
|
}, []);
|
|
|
|
const handleUpdateLater = useCallback(async () => {
|
|
try {
|
|
// Store timestamp when user chose "later"
|
|
await AsyncStorage.setItem(UPDATE_LATER_STORAGE_KEY, Date.now().toString());
|
|
setShowUpdatePopup(false);
|
|
} catch (error) {
|
|
if (__DEV__) console.error('Error storing update later preference:', error);
|
|
setShowUpdatePopup(false);
|
|
}
|
|
}, []);
|
|
|
|
const handleDismiss = useCallback(async () => {
|
|
try {
|
|
// Store the current version ID so we don't show popup again for this version
|
|
const currentVersion = updateInfo.manifest?.id;
|
|
if (currentVersion) {
|
|
await AsyncStorage.setItem(UPDATE_POPUP_STORAGE_KEY, currentVersion);
|
|
}
|
|
setShowUpdatePopup(false);
|
|
} catch (error) {
|
|
if (__DEV__) console.error('Error storing dismiss preference:', error);
|
|
setShowUpdatePopup(false);
|
|
}
|
|
}, [updateInfo.manifest?.id]);
|
|
|
|
// Handle startup update check results
|
|
useEffect(() => {
|
|
// Skip startup update check registration on Android
|
|
if (Platform.OS === 'android') {
|
|
return;
|
|
}
|
|
|
|
const handleStartupUpdateCheck = (updateInfo: UpdateInfo) => {
|
|
console.log('UpdatePopup: Received startup update check result', updateInfo);
|
|
setUpdateInfo(updateInfo);
|
|
setHasCheckedOnStartup(true);
|
|
|
|
if (updateInfo.isAvailable) {
|
|
if (Platform.OS === 'android') {
|
|
// Set badge and show a toast
|
|
(async () => {
|
|
try { await AsyncStorage.setItem(UPDATE_BADGE_KEY, 'true'); } catch {}
|
|
})();
|
|
try {
|
|
toast('Update available — go to Settings → App Updates', {
|
|
duration: 3000,
|
|
position: ToastPosition.TOP,
|
|
});
|
|
} catch {}
|
|
setShowUpdatePopup(false);
|
|
} else {
|
|
setShowUpdatePopup(true);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Register callback for startup update check
|
|
UpdateService.onUpdateCheck(handleStartupUpdateCheck);
|
|
|
|
// Cleanup callback on unmount
|
|
return () => {
|
|
UpdateService.offUpdateCheck(handleStartupUpdateCheck);
|
|
};
|
|
}, []);
|
|
|
|
// Mark app as ready after a delay (Android safety)
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setIsAppReady(true);
|
|
}, Platform.OS === 'android' ? 3000 : 1000);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, []);
|
|
|
|
// Show popup when app becomes ready on Android (if update is available)
|
|
useEffect(() => {
|
|
if (Platform.OS === 'android' && isAppReady && updateInfo.isAvailable && !showUpdatePopup) {
|
|
// Check if user hasn't dismissed this version
|
|
(async () => {
|
|
try {
|
|
const dismissedVersion = await AsyncStorage.getItem(UPDATE_POPUP_STORAGE_KEY);
|
|
const currentVersion = updateInfo.manifest?.id;
|
|
|
|
if (dismissedVersion !== currentVersion) {
|
|
setShowUpdatePopup(true);
|
|
}
|
|
} catch (error) {
|
|
// If we can't check, show the popup anyway
|
|
setShowUpdatePopup(true);
|
|
}
|
|
})();
|
|
}
|
|
}, [isAppReady, updateInfo.isAvailable, updateInfo.manifest?.id, showUpdatePopup]);
|
|
|
|
// Auto-check for updates when hook is first used (fallback if startup check fails)
|
|
useEffect(() => {
|
|
if (hasCheckedOnStartup) {
|
|
return; // Already checked on startup
|
|
}
|
|
|
|
// Skip auto-check on Android to prevent OTA checks
|
|
if (Platform.OS === 'android') {
|
|
return;
|
|
}
|
|
|
|
// Add a small delay to ensure the app is fully loaded
|
|
const timer = setTimeout(() => {
|
|
(async () => {
|
|
try {
|
|
const lastCheckTs = await AsyncStorage.getItem(UPDATE_LAST_CHECK_TS_KEY);
|
|
const last = lastCheckTs ? parseInt(lastCheckTs, 10) : 0;
|
|
const now = Date.now();
|
|
const sixHours = 6 * 60 * 60 * 1000; // Reduced from 24 hours
|
|
if (now - last < sixHours) {
|
|
return; // Throttle: only auto-check once per 6h
|
|
}
|
|
await checkForUpdates();
|
|
await AsyncStorage.setItem(UPDATE_LAST_CHECK_TS_KEY, String(now));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
})();
|
|
}, 3000); // Increased delay to 3 seconds to give startup check time
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [checkForUpdates, hasCheckedOnStartup]);
|
|
|
|
return {
|
|
showUpdatePopup,
|
|
updateInfo,
|
|
isInstalling,
|
|
checkForUpdates,
|
|
handleUpdateNow,
|
|
handleUpdateLater,
|
|
handleDismiss,
|
|
};
|
|
};
|