android freeze fix

This commit is contained in:
tapframe 2025-09-19 13:38:46 +05:30
parent 0fcc4edde1
commit 877a4c5dc6
8 changed files with 647 additions and 73 deletions

21
App.tsx
View file

@ -9,7 +9,8 @@ import React, { useState, useEffect } from 'react';
import {
View,
StyleSheet,
I18nManager
I18nManager,
Platform
} from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
@ -155,14 +156,16 @@ const ThemedApp = () => {
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
{/* Update Popup */}
<UpdatePopup
visible={showUpdatePopup}
updateInfo={updateInfo}
onUpdateNow={handleUpdateNow}
onUpdateLater={handleUpdateLater}
onDismiss={handleDismiss}
isInstalling={isInstalling}
/>
{Platform.OS === 'ios' && (
<UpdatePopup
visible={showUpdatePopup}
updateInfo={updateInfo}
onUpdateNow={handleUpdateNow}
onUpdateLater={handleUpdateLater}
onDismiss={handleDismiss}
isInstalling={isInstalling}
/>
)}
</View>
</NavigationContainer>
</PaperProvider>

View file

@ -0,0 +1,431 @@
import React, { useEffect, useRef } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
BackHandler,
Animated,
} from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { useTheme } from '../contexts/ThemeContext';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const { width } = Dimensions.get('window');
interface AndroidUpdatePopupProps {
visible: boolean;
updateInfo: {
isAvailable: boolean;
manifest?: {
id?: string;
version?: string;
description?: string;
};
};
onUpdateNow: () => void;
onUpdateLater: () => void;
onDismiss: () => void;
isInstalling?: boolean;
}
const AndroidUpdatePopup: React.FC<AndroidUpdatePopupProps> = ({
visible,
updateInfo,
onUpdateNow,
onUpdateLater,
onDismiss,
isInstalling = false,
}) => {
const { currentTheme } = useTheme();
const insets = useSafeAreaInsets();
const backHandlerRef = useRef<any>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const fadeAnim = useRef(new Animated.Value(0)).current;
const scaleAnim = useRef(new Animated.Value(0.8)).current;
const getReleaseNotes = () => {
const manifest: any = updateInfo?.manifest || {};
return (
manifest.description ||
manifest.releaseNotes ||
manifest.extra?.releaseNotes ||
manifest.metadata?.releaseNotes ||
''
);
};
// Handle Android back button
useEffect(() => {
if (visible) {
backHandlerRef.current = BackHandler.addEventListener('hardwareBackPress', () => {
if (!isInstalling) {
onDismiss();
return true;
}
return false;
});
}
return () => {
if (backHandlerRef.current) {
backHandlerRef.current.remove();
backHandlerRef.current = null;
}
};
}, [visible, isInstalling, onDismiss]);
// Animation effects
useEffect(() => {
if (visible) {
// Animate in
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
Animated.spring(scaleAnim, {
toValue: 1,
tension: 100,
friction: 8,
useNativeDriver: true,
}),
]).start();
// Safety timeout
timeoutRef.current = setTimeout(() => {
console.warn('AndroidUpdatePopup: Timeout reached, auto-dismissing');
onDismiss();
}, 30000);
} else {
// Animate out
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 0.8,
duration: 150,
useNativeDriver: true,
}),
]).start();
}
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
}, [visible, fadeAnim, scaleAnim, onDismiss]);
if (!visible || !updateInfo.isAvailable) {
return null;
}
return (
<View style={styles.overlay}>
<Animated.View
style={[
styles.overlayContent,
{
opacity: fadeAnim,
transform: [{ scale: scaleAnim }],
}
]}
>
<TouchableOpacity
style={styles.backdrop}
activeOpacity={1}
onPress={onDismiss}
/>
<View style={[
styles.popup,
{
backgroundColor: currentTheme.colors.darkBackground || '#1a1a1a',
borderColor: currentTheme.colors.elevation2 || '#333333',
marginTop: insets.top + 20,
marginBottom: insets.bottom + 20,
}
]}>
{/* Header */}
<View style={styles.header}>
<View style={[
styles.iconContainer,
{ backgroundColor: `${currentTheme.colors.primary}20` }
]}>
<MaterialIcons
name="system-update"
size={32}
color={currentTheme.colors.primary}
/>
</View>
<Text style={[
styles.title,
{ color: currentTheme.colors.highEmphasis }
]}>
Update Available
</Text>
<Text style={[
styles.subtitle,
{ color: currentTheme.colors.mediumEmphasis }
]}>
A new version of Nuvio is ready to install
</Text>
</View>
{/* Update Info */}
<View style={styles.updateInfo}>
<View style={styles.infoRow}>
<MaterialIcons
name="info-outline"
size={16}
color={currentTheme.colors.primary}
/>
<Text style={[
styles.infoLabel,
{ color: currentTheme.colors.mediumEmphasis }
]}>
Version:
</Text>
<Text
style={[
styles.infoValue,
{ color: currentTheme.colors.highEmphasis }
]}
numberOfLines={3}
ellipsizeMode="tail"
selectable
>
{updateInfo.manifest?.id || 'Latest'}
</Text>
</View>
{!!getReleaseNotes() && (
<View style={styles.descriptionContainer}>
<Text style={[
styles.description,
{ color: currentTheme.colors.mediumEmphasis }
]}>
{getReleaseNotes()}
</Text>
</View>
)}
</View>
{/* Actions */}
<View style={styles.actions}>
<TouchableOpacity
style={[
styles.button,
styles.primaryButton,
{ backgroundColor: currentTheme.colors.primary },
isInstalling && styles.disabledButton
]}
onPress={onUpdateNow}
disabled={isInstalling}
activeOpacity={0.8}
>
{isInstalling ? (
<>
<MaterialIcons name="install-mobile" size={18} color="white" />
<Text style={styles.buttonText}>Installing...</Text>
</>
) : (
<>
<MaterialIcons name="download" size={18} color="white" />
<Text style={styles.buttonText}>Update Now</Text>
</>
)}
</TouchableOpacity>
<View style={styles.secondaryActions}>
<TouchableOpacity
style={[
styles.button,
styles.secondaryButton,
{
backgroundColor: currentTheme.colors.darkBackground || '#2a2a2a',
borderColor: currentTheme.colors.elevation3 || '#444444',
}
]}
onPress={onUpdateLater}
disabled={isInstalling}
activeOpacity={0.7}
>
<Text style={[
styles.secondaryButtonText,
{ color: currentTheme.colors.mediumEmphasis }
]}>
Later
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.button,
styles.secondaryButton,
{
backgroundColor: currentTheme.colors.darkBackground || '#2a2a2a',
borderColor: currentTheme.colors.elevation3 || '#444444',
}
]}
onPress={onDismiss}
disabled={isInstalling}
activeOpacity={0.7}
>
<Text style={[
styles.secondaryButtonText,
{ color: currentTheme.colors.mediumEmphasis }
]}>
Dismiss
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
overlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 9999,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
},
overlayContent: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
backdrop: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'transparent',
},
popup: {
width: Math.min(width - 40, 400),
borderRadius: 20,
borderWidth: 1,
backgroundColor: '#1a1a1a',
elevation: 15,
overflow: 'hidden',
},
header: {
alignItems: 'center',
paddingHorizontal: 24,
paddingTop: 32,
paddingBottom: 20,
},
iconContainer: {
width: 64,
height: 64,
borderRadius: 32,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 16,
},
title: {
fontSize: 24,
fontWeight: '700',
letterSpacing: 0.3,
marginBottom: 8,
textAlign: 'center',
},
subtitle: {
fontSize: 16,
textAlign: 'center',
lineHeight: 22,
},
updateInfo: {
paddingHorizontal: 24,
paddingBottom: 20,
},
infoRow: {
flexDirection: 'row',
alignItems: 'flex-start',
marginBottom: 12,
},
infoLabel: {
fontSize: 14,
fontWeight: '500',
marginLeft: 8,
marginRight: 8,
marginTop: 2,
minWidth: 60,
},
infoValue: {
fontSize: 14,
fontWeight: '600',
flex: 1,
lineHeight: 20,
},
descriptionContainer: {
marginTop: 8,
padding: 12,
borderRadius: 8,
backgroundColor: 'rgba(255, 255, 255, 0.15)',
},
description: {
fontSize: 14,
lineHeight: 20,
},
actions: {
paddingHorizontal: 24,
paddingBottom: 20,
},
button: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 14,
paddingHorizontal: 20,
borderRadius: 12,
gap: 8,
marginBottom: 12,
},
primaryButton: {
elevation: 4,
},
secondaryButton: {
borderWidth: 1,
flex: 1,
marginHorizontal: 4,
},
disabledButton: {
opacity: 0.6,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
letterSpacing: 0.3,
},
secondaryActions: {
flexDirection: 'row',
gap: 8,
},
secondaryButtonText: {
fontSize: 15,
fontWeight: '500',
},
});
export default AndroidUpdatePopup;

