changes CP

This commit is contained in:
tapframe 2025-10-12 03:02:27 +05:30
parent 303c4c909e
commit f605dd3d49
34 changed files with 191 additions and 238 deletions

View file

@ -0,0 +1 @@
{}

View file

@ -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";

View file

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array/>
</dict>
</plist>
<dict/>
</plist>

View file

@ -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

20
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -106,7 +106,7 @@ const OptimizedImage: React.FC<OptimizedImageProps> = ({
if (!optimizedUrl || !isVisible) return;
try {
await FastImage.preload([{ uri: optimizedUrl }]);
FastImage.preload([{ uri: optimizedUrl }]);
if (!mountedRef.current) return;
setIsLoaded(true);
onLoad?.();

View file

@ -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}
/>

View file

@ -166,9 +166,8 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ 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}
/>
<LinearGradient
colors={["rgba(0,0,0,0.45)", "rgba(0,0,0,0.75)"]}
@ -291,7 +290,7 @@ const CarouselCard: React.FC<CarouselCardProps> = 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}
/>
<LinearGradient
@ -308,7 +307,7 @@ const CarouselCard: React.FC<CarouselCardProps> = 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}
/>

View file

@ -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<CastDetailsModalProps> = ({
backgroundColor: 'rgba(255, 255, 255, 0.08)',
}}>
{castMember?.profile_path ? (
<Image
<FastImage
source={{
uri: `https://image.tmdb.org/t/p/w185${castMember.profile_path}`,
}}
style={{ width: '100%', height: '100%' }}
contentFit="cover"
resizeMode={FastImage.resizeMode.cover}
/>
) : (
<View style={{

View file

@ -7,7 +7,7 @@ import {
TouchableOpacity,
ActivityIndicator,
} from 'react-native';
import { Image } from 'expo-image';
import FastImage from '@d11/react-native-fast-image';
import Animated, {
FadeIn,
} from 'react-native-reanimated';
@ -65,13 +65,12 @@ export const CastSection: React.FC<CastSectionProps> = ({
>
<View style={styles.castImageContainer}>
{item.profile_path ? (
<Image
<FastImage
source={{
uri: `https://image.tmdb.org/t/p/w185${item.profile_path}`,
}}
style={styles.castImage}
contentFit="cover"
transition={200}
resizeMode={FastImage.resizeMode.cover}
/>
) : (
<View style={[styles.castImagePlaceholder, { backgroundColor: currentTheme.colors.darkBackground }]}>

View file

@ -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<FloatingHeaderProps> = ({
<View style={styles.headerTitleContainer}>
{metadata.logo && !logoLoadError ? (
<Image
<FastImage
source={{ uri: metadata.logo }}
style={styles.floatingHeaderLogo}
contentFit="contain"
transition={150}
resizeMode={FastImage.resizeMode.contain}
onError={() => {
logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`);
setLogoLoadError(true);
@ -146,11 +145,10 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
<View style={styles.headerTitleContainer}>
{metadata.logo && !logoLoadError ? (
<Image
<FastImage
source={{ uri: metadata.logo }}
style={styles.floatingHeaderLogo}
contentFit="contain"
transition={150}
resizeMode={FastImage.resizeMode.contain}
onError={() => {
logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`);
setLogoLoadError(true);

View file

@ -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<HeroSectionProps> = memo(({
<Animated.View style={[styles.logoContainer, titleCardAnimatedStyle]}>
<Animated.View style={[styles.titleLogoContainer, logoAnimatedStyle]}>
{shouldLoadSecondaryData && metadata.logo && !logoLoadError ? (
<Image
<FastImage
source={{ uri: metadata.logo }}
style={isTablet ? styles.tabletTitleLogo : styles.titleLogo}
contentFit="contain"
transition={150}
resizeMode={FastImage.resizeMode.contain}
onError={() => {
runOnJS(setLogoLoadError)(true);
}}

View file

@ -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 && (
<View style={styles.ratingContainer}>
<Image
<FastImage
source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/IMDB_Logo_2016.svg/575px-IMDB_Logo_2016.svg.png' }}
style={styles.imdbLogo}
contentFit="contain"
resizeMode={FastImage.resizeMode.contain}
/>
<Text style={[styles.ratingText, { color: currentTheme.colors.text }]}>{metadata.imdbRating}</Text>
</View>

View file

@ -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<MoreLikeThisSectionProps> = ({
style={styles.itemContainer}
onPress={() => handleItemPress(item)}
>
<Image
<FastImage
source={{ uri: item.poster }}
style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1 }]}
contentFit="cover"
transition={200}
resizeMode={FastImage.resizeMode.cover}
/>
<Text style={[styles.title, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={2}>
{item.name}

View file

@ -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<SeriesContentProps> = ({
onPress={() => onSeasonChange(season)}
>
<View style={[styles.seasonPosterContainer, isTablet && styles.seasonPosterContainerTablet]}>
<Image
<FastImage
source={{ uri: seasonPoster }}
style={styles.seasonPoster}
contentFit="cover"
resizeMode={FastImage.resizeMode.cover}
/>
{selectedSeason === season && (
<View style={[
@ -559,10 +559,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
styles.episodeImageContainer,
isTablet && styles.episodeImageContainerTablet
]}>
<Image
<FastImage
source={{ uri: episodeImage }}
style={styles.episodeImage}
contentFit="cover"
resizeMode={FastImage.resizeMode.cover}
/>
<View style={styles.episodeNumberBadge}>
<Text style={styles.episodeNumberText}>{episodeString}</Text>
@ -605,10 +605,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
]}>
{effectiveVote > 0 && (
<View style={styles.ratingContainer}>
<Image
<FastImage
source={{ uri: TMDB_LOGO }}
style={styles.tmdbLogo}
contentFit="contain"
resizeMode={FastImage.resizeMode.contain}
/>
<Text style={[styles.ratingText, { color: currentTheme.colors.textMuted }]}>
{effectiveVote.toFixed(1)}
@ -727,10 +727,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
</View>
{/* Background Image */}
<Image
<FastImage
source={{ uri: episodeImage }}
style={styles.episodeBackgroundImage}
contentFit="cover"
resizeMode={FastImage.resizeMode.cover}
/>
{/* Standard Gradient Overlay */}

View file

@ -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 && (
<Animated.Image
source={{ uri: backdrop }}
style={[
<Animated.View style={[
StyleSheet.absoluteFill,
{
width: screenDimensions.width,
height: screenDimensions.height,
opacity: backdropImageOpacityAnim
}
]}
resizeMode="cover"
blurRadius={0}
/>
]}>
<FastImage
source={{ uri: backdrop }}
style={StyleSheet.absoluteFillObject}
resizeMode={FastImage.resizeMode.cover}
/>
</Animated.View>
)}
<LinearGradient
colors={[
@ -3155,13 +3160,13 @@ const AndroidVideoPlayer: React.FC = () => {
opacity: logoOpacityAnim,
alignItems: 'center',
}}>
<Image
<FastImage
source={{ uri: metadata.logo }}
style={{
width: 300,
height: 180,
resizeMode: 'contain',
}}
resizeMode={FastImage.resizeMode.contain}
/>
</Animated.View>
</>
@ -3599,7 +3604,7 @@ const AndroidVideoPlayer: React.FC = () => {
shadowRadius: 8,
elevation: 5,
}}>
<Image
<FastImage
source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }}
style={{
width: Math.min(120, screenDimensions.width * 0.18),
@ -3607,7 +3612,7 @@ const AndroidVideoPlayer: React.FC = () => {
borderRadius: 12,
backgroundColor: 'rgba(255,255,255,0.1)'
}}
resizeMode="cover"
resizeMode={FastImage.resizeMode.cover}
/>
</View>
)}

View file

@ -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 && (
<Animated.Image
source={{ uri: backdrop }}
style={[
<Animated.View style={[
StyleSheet.absoluteFill,
{
width: screenDimensions.width,
height: screenDimensions.height,
opacity: backdropImageOpacityAnim
}
]}
resizeMode="cover"
blurRadius={0}
/>
]}>
<FastImage
source={{ uri: backdrop }}
style={StyleSheet.absoluteFillObject}
resizeMode={FastImage.resizeMode.cover}
/>
</Animated.View>
)}
<LinearGradient
colors={[
@ -2474,13 +2479,13 @@ const KSPlayerCore: React.FC = () => {
opacity: logoOpacityAnim,
alignItems: 'center',
}}>
<Image
<FastImage
source={{ uri: metadata.logo }}
style={{
width: 300,
height: 180,
resizeMode: 'contain',
}}
resizeMode={FastImage.resizeMode.contain}
/>
</Animated.View>
</>
@ -2817,7 +2822,7 @@ const KSPlayerCore: React.FC = () => {
shadowRadius: 8,
elevation: 5,
}}>
<Image
<FastImage
source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }}
style={{
width: Math.min(120, screenDimensions.width * 0.18),
@ -2825,7 +2830,7 @@ const KSPlayerCore: React.FC = () => {
borderRadius: 12,
backgroundColor: 'rgba(255,255,255,0.1)'
}}
resizeMode="cover"
resizeMode={FastImage.resizeMode.cover}
/>
</View>
)}

View file

@ -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) {

View file

@ -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 = () => {
<SafeAreaView edges={['top','bottom']} style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
{backdropUrl && (
<View style={StyleSheet.absoluteFill} pointerEvents="none">
<Image
<FastImage
source={{ uri: backdropUrl }}
style={StyleSheet.absoluteFill}
contentFit="cover"
recyclingKey={backdropUrl || undefined}
resizeMode={FastImage.resizeMode.cover}
/>
{Platform.OS === 'android' && AndroidBlurView
? <AndroidBlurView blurAmount={12} blurRadius={6} style={StyleSheet.absoluteFill} />

View file

@ -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 = () => {
<View style={styles.profileContainer}>
{avatarUrl && !avatarError ? (
<View style={[styles.avatar, { overflow: 'hidden' }]}>
<Image
<FastImage
source={{ uri: avatarUrl }}
style={styles.avatarImage}
contentFit="cover"
resizeMode={FastImage.resizeMode.cover}
onError={() => setAvatarError(true)}
/>
</View>

View file

@ -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 = () => {
<View style={styles.addonHeader}>
{logo ? (
<ExpoImage
<FastImage
source={{ uri: logo }}
style={styles.addonIcon}
contentFit="contain"
style={styles.addonIcon}
resizeMode={FastImage.resizeMode.contain}
/>
) : (
<View style={styles.addonIconPlaceholder}>
@ -1048,10 +1048,10 @@ const AddonsScreen = () => {
return (
<View style={styles.communityAddonItem}>
{logo ? (
<ExpoImage
<FastImage
source={{ uri: logo }}
style={styles.communityAddonIcon}
contentFit="contain"
resizeMode={FastImage.resizeMode.contain}
/>
) : (
<View style={styles.communityAddonIconPlaceholder}>
@ -1240,10 +1240,10 @@ const AddonsScreen = () => {
<View style={styles.addonItem}>
<View style={styles.addonHeader}>
{promoAddon.logo ? (
<ExpoImage
<FastImage
source={{ uri: promoAddon.logo }}
style={styles.addonIcon}
contentFit="contain"
style={styles.addonIcon}
resizeMode={FastImage.resizeMode.contain}
/>
) : (
<View style={styles.addonIconPlaceholder}>
@ -1318,10 +1318,10 @@ const AddonsScreen = () => {
<View style={styles.addonItem}>
<View style={styles.addonHeader}>
{item.manifest.logo ? (
<ExpoImage
<FastImage
source={{ uri: item.manifest.logo }}
style={styles.addonIcon}
contentFit="contain"
style={styles.addonIcon}
resizeMode={FastImage.resizeMode.contain}
/>
) : (
<View style={styles.addonIconPlaceholder}>
@ -1419,10 +1419,10 @@ const AddonsScreen = () => {
<View style={styles.addonDetailHeader}>
{/* @ts-ignore */}
{addonDetails.logo ? (
<ExpoImage
<FastImage
source={{ uri: addonDetails.logo }}
style={styles.addonLogo}
contentFit="contain"
resizeMode={FastImage.resizeMode.contain}
/>
) : (
<View style={styles.addonLogoPlaceholder}>

View file

@ -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}
>
<Image
source={{ uri: imageUrl }}
<FastImage
source={{ uri: imageUrl || '' }}
style={styles.poster}
contentFit="cover"
transition={300}
resizeMode={FastImage.resizeMode.cover}
/>
</TouchableOpacity>

View file

@ -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 ? (
<Image
<FastImage
source={{
uri: `https://image.tmdb.org/t/p/w500${item.poster_path}`,
}}
style={{ width: '100%', height: '100%' }}
contentFit="cover"
resizeMode={FastImage.resizeMode.cover}
/>
) : (
<View style={{
@ -589,12 +589,12 @@ const CastMoviesScreen: React.FC = () => {
backgroundColor: 'rgba(255, 255, 255, 0.08)',
}}>
{castMember?.profile_path ? (
<Image
<FastImage
source={{
uri: `https://image.tmdb.org/t/p/w185${castMember.profile_path}`,
}}
style={{ width: '100%', height: '100%' }}
contentFit="cover"
resizeMode={FastImage.resizeMode.cover}
/>
) : (
<View style={{

View file

@ -18,7 +18,7 @@ import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from '../navigation/AppNavigator';
import { Meta, stremioService } from '../services/stremioService';
import { useTheme } from '../contexts/ThemeContext';
import { Image } from 'expo-image';
import FastImage from '@d11/react-native-fast-image';
import { BlurView } from 'expo-blur';
import { MaterialIcons } from '@expo/vector-icons';
import { logger } from '../utils/logger';
@ -620,15 +620,10 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
onPress={() => navigation.navigate('Metadata', { id: item.id, type: item.type, addonId })}
activeOpacity={0.7}
>
<Image
<FastImage
source={{ uri: optimizePosterUrl(item.poster) }}
style={styles.poster}
contentFit="cover"
cachePolicy={Platform.OS === 'android' ? 'memory-disk' : 'memory-disk'}
transition={100}
allowDownscaling
priority="normal"
recyclingKey={`${item.id}-${item.type}`}
resizeMode={FastImage.resizeMode.cover}
/>
{type === 'movie' && nowPlayingMovies.has(item.id) && (

View file

@ -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);

View file

@ -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:
<View>
<View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}>
{posterUrl ? (
<Image
<FastImage
source={{ uri: posterUrl }}
style={styles.poster}
contentFit="cover"
cachePolicy="disk"
transition={0}
allowDownscaling
resizeMode={FastImage.resizeMode.cover}
/>
) : (
<View style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1, justifyContent: 'center', alignItems: 'center' }]}>
@ -403,12 +400,10 @@ const LibraryScreen = () => {
>
<View>
<View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}>
<Image
<FastImage
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
style={styles.poster}
contentFit="cover"
cachePolicy="memory"
transition={300}
resizeMode={FastImage.resizeMode.cover}
/>
{item.watched && (
<View style={styles.watchedIndicator}>

View file

@ -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 = () => {
<View key={scraper.id} style={styles.scraperCard}>
<View style={styles.scraperCardHeader}>
{scraper.logo ? (
<Image
<FastImage
source={{ uri: scraper.logo }}
style={styles.scraperLogo}
contentFit="contain"
resizeMode={FastImage.resizeMode.contain}
/>
) : (
<View style={styles.scraperLogo} />

View file

@ -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)'
}]}>
<Image
<FastImage
source={{ uri: item.poster || PLACEHOLDER_POSTER }}
style={styles.horizontalItemPoster}
contentFit="cover"
transition={300}
resizeMode={FastImage.resizeMode.cover}
/>
{/* Bookmark and watched icons top right, bookmark to the left of watched */}
{inLibrary && (

View file

@ -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}
>
<View style={styles.discordButtonContent}>
<Image
<FastImage
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
style={styles.discordLogo}
resizeMode="contain"
resizeMode={FastImage.resizeMode.contain}
/>
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
Join Discord
@ -821,10 +821,10 @@ const SettingsScreen: React.FC = () => {
onPress={() => Linking.openURL('https://ko-fi.com/tapframe')}
activeOpacity={0.7}
>
<Image
<FastImage
source={require('../../assets/support_me_on_kofi_red.png')}
style={styles.kofiImage}
resizeMode="contain"
resizeMode={FastImage.resizeMode.contain}
/>
</TouchableOpacity>
</View>
@ -892,10 +892,10 @@ const SettingsScreen: React.FC = () => {
activeOpacity={0.7}
>
<View style={styles.discordButtonContent}>
<Image
<FastImage
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
style={styles.discordLogo}
resizeMode="contain"
resizeMode={FastImage.resizeMode.contain}
/>
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
Join Discord
@ -908,10 +908,10 @@ const SettingsScreen: React.FC = () => {
onPress={() => Linking.openURL('https://ko-fi.com/tapframe')}
activeOpacity={0.7}
>
<Image
<FastImage
source={require('../../assets/support_me_on_kofi_red.png')}
style={styles.kofiImage}
resizeMode="contain"
resizeMode={FastImage.resizeMode.contain}
/>
</TouchableOpacity>
</View>

View file

@ -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 }) => (
<View style={styles.showInfo}>
<Image
<FastImage
source={{ uri: `https://image.tmdb.org/t/p/w500${show?.poster_path}` }}
style={styles.poster}
contentFit="cover"
transition={200}
resizeMode={FastImage.resizeMode.cover}
/>
<View style={styles.showDetails}>
<Text style={[styles.showTitle, { color: theme.colors.white }]}>{show?.name}</Text>

View file

@ -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 (
<Animated.View style={[style, animatedStyle]}>
<Image
<FastImage
source={source}
style={StyleSheet.absoluteFillObject}
contentFit={contentFit}
resizeMode={FastImage.resizeMode.cover}
onLoad={onLoad}
/>
</Animated.View>
@ -336,10 +336,10 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
{/* Scraper Logo */}
{showLogos && scraperLogo && (
<View style={styles.scraperLogoContainer}>
<Image
<FastImage
source={{ uri: scraperLogo }}
style={styles.scraperLogo}
resizeMode="contain"
resizeMode={FastImage.resizeMode.contain}
/>
</View>
)}
@ -1968,10 +1968,10 @@ export const StreamsScreen = () => {
<View style={[styles.movieTitleContainer]}>
<View style={styles.movieTitleContent}>
{metadata.logo && !movieLogoError ? (
<Image
<FastImage
source={{ uri: metadata.logo }}
style={styles.movieLogo}
contentFit="contain"
resizeMode={FastImage.resizeMode.contain}
onError={() => setMovieLogoError(true)}
/>
) : (
@ -2021,7 +2021,7 @@ export const StreamsScreen = () => {
</Text>
{effectiveEpisodeVote > 0 && (
<View style={styles.streamsHeroRating}>
<Image source={{ uri: TMDB_LOGO }} style={styles.tmdbLogo} contentFit="contain" />
<FastImage source={{ uri: TMDB_LOGO }} style={styles.tmdbLogo} resizeMode={FastImage.resizeMode.contain} />
<Text style={styles.streamsHeroRatingText}>
{effectiveEpisodeVote.toFixed(1)}
</Text>

View file

@ -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 (
<View style={styles.bannerContainer}>
<Image
<FastImage
source={{ uri: banner || undefined }}
style={styles.bannerImage}
resizeMode="cover"
resizeMode={FastImage.resizeMode.cover}
/>
<View style={styles.bannerOverlay} />
{logo && (
<Image
<FastImage
source={{ uri: logo }}
style={styles.logoOverBanner}
resizeMode="contain"
resizeMode={FastImage.resizeMode.contain}
/>
)}
{!logo && (

View file

@ -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 = () => {
<View style={styles.profileContainer}>
<View style={styles.profileHeader}>
{userProfile.avatar ? (
<Image
<FastImage
source={{ uri: userProfile.avatar }}
style={styles.avatar}
resizeMode={FastImage.resizeMode.cover}
/>
) : (
<View style={[styles.avatarPlaceholder, { backgroundColor: currentTheme.colors.primary }]}>