diff --git a/android/app/src/main/assets/modules.json b/android/app/src/main/assets/modules.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/android/app/src/main/assets/modules.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/ios/Nuvio.xcodeproj/project.pbxproj b/ios/Nuvio.xcodeproj/project.pbxproj index a248d5a..73cf974 100644 --- a/ios/Nuvio.xcodeproj/project.pbxproj +++ b/ios/Nuvio.xcodeproj/project.pbxproj @@ -189,7 +189,6 @@ TargetAttributes = { 13B07F861A680F5B00A75B9A = { LastSwiftMigration = 1250; - ProvisioningStyle = Automatic; }; }; }; @@ -502,6 +501,7 @@ OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; PRODUCT_NAME = "Nuvio"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios/Nuvio/Nuvio.entitlements b/ios/Nuvio/Nuvio.entitlements index a0bc443..0c67376 100644 --- a/ios/Nuvio/Nuvio.entitlements +++ b/ios/Nuvio/Nuvio.entitlements @@ -1,10 +1,5 @@ - - aps-environment - development - com.apple.developer.associated-domains - - - \ No newline at end of file + + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a1bfa4a..30f302b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -240,15 +240,9 @@ PODS: - ExpoModulesCore - ExpoHaptics (14.0.1): - ExpoModulesCore - - ExpoImage (2.0.7): - - ExpoModulesCore - - libavif/libdav1d - - SDWebImage (~> 5.19.1) - - SDWebImageAVIFCoder (~> 0.11.0) - - SDWebImageSVGCoder (~> 1.7.0) - ExpoKeepAwake (14.0.3): - ExpoModulesCore - - ExpoLibVlcPlayer (2.1.7): + - ExpoLibVlcPlayer (2.2.1): - ExpoModulesCore - MobileVLCKit (= 3.6.1b1) - ExpoLinearGradient (14.0.2): @@ -2519,11 +2513,9 @@ PODS: - SDWebImage (5.19.7): - SDWebImage/Core (= 5.19.7) - SDWebImage/Core (5.19.7) - - SDWebImageAVIFCoder (0.11.0): + - SDWebImageAVIFCoder (0.11.1): - libavif/core (>= 0.11.0) - SDWebImage (~> 5.10) - - SDWebImageSVGCoder (1.7.0): - - SDWebImage/Core (~> 5.6) - SDWebImageWebPCoder (0.14.6): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) @@ -2555,7 +2547,6 @@ DEPENDENCIES: - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - ExpoFont (from `../node_modules/expo-font/ios`) - ExpoHaptics (from `../node_modules/expo-haptics/ios`) - - ExpoImage (from `../node_modules/expo-image/ios`) - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoLibVlcPlayer (from `../node_modules/expo-libvlc-player/ios`) - ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`) @@ -2666,7 +2657,6 @@ SPEC REPOS: - ReachabilitySwift - SDWebImage - SDWebImageAVIFCoder - - SDWebImageSVGCoder - SDWebImageWebPCoder - Sentry - SocketRocket @@ -2719,8 +2709,6 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-font/ios" ExpoHaptics: :path: "../node_modules/expo-haptics/ios" - ExpoImage: - :path: "../node_modules/expo-image/ios" ExpoKeepAwake: :path: "../node_modules/expo-keep-awake/ios" ExpoLibVlcPlayer: @@ -2958,9 +2946,8 @@ SPEC CHECKSUMS: ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655 ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188 ExpoHaptics: 8d199b2f33245ea85289ff6c954c7ee7c00a5b5d - ExpoImage: d840b256050f4428d2942bc2b6e9251f9e0d7021 ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680 - ExpoLibVlcPlayer: 027c16c178364a133f6ee10fc7a7d8636f92dbe8 + ExpoLibVlcPlayer: dce3d0b5847838cd5f8c5f3c3aa1bc55c92e911d ExpoLinearGradient: 35ebd83b16f80b3add053a2fd68cc328ed927f60 ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402 ExpoLocalization: 7776ea3bdb112125390745bbaf919b734b2ad1c7 @@ -3062,8 +3049,7 @@ SPEC CHECKSUMS: RNSVG: b889dc9c1948eeea0576a16cc405c91c37a12c19 RNVectorIcons: c95fdae217b0ed388f2b4d7ed7a4edc457c1df47 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 - SDWebImageAVIFCoder: 00310d246aab3232ce77f1d8f0076f8c4b021d90 - SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c + SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: 1ca8405451040482877dcd344dfa3ef80b646631 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 diff --git a/package-lock.json b/package-lock.json index e700370..c4628d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,9 +47,8 @@ "expo-document-picker": "~13.0.3", "expo-file-system": "~18.0.12", "expo-haptics": "~14.0.1", - "expo-image": "~2.0.7", "expo-intent-launcher": "~12.0.2", - "expo-libvlc-player": "^2.2.1", + "expo-libvlc-player": "^2.1.7", "expo-linear-gradient": "~14.0.2", "expo-localization": "~16.0.1", "expo-notifications": "~0.29.14", @@ -7986,23 +7985,6 @@ "expo": "*" } }, - "node_modules/expo-image": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-2.0.7.tgz", - "integrity": "sha512-kv40OIJOkItwznhdqFmKxTMC5O8GkpyTf8ng7Py4Hy6IBiH59dkeP6vUZQhzPhJOm5v1kZK4XldbskBosqzOug==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*", - "react-native-web": "*" - }, - "peerDependenciesMeta": { - "react-native-web": { - "optional": true - } - } - }, "node_modules/expo-intent-launcher": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/expo-intent-launcher/-/expo-intent-launcher-12.0.2.tgz", diff --git a/package.json b/package.json index 7ea9b4a..88e5ddb 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "expo-document-picker": "~13.0.3", "expo-file-system": "~18.0.12", "expo-haptics": "~14.0.1", - "expo-image": "~2.0.7", "expo-intent-launcher": "~12.0.2", "expo-libvlc-player": "^2.1.7", "expo-linear-gradient": "~14.0.2", diff --git a/src/components/common/OptimizedImage.tsx b/src/components/common/OptimizedImage.tsx index bf3e65a..b9d26b9 100644 --- a/src/components/common/OptimizedImage.tsx +++ b/src/components/common/OptimizedImage.tsx @@ -106,7 +106,7 @@ const OptimizedImage: React.FC = ({ if (!optimizedUrl || !isVisible) return; try { - await FastImage.preload([{ uri: optimizedUrl }]); + FastImage.preload([{ uri: optimizedUrl }]); if (!mountedRef.current) return; setIsLoaded(true); onLoad?.(); diff --git a/src/components/home/FeaturedContent.tsx b/src/components/home/FeaturedContent.tsx index 7d2f1d6..9157cdc 100644 --- a/src/components/home/FeaturedContent.tsx +++ b/src/components/home/FeaturedContent.tsx @@ -232,10 +232,9 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin }, 1500); }); - await Promise.race([ - FastImage.preload([{ uri: url }]), - timeout, - ]); + // FastImage.preload doesn't return a promise, so we just call it and use timeout + FastImage.preload([{ uri: url }]); + await timeout; imageCache[url] = true; logger.debug('[FeaturedContent] preloadImage:success', { url, duration: since(t0) }); return true; @@ -569,7 +568,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin priority: FastImage.priority.high, cache: FastImage.cacheControl.immutable }} - style={styles.tabletLogo as ImageStyle} + style={styles.tabletLogo as any} resizeMode={FastImage.resizeMode.contain} onError={onLogoLoadError} /> @@ -703,7 +702,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin priority: FastImage.priority.high, cache: FastImage.cacheControl.immutable }} - style={styles.featuredLogo as ImageStyle} + style={styles.featuredLogo as any} resizeMode={FastImage.resizeMode.contain} onError={onLogoLoadError} /> diff --git a/src/components/home/HeroCarousel.tsx b/src/components/home/HeroCarousel.tsx index 0752d13..d67408e 100644 --- a/src/components/home/HeroCarousel.tsx +++ b/src/components/home/HeroCarousel.tsx @@ -166,9 +166,8 @@ const HeroCarousel: React.FC = ({ items, loading = false }) = priority: FastImage.priority.low, cache: FastImage.cacheControl.immutable }} - style={styles.backgroundImage as ImageStyle} + style={styles.backgroundImage as any} resizeMode={FastImage.resizeMode.cover} - blurRadius={Platform.OS === 'android' ? 8 : 12} /> = memo(({ item, colors, logoFail priority: FastImage.priority.normal, cache: FastImage.cacheControl.immutable }} - style={styles.banner as ImageStyle} + style={styles.banner as any} resizeMode={FastImage.resizeMode.cover} /> = memo(({ item, colors, logoFail priority: FastImage.priority.high, cache: FastImage.cacheControl.immutable }} - style={styles.logo as ImageStyle} + style={styles.logo as any} resizeMode={FastImage.resizeMode.contain} onError={onLogoError} /> diff --git a/src/components/metadata/CastDetailsModal.tsx b/src/components/metadata/CastDetailsModal.tsx index 132b80a..8b7f4f6 100644 --- a/src/components/metadata/CastDetailsModal.tsx +++ b/src/components/metadata/CastDetailsModal.tsx @@ -10,7 +10,7 @@ import { } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; import { BlurView } from 'expo-blur'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import Animated, { FadeIn, FadeOut, @@ -228,12 +228,12 @@ export const CastDetailsModal: React.FC = ({ backgroundColor: 'rgba(255, 255, 255, 0.08)', }}> {castMember?.profile_path ? ( - ) : ( = ({ > {item.profile_path ? ( - ) : ( diff --git a/src/components/metadata/FloatingHeader.tsx b/src/components/metadata/FloatingHeader.tsx index f5c4286..efcdeeb 100644 --- a/src/components/metadata/FloatingHeader.tsx +++ b/src/components/metadata/FloatingHeader.tsx @@ -9,7 +9,7 @@ import { } from 'react-native'; import { BlurView as ExpoBlurView } from 'expo-blur'; import { MaterialIcons } from '@expo/vector-icons'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import Animated, { useAnimatedStyle, interpolate, @@ -96,11 +96,10 @@ const FloatingHeader: React.FC = ({ {metadata.logo && !logoLoadError ? ( - { logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`); setLogoLoadError(true); @@ -146,11 +145,10 @@ const FloatingHeader: React.FC = ({ {metadata.logo && !logoLoadError ? ( - { logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`); setLogoLoadError(true); diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 04a3996..8a0a811 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -13,7 +13,7 @@ import { useFocusEffect, useIsFocused } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { BlurView as ExpoBlurView } from 'expo-blur'; import { BlurView as CommunityBlurView } from '@react-native-community/blur'; import Constants, { ExecutionEnvironment } from 'expo-constants'; @@ -1489,11 +1489,10 @@ const HeroSection: React.FC = memo(({ {shouldLoadSecondaryData && metadata.logo && !logoLoadError ? ( - { runOnJS(setLogoLoadError)(true); }} diff --git a/src/components/metadata/MetadataDetails.tsx b/src/components/metadata/MetadataDetails.tsx index 13626f2..14463d7 100644 --- a/src/components/metadata/MetadataDetails.tsx +++ b/src/components/metadata/MetadataDetails.tsx @@ -7,7 +7,7 @@ import { ActivityIndicator, } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import Animated, { FadeIn, useAnimatedStyle, @@ -162,10 +162,10 @@ function formatRuntime(runtime: string): string { )} {metadata.imdbRating && !isMDBEnabled && ( - {metadata.imdbRating} diff --git a/src/components/metadata/MoreLikeThisSection.tsx b/src/components/metadata/MoreLikeThisSection.tsx index 7830f1c..64d97b9 100644 --- a/src/components/metadata/MoreLikeThisSection.tsx +++ b/src/components/metadata/MoreLikeThisSection.tsx @@ -8,7 +8,7 @@ import { ActivityIndicator, Dimensions, } from 'react-native'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { useNavigation, StackActions } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { RootStackParamList } from '../../navigation/AppNavigator'; @@ -97,11 +97,10 @@ export const MoreLikeThisSection: React.FC = ({ style={styles.itemContainer} onPress={() => handleItemPress(item)} > - {item.name} diff --git a/src/components/metadata/SeriesContent.tsx b/src/components/metadata/SeriesContent.tsx index 2f5fb37..555d7f9 100644 --- a/src/components/metadata/SeriesContent.tsx +++ b/src/components/metadata/SeriesContent.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList } from 'react-native'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { MaterialIcons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { FlashList, FlashListRef } from '@shopify/flash-list'; @@ -454,10 +454,10 @@ export const SeriesContent: React.FC = ({ onPress={() => onSeasonChange(season)} > - {selectedSeason === season && ( = ({ styles.episodeImageContainer, isTablet && styles.episodeImageContainerTablet ]}> - {episodeString} @@ -605,10 +605,10 @@ export const SeriesContent: React.FC = ({ ]}> {effectiveVote > 0 && ( - {effectiveVote.toFixed(1)} @@ -727,10 +727,10 @@ export const SeriesContent: React.FC = ({ {/* Background Image */} - {/* Standard Gradient Overlay */} diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index ddffe64..485ac44 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -1,7 +1,8 @@ import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; -import { View, TouchableOpacity, TouchableWithoutFeedback, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, Image, StyleSheet, Modal, AppState } from 'react-native'; +import { View, TouchableOpacity, TouchableWithoutFeedback, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Video, { VideoRef, SelectedTrack, SelectedTrackType, BufferingStrategyType, ViewType } from 'react-native-video'; +import FastImage from '@d11/react-native-fast-image'; import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native'; import { RootStackParamList } from '../../navigation/AppNavigator'; import { PinchGestureHandler, PanGestureHandler, TapGestureHandler, State, PinchGestureHandlerGestureEvent, PanGestureHandlerGestureEvent, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'; @@ -586,22 +587,21 @@ const AndroidVideoPlayer: React.FC = () => { backdropImageOpacityAnim.setValue(0); // Prefetch the image - Image.prefetch(backdrop) - .then(() => { - // Image loaded successfully, fade it in smoothly - setIsBackdropLoaded(true); - Animated.timing(backdropImageOpacityAnim, { - toValue: 1, - duration: 400, - useNativeDriver: true, - }).start(); - }) - .catch((error) => { - // If prefetch fails, still show the image but without animation - if (__DEV__) logger.warn('[AndroidVideoPlayer] Backdrop prefetch failed, showing anyway:', error); - setIsBackdropLoaded(true); - backdropImageOpacityAnim.setValue(1); - }); + try { + FastImage.preload([{ uri: backdrop }]); + // Image prefetch initiated, fade it in smoothly + setIsBackdropLoaded(true); + Animated.timing(backdropImageOpacityAnim, { + toValue: 1, + duration: 400, + useNativeDriver: true, + }).start(); + } catch (error) { + // If prefetch fails, still show the image but without animation + if (__DEV__) logger.warn('[AndroidVideoPlayer] Backdrop prefetch failed, showing anyway:', error); + setIsBackdropLoaded(true); + backdropImageOpacityAnim.setValue(1); + } } else { // No backdrop provided, consider it "loaded" setIsBackdropLoaded(true); @@ -612,7 +612,11 @@ const AndroidVideoPlayer: React.FC = () => { useEffect(() => { const logoUrl = (metadata && (metadata as any).logo) as string | undefined; if (logoUrl && typeof logoUrl === 'string') { - Image.prefetch(logoUrl).catch(() => {}); + try { + FastImage.preload([{ uri: logoUrl }]); + } catch (error) { + // Silently ignore logo prefetch errors + } } }, [metadata]); @@ -3112,19 +3116,20 @@ const AndroidVideoPlayer: React.FC = () => { pointerEvents={isOpeningAnimationComplete ? 'none' : 'auto'} > {backdrop && ( - + ]}> + + )} { opacity: logoOpacityAnim, alignItems: 'center', }}> - @@ -3599,7 +3604,7 @@ const AndroidVideoPlayer: React.FC = () => { shadowRadius: 8, elevation: 5, }}> - { borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.1)' }} - resizeMode="cover" + resizeMode={FastImage.resizeMode.cover} /> )} diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index 2952138..0d63312 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -1,7 +1,8 @@ import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; -import { View, TouchableOpacity, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, Image, StyleSheet, Modal, AppState } from 'react-native'; +import { View, TouchableOpacity, Dimensions, Animated, ActivityIndicator, Platform, NativeModules, StatusBar, Text, StyleSheet, Modal, AppState } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native'; +import FastImage from '@d11/react-native-fast-image'; import { RootStackParamList, RootStackNavigationProp } from '../../navigation/AppNavigator'; import { PinchGestureHandler, PanGestureHandler, TapGestureHandler, State, PinchGestureHandlerGestureEvent, PanGestureHandlerGestureEvent, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'; import RNImmersiveMode from 'react-native-immersive-mode'; @@ -317,22 +318,21 @@ const KSPlayerCore: React.FC = () => { backdropImageOpacityAnim.setValue(0); // Prefetch the image - Image.prefetch(backdrop) - .then(() => { - // Image loaded successfully, fade it in smoothly - setIsBackdropLoaded(true); - Animated.timing(backdropImageOpacityAnim, { - toValue: 1, - duration: 400, - useNativeDriver: true, - }).start(); - }) - .catch((error) => { - // If prefetch fails, still show the image but without animation - if (__DEV__) logger.warn('[VideoPlayer] Backdrop prefetch failed, showing anyway:', error); - setIsBackdropLoaded(true); - backdropImageOpacityAnim.setValue(1); - }); + try { + FastImage.preload([{ uri: backdrop }]); + // Image prefetch initiated, fade it in smoothly + setIsBackdropLoaded(true); + Animated.timing(backdropImageOpacityAnim, { + toValue: 1, + duration: 400, + useNativeDriver: true, + }).start(); + } catch (error) { + // If prefetch fails, still show the image but without animation + if (__DEV__) logger.warn('[VideoPlayer] Backdrop prefetch failed, showing anyway:', error); + setIsBackdropLoaded(true); + backdropImageOpacityAnim.setValue(1); + } } else { // No backdrop provided, consider it "loaded" setIsBackdropLoaded(true); @@ -343,7 +343,11 @@ const KSPlayerCore: React.FC = () => { useEffect(() => { const logoUrl = (metadata && (metadata as any).logo) as string | undefined; if (logoUrl && typeof logoUrl === 'string') { - Image.prefetch(logoUrl).catch(() => {}); + try { + FastImage.preload([{ uri: logoUrl }]); + } catch (error) { + // Silently ignore logo prefetch errors + } } }, [metadata]); // Resolve current episode description for series @@ -2431,19 +2435,20 @@ const KSPlayerCore: React.FC = () => { pointerEvents={shouldHideOpeningOverlay ? 'none' : 'auto'} > {backdrop && ( - + ]}> + + )} { opacity: logoOpacityAnim, alignItems: 'center', }}> - @@ -2817,7 +2822,7 @@ const KSPlayerCore: React.FC = () => { shadowRadius: 8, elevation: 5, }}> - { borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.1)' }} - resizeMode="cover" + resizeMode={FastImage.resizeMode.cover} /> )} diff --git a/src/hooks/useMetadataAssets.ts b/src/hooks/useMetadataAssets.ts index 6696126..052632c 100644 --- a/src/hooks/useMetadataAssets.ts +++ b/src/hooks/useMetadataAssets.ts @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { logger } from '../utils/logger'; import { TMDBService } from '../services/tmdbService'; import { isTmdbUrl } from '../utils/logoUtils'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import AsyncStorage from '@react-native-async-storage/async-storage'; // Cache for image availability checks @@ -176,7 +176,7 @@ export const useMetadataAssets = ( if (logoUrl) { // Preload the image - await Image.prefetch(logoUrl); + FastImage.preload([{ uri: logoUrl }]); setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: logoUrl })); } @@ -275,7 +275,7 @@ export const useMetadataAssets = ( // Preload the image if (finalBanner) { - await Image.prefetch(finalBanner); + FastImage.preload([{ uri: finalBanner }]); } } else if (details?.poster_path) { @@ -284,7 +284,7 @@ export const useMetadataAssets = ( // Preload the image if (finalBanner) { - await Image.prefetch(finalBanner); + FastImage.preload([{ uri: finalBanner }]); } } } catch (error) { diff --git a/src/screens/AIChatScreen.tsx b/src/screens/AIChatScreen.tsx index 584c629..e90dffc 100644 --- a/src/screens/AIChatScreen.tsx +++ b/src/screens/AIChatScreen.tsx @@ -18,7 +18,7 @@ import CustomAlert from '../components/CustomAlert'; import { useRoute, useNavigation, RouteProp, useFocusEffect } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import { useTheme } from '../contexts/ThemeContext'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { BlurView as ExpoBlurView } from 'expo-blur'; // Lazy-safe community blur import (avoid bundling issues on web) let AndroidBlurView: any = null; @@ -642,11 +642,10 @@ const AIChatScreen: React.FC = () => { {backdropUrl && ( - {Platform.OS === 'android' && AndroidBlurView ? diff --git a/src/screens/AccountManageScreen.tsx b/src/screens/AccountManageScreen.tsx index f523253..6b129dd 100644 --- a/src/screens/AccountManageScreen.tsx +++ b/src/screens/AccountManageScreen.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, StatusBar, Platform, Animated, Easing, TextInput, ActivityIndicator } from 'react-native'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { useNavigation } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { MaterialIcons } from '@expo/vector-icons'; @@ -110,10 +110,10 @@ const AccountManageScreen: React.FC = () => { {avatarUrl && !avatarError ? ( - setAvatarError(true)} /> diff --git a/src/screens/AddonsScreen.tsx b/src/screens/AddonsScreen.tsx index 109f562..fa0d1d9 100644 --- a/src/screens/AddonsScreen.tsx +++ b/src/screens/AddonsScreen.tsx @@ -21,7 +21,7 @@ import { } from 'react-native'; import { stremioService, Manifest } from '../services/stremioService'; import { MaterialIcons } from '@expo/vector-icons'; -import { Image as ExpoImage } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { LinearGradient } from 'expo-linear-gradient'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; @@ -972,10 +972,10 @@ const AddonsScreen = () => { {logo ? ( - ) : ( @@ -1048,10 +1048,10 @@ const AddonsScreen = () => { return ( {logo ? ( - ) : ( @@ -1240,10 +1240,10 @@ const AddonsScreen = () => { {promoAddon.logo ? ( - ) : ( @@ -1318,10 +1318,10 @@ const AddonsScreen = () => { {item.manifest.logo ? ( - ) : ( @@ -1419,10 +1419,10 @@ const AddonsScreen = () => { {/* @ts-ignore */} {addonDetails.logo ? ( - ) : ( diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx index a3d01ac..e2eb31a 100644 --- a/src/screens/CalendarScreen.tsx +++ b/src/screens/CalendarScreen.tsx @@ -16,7 +16,7 @@ import { import { InteractionManager } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { MaterialIcons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { useTheme } from '../contexts/ThemeContext'; @@ -138,11 +138,10 @@ const CalendarScreen = () => { onPress={() => handleSeriesPress(item.seriesId, item)} activeOpacity={0.7} > - diff --git a/src/screens/CastMoviesScreen.tsx b/src/screens/CastMoviesScreen.tsx index 80e3032..87e397c 100644 --- a/src/screens/CastMoviesScreen.tsx +++ b/src/screens/CastMoviesScreen.tsx @@ -10,7 +10,7 @@ import { FlatList, } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import Animated, { FadeIn, FadeOut, @@ -379,12 +379,12 @@ const CastMoviesScreen: React.FC = () => { backgroundColor: 'rgba(255, 255, 255, 0.05)', }}> {item.poster_path ? ( - ) : ( { backgroundColor: 'rgba(255, 255, 255, 0.08)', }}> {castMember?.profile_path ? ( - ) : ( = ({ route, navigation }) => { onPress={() => navigation.navigate('Metadata', { id: item.id, type: item.type, addonId })} activeOpacity={0.7} > - {type === 'movie' && nowPlayingMovies.has(item.id) && ( diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 2534421..d3784e4 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -463,7 +463,7 @@ const HomeScreen = () => { })); // Preload all images at once - FastImage handles batching internally - await FastImage.preload(sources); + FastImage.preload(sources); } catch (error) { // Silently handle preload errors if (__DEV__) console.warn('Image preload error:', error); diff --git a/src/screens/LibraryScreen.tsx b/src/screens/LibraryScreen.tsx index 731342c..75088ed 100644 --- a/src/screens/LibraryScreen.tsx +++ b/src/screens/LibraryScreen.tsx @@ -23,7 +23,7 @@ import { FlashList } from '@shopify/flash-list'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { LinearGradient } from 'expo-linear-gradient'; import { catalogService } from '../services/catalogService'; @@ -119,13 +119,10 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item: {posterUrl ? ( - ) : ( @@ -403,12 +400,10 @@ const LibraryScreen = () => { > - {item.watched && ( diff --git a/src/screens/PluginsScreen.tsx b/src/screens/PluginsScreen.tsx index e093991..0fc774a 100644 --- a/src/screens/PluginsScreen.tsx +++ b/src/screens/PluginsScreen.tsx @@ -16,7 +16,7 @@ import { Animated, } from 'react-native'; import CustomAlert from '../components/CustomAlert'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { useNavigation } from '@react-navigation/native'; @@ -1598,10 +1598,10 @@ const PluginsScreen: React.FC = () => { {scraper.logo ? ( - ) : ( diff --git a/src/screens/SearchScreen.tsx b/src/screens/SearchScreen.tsx index 95942f5..be92d0b 100644 --- a/src/screens/SearchScreen.tsx +++ b/src/screens/SearchScreen.tsx @@ -22,7 +22,7 @@ import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import { catalogService, StreamingContent, GroupedSearchResults, AddonSearchResults } from '../services/catalogService'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import debounce from 'lodash/debounce'; import { DropUpMenu } from '../components/home/DropUpMenu'; import { DeviceEventEmitter, Share } from 'react-native'; @@ -571,11 +571,10 @@ const SearchScreen = () => { backgroundColor: currentTheme.colors.darkBackground, borderColor: 'rgba(255,255,255,0.05)' }]}> - {/* Bookmark and watched icons top right, bookmark to the left of watched */} {inLibrary && ( diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 1995e2a..f5a0f5c 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -10,7 +10,6 @@ import { StatusBar, Platform, Dimensions, - Image, Button, Linking, Clipboard @@ -18,6 +17,7 @@ import { import AsyncStorage from '@react-native-async-storage/async-storage'; import { useNavigation } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; +import FastImage from '@d11/react-native-fast-image'; import { MaterialIcons } from '@expo/vector-icons'; import { Picker } from '@react-native-picker/picker'; import { useSettings, DEFAULT_SETTINGS } from '../hooks/useSettings'; @@ -805,10 +805,10 @@ const SettingsScreen: React.FC = () => { activeOpacity={0.7} > - Join Discord @@ -821,10 +821,10 @@ const SettingsScreen: React.FC = () => { onPress={() => Linking.openURL('https://ko-fi.com/tapframe')} activeOpacity={0.7} > - @@ -892,10 +892,10 @@ const SettingsScreen: React.FC = () => { activeOpacity={0.7} > - Join Discord @@ -908,10 +908,10 @@ const SettingsScreen: React.FC = () => { onPress={() => Linking.openURL('https://ko-fi.com/tapframe')} activeOpacity={0.7} > - diff --git a/src/screens/ShowRatingsScreen.tsx b/src/screens/ShowRatingsScreen.tsx index aa820e8..53b1bfd 100644 --- a/src/screens/ShowRatingsScreen.tsx +++ b/src/screens/ShowRatingsScreen.tsx @@ -10,7 +10,7 @@ import { Platform, StatusBar, } from 'react-native'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { BlurView } from 'expo-blur'; import { useTheme } from '../contexts/ThemeContext'; import { TMDBService, TMDBShow as Show, TMDBSeason, TMDBEpisode } from '../services/tmdbService'; @@ -175,11 +175,10 @@ const RatingSourceToggle = memo(({ ratingSource, setRatingSource, theme }: { const ShowInfo = memo(({ show, theme }: { show: Show | null, theme: any }) => ( - {show?.name} diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index 5eb0889..42e67d0 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -31,7 +31,7 @@ import { RouteProp } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { MaterialIcons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; -import { Image } from 'expo-image'; +import FastImage from '@d11/react-native-fast-image'; import { RootStackParamList, RootStackNavigationProp } from '../navigation/AppNavigator'; import { useMetadata } from '../hooks/useMetadata'; import { useMetadataAssets } from '../hooks/useMetadataAssets'; @@ -116,10 +116,10 @@ const AnimatedImage = memo(({ return ( - @@ -336,10 +336,10 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the {/* Scraper Logo */} {showLogos && scraperLogo && ( - )} @@ -1968,10 +1968,10 @@ export const StreamsScreen = () => { {metadata.logo && !movieLogoError ? ( - setMovieLogoError(true)} /> ) : ( @@ -2021,7 +2021,7 @@ export const StreamsScreen = () => { {effectiveEpisodeVote > 0 && ( - + {effectiveEpisodeVote.toFixed(1)} diff --git a/src/screens/TMDBSettingsScreen.tsx b/src/screens/TMDBSettingsScreen.tsx index f331bc9..42401de 100644 --- a/src/screens/TMDBSettingsScreen.tsx +++ b/src/screens/TMDBSettingsScreen.tsx @@ -14,7 +14,6 @@ import { Keyboard, Clipboard, Switch, - Image, KeyboardAvoidingView, TouchableWithoutFeedback, Modal, @@ -22,6 +21,7 @@ import { import { useNavigation } from '@react-navigation/native'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import FastImage from '@d11/react-native-fast-image'; import { tmdbService } from '../services/tmdbService'; import { useSettings } from '../hooks/useSettings'; import { logger } from '../utils/logger'; @@ -387,17 +387,17 @@ const TMDBSettingsScreen = () => { return ( - {logo && ( - )} {!logo && ( diff --git a/src/screens/TraktSettingsScreen.tsx b/src/screens/TraktSettingsScreen.tsx index 79d978f..74acf7c 100644 --- a/src/screens/TraktSettingsScreen.tsx +++ b/src/screens/TraktSettingsScreen.tsx @@ -5,7 +5,6 @@ import { StyleSheet, TouchableOpacity, ActivityIndicator, - Image, SafeAreaView, ScrollView, StatusBar, @@ -16,6 +15,7 @@ import { import { useNavigation } from '@react-navigation/native'; import { makeRedirectUri, useAuthRequest, ResponseType, Prompt, CodeChallengeMethod } from 'expo-auth-session'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; +import FastImage from '@d11/react-native-fast-image'; import { traktService, TraktUser } from '../services/traktService'; import { useSettings } from '../hooks/useSettings'; import { logger } from '../utils/logger'; @@ -259,9 +259,10 @@ const TraktSettingsScreen: React.FC = () => { {userProfile.avatar ? ( - ) : (