View file

@ -12,6 +12,7 @@ import { MaterialIcons } from '@expo/vector-icons';
import { useTheme } from '../contexts/ThemeContext';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import * as Haptics from 'expo-haptics';
import AndroidUpdatePopup from './AndroidUpdatePopup';
const { width, height } = Dimensions.get('window');
@ -54,17 +55,23 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
};
const handleUpdateNow = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
if (Platform.OS === 'ios') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
}
onUpdateNow();
};
const handleUpdateLater = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (Platform.OS === 'ios') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
onUpdateLater();
};
const handleDismiss = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (Platform.OS === 'ios') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
onDismiss();
};
@ -72,12 +79,19 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
return null;
}
// Completely disable popup on Android
if (Platform.OS === 'android') {
return null;
}
// iOS implementation with full features
return (
<Modal
visible={visible}
transparent
animationType="fade"
statusBarTranslucent
statusBarTranslucent={true}
presentationStyle="overFullScreen"
>
<View style={styles.overlay}>
<View style={[
@ -134,8 +148,8 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
styles.infoValue,
{ color: currentTheme.colors.highEmphasis }
]}
numberOfLines={1}
ellipsizeMode="middle"
numberOfLines={3}
ellipsizeMode="tail"
selectable
>
{updateInfo.manifest?.id || 'Latest'}
@ -224,8 +238,6 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
</TouchableOpacity>
</View>
</View>
{/* Footer removed: hardcoded message no longer shown */}
</View>
</View>
</Modal>
@ -245,11 +257,14 @@ const styles = StyleSheet.create({
borderRadius: 20,
borderWidth: 1,
backgroundColor: '#1a1a1a', // Solid background - not transparent
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.5,
shadowRadius: 20,
elevation: 15,
...(Platform.OS === 'ios' ? {
shadowColor: '#000',
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.5,
shadowRadius: 20,
} : {
elevation: 15,
}),
overflow: 'hidden',
},
header: {
@ -284,7 +299,7 @@ const styles = StyleSheet.create({
},
infoRow: {
flexDirection: 'row',
alignItems: 'center',
alignItems: 'flex-start',
marginBottom: 12,
},
infoLabel: {
@ -292,11 +307,14 @@ const styles = StyleSheet.create({
fontWeight: '500',
marginLeft: 8,
marginRight: 8,
marginTop: 2,
minWidth: 60,
},
infoValue: {
fontSize: 14,
fontWeight: '600',
flex: 1,
lineHeight: 20,
},
descriptionContainer: {
marginTop: 8,
@ -323,11 +341,14 @@ const styles = StyleSheet.create({
marginBottom: 12,
},
primaryButton: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 4,
...(Platform.OS === 'ios' ? {
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
} : {
elevation: 4,
}),
},
secondaryButton: {
borderWidth: 1,

View file

@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import { Alert } from 'react-native';
import { Alert, 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';
@ -16,12 +17,14 @@ interface UseUpdatePopupReturn {
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 {
@ -49,13 +52,29 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
setUpdateInfo(info);
if (info.isAvailable) {
setShowUpdatePopup(true);
// Android: use badge instead of popup to avoid freezes
if (Platform.OS === 'android') {
try {
await AsyncStorage.setItem(UPDATE_BADGE_KEY, 'true');
} catch {}
// Show actionable toast instead of popup
try {
toast('Update available — go to Settings → App Updates', {
duration: 3000,
position: ToastPosition.TOP,
});
} catch {}
setShowUpdatePopup(false);
} else {
// iOS: show popup as usual
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]);
}, [updateInfo.manifest?.id, isAppReady]);
const handleUpdateNow = useCallback(async () => {
try {
@ -65,27 +84,9 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
const success = await UpdateService.downloadAndInstallUpdate();
if (success) {
Alert.alert(
'Update Installed',
'The update has been installed successfully. Please restart the app to apply the changes.',
[
{
text: 'Restart Later',
style: 'cancel',
},
{
text: 'Restart Now',
onPress: () => {
// On React Native, we can't programmatically restart the app
// The user will need to manually restart
Alert.alert(
'Restart Required',
'Please close and reopen the app to complete the update.'
);
},
},
]
);
// Update installed successfully - no restart alert needed
// The app will automatically reload with the new version
console.log('Update installed successfully');
} else {
Alert.alert(
'Update Failed',
@ -140,7 +141,21 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
setHasCheckedOnStartup(true);
if (updateInfo.isAvailable) {
setShowUpdatePopup(true);
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);
}
}
};
@ -153,6 +168,35 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
};
}, []);
// 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) {

View file

@ -3,6 +3,7 @@ import { NavigationContainer, DefaultTheme as NavigationDefaultTheme, DarkTheme
import { createNativeStackNavigator, NativeStackNavigationOptions, NativeStackNavigationProp } from '@react-navigation/native-stack';
import { createBottomTabNavigator, BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { useColorScheme, Platform, Animated, StatusBar, TouchableOpacity, View, Text, AppState, Easing, Dimensions } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { PaperProvider, MD3DarkTheme, MD3LightTheme, adaptNavigationTheme } from 'react-native-paper';
import type { MD3Theme } from 'react-native-paper';
import type { BottomTabBarProps } from '@react-navigation/bottom-tabs';
@ -440,6 +441,38 @@ const WrappedScreen: React.FC<{Screen: React.ComponentType<any>}> = ({ Screen })
// Tab Navigator
const MainTabs = () => {
const { currentTheme } = useTheme();
const [hasUpdateBadge, setHasUpdateBadge] = React.useState(false);
React.useEffect(() => {
if (Platform.OS !== 'android') return;
let mounted = true;
const load = async () => {
try {
const flag = await AsyncStorage.getItem('@update_badge_pending');
if (mounted) setHasUpdateBadge(flag === 'true');
} catch {}
};
load();
// Fast poll initially for quick badge appearance, then slow down
const fast = setInterval(load, 800);
const slowTimer = setTimeout(() => {
clearInterval(fast);
const slow = setInterval(load, 10000);
// store slow interval id on closure for cleanup
(load as any)._slow = slow;
}, 6000);
const onAppStateChange = (state: string) => {
if (state === 'active') load();
};
const sub = AppState.addEventListener('change', onAppStateChange);
return () => {
mounted = false;
clearInterval(fast);
// @ts-ignore
if ((load as any)._slow) clearInterval((load as any)._slow);
clearTimeout(slowTimer);
sub.remove();
};
}, []);
const { isHomeLoading } = useLoading();
const isTablet = useMemo(() => {
const { width, height } = Dimensions.get('window');

View file

@ -226,6 +226,19 @@ const Sidebar: React.FC<SidebarProps> = ({ selectedCategory, onCategorySelect, c
const SettingsScreen: React.FC = () => {
const { settings, updateSetting } = useSettings();
const [hasUpdateBadge, setHasUpdateBadge] = useState(false);
useEffect(() => {
if (Platform.OS !== 'android') return;
let mounted = true;
(async () => {
try {
const flag = await AsyncStorage.getItem('@update_badge_pending');
if (mounted) setHasUpdateBadge(flag === 'true');
} catch {}
})();
return () => { mounted = false; };
}, []);
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const { lastUpdate } = useCatalogContext();
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
@ -681,7 +694,14 @@ const SettingsScreen: React.FC = () => {
description="Check for updates and manage app version"
icon="system-update"
renderControl={ChevronRight}
onPress={() => navigation.navigate('Update')}
badge={Platform.OS === 'android' && hasUpdateBadge ? 1 : undefined}
onPress={async () => {
if (Platform.OS === 'android') {
try { await AsyncStorage.removeItem('@update_badge_pending'); } catch {}
setHasUpdateBadge(false);
}
navigation.navigate('Update');
}}
isLast={true}
isTablet={isTablet}
/>

View file

@ -11,6 +11,7 @@ import {
Platform,
Dimensions
} from 'react-native';
import { toast, ToastPosition } from '@backpackapp-io/react-native-toast';
import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons';
@ -18,6 +19,7 @@ import { RootStackParamList } from '../navigation/AppNavigator';
import { useTheme } from '../contexts/ThemeContext';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import UpdateService from '../services/updateService';
import AsyncStorage from '@react-native-async-storage/async-storage';
const { width, height } = Dimensions.get('window');
const isTablet = width >= 768;
@ -104,6 +106,22 @@ const UpdateScreen: React.FC = () => {
}
};
// Auto-check on mount and keep section visible
useEffect(() => {
if (Platform.OS === 'android') {
// ensure badge clears when entering this screen
(async () => {
try { await AsyncStorage.removeItem('@update_badge_pending'); } catch {}
})();
}
checkForUpdates();
if (Platform.OS === 'android') {
try {
toast('Checking for updates…', { duration: 1200, position: ToastPosition.TOP });
} catch {}
}
}, []);
const installUpdate = async () => {
try {
setIsInstalling(true);

View file

@ -250,27 +250,31 @@ export class UpdateService {
this.addLog('Updates are enabled, performing initial update check...', 'INFO');
// Perform an initial update check on app startup
try {
const updateInfo = await this.checkForUpdates();
this.addLog(`Initial update check completed - Updates available: ${updateInfo.isAvailable}`, 'INFO');
// Perform an initial update check on app startup (non-blocking)
// Use setTimeout to defer the check and prevent blocking the main thread
setTimeout(async () => {
try {
this.addLog('Starting deferred update check...', 'INFO');
const updateInfo = await this.checkForUpdates();
this.addLog(`Initial update check completed - Updates available: ${updateInfo.isAvailable}`, 'INFO');
if (updateInfo.isAvailable) {
this.addLog('Update available! The popup will be shown to the user.', 'INFO');
} else {
this.addLog('No updates available at startup', 'INFO');
if (updateInfo.isAvailable) {
this.addLog('Update available! The popup will be shown to the user.', 'INFO');
} else {
this.addLog('No updates available at startup', 'INFO');
}
// Notify registered callbacks about the update check result
this.notifyUpdateCheckCallbacks(updateInfo);
} catch (checkError) {
this.addLog(`Initial update check failed: ${checkError instanceof Error ? checkError.message : String(checkError)}`, 'ERROR');
// Notify callbacks about the failed check
this.notifyUpdateCheckCallbacks({ isAvailable: false });
// Don't fail initialization if update check fails
}
// Notify registered callbacks about the update check result
this.notifyUpdateCheckCallbacks(updateInfo);
} catch (checkError) {
this.addLog(`Initial update check failed: ${checkError instanceof Error ? checkError.message : String(checkError)}`, 'ERROR');
// Notify callbacks about the failed check
this.notifyUpdateCheckCallbacks({ isAvailable: false });
// Don't fail initialization if update check fails
}
}, 1000); // Defer by 1 second to let the app fully initialize
this.addLog('UpdateService initialization completed successfully', 'INFO');
} catch (error) {