diff --git a/App.tsx b/App.tsx
index 2527397c..0ff1b550 100644
--- a/App.tsx
+++ b/App.tsx
@@ -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 && }
{/* Update Popup */}
-
+ {Platform.OS === 'ios' && (
+
+ )}
diff --git a/src/components/AndroidUpdatePopup.tsx b/src/components/AndroidUpdatePopup.tsx
new file mode 100644
index 00000000..31190371
--- /dev/null
+++ b/src/components/AndroidUpdatePopup.tsx
@@ -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 = ({
+ visible,
+ updateInfo,
+ onUpdateNow,
+ onUpdateLater,
+ onDismiss,
+ isInstalling = false,
+}) => {
+ const { currentTheme } = useTheme();
+ const insets = useSafeAreaInsets();
+ const backHandlerRef = useRef(null);
+ const timeoutRef = useRef(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 (
+
+
+
+
+ {/* Header */}
+
+
+
+
+
+ Update Available
+
+
+ A new version of Nuvio is ready to install
+
+
+
+ {/* Update Info */}
+
+
+
+
+ Version:
+
+
+ {updateInfo.manifest?.id || 'Latest'}
+
+
+
+ {!!getReleaseNotes() && (
+
+
+ {getReleaseNotes()}
+
+
+ )}
+
+
+ {/* Actions */}
+
+
+ {isInstalling ? (
+ <>
+
+ Installing...
+ >
+ ) : (
+ <>
+
+ Update Now
+ >
+ )}
+
+
+
+
+
+ Later
+
+
+
+
+
+ Dismiss
+
+
+
+
+
+
+
+ );
+};
+
+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;
diff --git a/src/components/UpdatePopup.tsx b/src/components/UpdatePopup.tsx
index 5033e32b..f9c1351d 100644
--- a/src/components/UpdatePopup.tsx
+++ b/src/components/UpdatePopup.tsx
@@ -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 = ({
};
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 = ({
return null;
}
+ // Completely disable popup on Android
+ if (Platform.OS === 'android') {
+ return null;
+ }
+
+ // iOS implementation with full features
return (
= ({
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 = ({
-
- {/* Footer removed: hardcoded message no longer shown */}
@@ -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,
diff --git a/src/hooks/useUpdatePopup.ts b/src/hooks/useUpdatePopup.ts
index ffd9c5b5..b9a5296c 100644
--- a/src/hooks/useUpdatePopup.ts
+++ b/src/hooks/useUpdatePopup.ts
@@ -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({ 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) {
diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx
index f7957ffc..4c47283f 100644
--- a/src/navigation/AppNavigator.tsx
+++ b/src/navigation/AppNavigator.tsx
@@ -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}> = ({ 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');
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index f217a99c..a385d273 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -226,6 +226,19 @@ const Sidebar: React.FC = ({ 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>();
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}
/>
diff --git a/src/screens/UpdateScreen.tsx b/src/screens/UpdateScreen.tsx
index 8fe7d4fa..2a4fd4a3 100644
--- a/src/screens/UpdateScreen.tsx
+++ b/src/screens/UpdateScreen.tsx
@@ -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);
diff --git a/src/services/updateService.ts b/src/services/updateService.ts
index 7a9df2a9..5375dd4a 100644
--- a/src/services/updateService.ts
+++ b/src/services/updateService.ts
@@ -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) {