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 ? (
-
) : (