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 = { TargetAttributes = {
13B07F861A680F5B00A75B9A = { 13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1250; LastSwiftMigration = 1250;
ProvisioningStyle = Automatic;
}; };
}; };
}; };
@ -502,6 +501,7 @@
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio"; PRODUCT_NAME = "Nuvio";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";

View file

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

View file

@ -240,15 +240,9 @@ PODS:
- ExpoModulesCore - ExpoModulesCore
- ExpoHaptics (14.0.1): - ExpoHaptics (14.0.1):
- ExpoModulesCore - ExpoModulesCore
- ExpoImage (2.0.7):
- ExpoModulesCore
- libavif/libdav1d
- SDWebImage (~> 5.19.1)
- SDWebImageAVIFCoder (~> 0.11.0)
- SDWebImageSVGCoder (~> 1.7.0)
- ExpoKeepAwake (14.0.3): - ExpoKeepAwake (14.0.3):
- ExpoModulesCore - ExpoModulesCore
- ExpoLibVlcPlayer (2.1.7): - ExpoLibVlcPlayer (2.2.1):
- ExpoModulesCore - ExpoModulesCore
- MobileVLCKit (= 3.6.1b1) - MobileVLCKit (= 3.6.1b1)
- ExpoLinearGradient (14.0.2): - ExpoLinearGradient (14.0.2):
@ -2519,11 +2513,9 @@ PODS:
- SDWebImage (5.19.7): - SDWebImage (5.19.7):
- SDWebImage/Core (= 5.19.7) - SDWebImage/Core (= 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) - libavif/core (>= 0.11.0)
- SDWebImage (~> 5.10) - SDWebImage (~> 5.10)
- SDWebImageSVGCoder (1.7.0):
- SDWebImage/Core (~> 5.6)
- SDWebImageWebPCoder (0.14.6): - SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0) - libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17) - SDWebImage/Core (~> 5.17)
@ -2555,7 +2547,6 @@ DEPENDENCIES:
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
- ExpoFont (from `../node_modules/expo-font/ios`) - ExpoFont (from `../node_modules/expo-font/ios`)
- ExpoHaptics (from `../node_modules/expo-haptics/ios`) - ExpoHaptics (from `../node_modules/expo-haptics/ios`)
- ExpoImage (from `../node_modules/expo-image/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
- ExpoLibVlcPlayer (from `../node_modules/expo-libvlc-player/ios`) - ExpoLibVlcPlayer (from `../node_modules/expo-libvlc-player/ios`)
- ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`) - ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
@ -2666,7 +2657,6 @@ SPEC REPOS:
- ReachabilitySwift - ReachabilitySwift
- SDWebImage - SDWebImage
- SDWebImageAVIFCoder - SDWebImageAVIFCoder
- SDWebImageSVGCoder
- SDWebImageWebPCoder - SDWebImageWebPCoder
- Sentry - Sentry
- SocketRocket - SocketRocket
@ -2719,8 +2709,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-font/ios" :path: "../node_modules/expo-font/ios"
ExpoHaptics: ExpoHaptics:
:path: "../node_modules/expo-haptics/ios" :path: "../node_modules/expo-haptics/ios"
ExpoImage:
:path: "../node_modules/expo-image/ios"
ExpoKeepAwake: ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios" :path: "../node_modules/expo-keep-awake/ios"
ExpoLibVlcPlayer: ExpoLibVlcPlayer:
@ -2958,9 +2946,8 @@ SPEC CHECKSUMS:
ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655 ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655
ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188 ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188
ExpoHaptics: 8d199b2f33245ea85289ff6c954c7ee7c00a5b5d ExpoHaptics: 8d199b2f33245ea85289ff6c954c7ee7c00a5b5d
ExpoImage: d840b256050f4428d2942bc2b6e9251f9e0d7021
ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680 ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680
ExpoLibVlcPlayer: 027c16c178364a133f6ee10fc7a7d8636f92dbe8 ExpoLibVlcPlayer: dce3d0b5847838cd5f8c5f3c3aa1bc55c92e911d
ExpoLinearGradient: 35ebd83b16f80b3add053a2fd68cc328ed927f60 ExpoLinearGradient: 35ebd83b16f80b3add053a2fd68cc328ed927f60
ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402 ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402
ExpoLocalization: 7776ea3bdb112125390745bbaf919b734b2ad1c7 ExpoLocalization: 7776ea3bdb112125390745bbaf919b734b2ad1c7
@ -3062,8 +3049,7 @@ SPEC CHECKSUMS:
RNSVG: b889dc9c1948eeea0576a16cc405c91c37a12c19 RNSVG: b889dc9c1948eeea0576a16cc405c91c37a12c19
RNVectorIcons: c95fdae217b0ed388f2b4d7ed7a4edc457c1df47 RNVectorIcons: c95fdae217b0ed388f2b4d7ed7a4edc457c1df47
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
SDWebImageAVIFCoder: 00310d246aab3232ce77f1d8f0076f8c4b021d90 SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: 1ca8405451040482877dcd344dfa3ef80b646631 Sentry: 1ca8405451040482877dcd344dfa3ef80b646631
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748

20
package-lock.json generated
View file

@ -47,9 +47,8 @@
"expo-document-picker": "~13.0.3", "expo-document-picker": "~13.0.3",
"expo-file-system": "~18.0.12", "expo-file-system": "~18.0.12",
"expo-haptics": "~14.0.1", "expo-haptics": "~14.0.1",
"expo-image": "~2.0.7",
"expo-intent-launcher": "~12.0.2", "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-linear-gradient": "~14.0.2",
"expo-localization": "~16.0.1", "expo-localization": "~16.0.1",
"expo-notifications": "~0.29.14", "expo-notifications": "~0.29.14",
@ -7986,23 +7985,6 @@
"expo": "*" "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": { "node_modules/expo-intent-launcher": {
"version": "12.0.2", "version": "12.0.2",
"resolved": "https://registry.npmjs.org/expo-intent-launcher/-/expo-intent-launcher-12.0.2.tgz", "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-document-picker": "~13.0.3",
"expo-file-system": "~18.0.12", "expo-file-system": "~18.0.12",
"expo-haptics": "~14.0.1", "expo-haptics": "~14.0.1",
"expo-image": "~2.0.7",
"expo-intent-launcher": "~12.0.2", "expo-intent-launcher": "~12.0.2",
"expo-libvlc-player": "^2.1.7", "expo-libvlc-player": "^2.1.7",
"expo-linear-gradient": "~14.0.2", "expo-linear-gradient": "~14.0.2",

View file

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

View file

@ -232,10 +232,9 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
}, 1500); }, 1500);
}); });
await Promise.race([ // FastImage.preload doesn't return a promise, so we just call it and use timeout
FastImage.preload([{ uri: url }]), FastImage.preload([{ uri: url }]);
timeout, await timeout;
]);
imageCache[url] = true; imageCache[url] = true;
logger.debug('[FeaturedContent] preloadImage:success', { url, duration: since(t0) }); logger.debug('[FeaturedContent] preloadImage:success', { url, duration: since(t0) });
return true; return true;
@ -569,7 +568,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
priority: FastImage.priority.high, priority: FastImage.priority.high,
cache: FastImage.cacheControl.immutable cache: FastImage.cacheControl.immutable
}} }}
style={styles.tabletLogo as ImageStyle} style={styles.tabletLogo as any}
resizeMode={FastImage.resizeMode.contain} resizeMode={FastImage.resizeMode.contain}
onError={onLogoLoadError} onError={onLogoLoadError}
/> />
@ -703,7 +702,7 @@ const FeaturedContent = ({ featuredContent, isSaved, handleSaveToLibrary, loadin
priority: FastImage.priority.high, priority: FastImage.priority.high,
cache: FastImage.cacheControl.immutable cache: FastImage.cacheControl.immutable
}} }}
style={styles.featuredLogo as ImageStyle} style={styles.featuredLogo as any}
resizeMode={FastImage.resizeMode.contain} resizeMode={FastImage.resizeMode.contain}
onError={onLogoLoadError} onError={onLogoLoadError}
/> />

View file

@ -166,9 +166,8 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
priority: FastImage.priority.low, priority: FastImage.priority.low,
cache: FastImage.cacheControl.immutable cache: FastImage.cacheControl.immutable
}} }}
style={styles.backgroundImage as ImageStyle} style={styles.backgroundImage as any}
resizeMode={FastImage.resizeMode.cover} resizeMode={FastImage.resizeMode.cover}
blurRadius={Platform.OS === 'android' ? 8 : 12}
/> />
<LinearGradient <LinearGradient
colors={["rgba(0,0,0,0.45)", "rgba(0,0,0,0.75)"]} 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, priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable cache: FastImage.cacheControl.immutable
}} }}
style={styles.banner as ImageStyle} style={styles.banner as any}
resizeMode={FastImage.resizeMode.cover} resizeMode={FastImage.resizeMode.cover}
/> />
<LinearGradient <LinearGradient
@ -308,7 +307,7 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
priority: FastImage.priority.high, priority: FastImage.priority.high,
cache: FastImage.cacheControl.immutable cache: FastImage.cacheControl.immutable
}} }}
style={styles.logo as ImageStyle} style={styles.logo as any}
resizeMode={FastImage.resizeMode.contain} resizeMode={FastImage.resizeMode.contain}
onError={onLogoError} onError={onLogoError}
/> />

View file

@ -10,7 +10,7 @@ import {
} from 'react-native'; } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { BlurView } from 'expo-blur'; import { BlurView } from 'expo-blur';
import { Image } from 'expo-image'; import FastImage from '@d11/react-native-fast-image';
import Animated, { import Animated, {
FadeIn, FadeIn,
FadeOut, FadeOut,
@ -228,12 +228,12 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
backgroundColor: 'rgba(255, 255, 255, 0.08)', backgroundColor: 'rgba(255, 255, 255, 0.08)',
}}> }}>
{castMember?.profile_path ? ( {castMember?.profile_path ? (
<Image <FastImage
source={{ source={{
uri: `https://image.tmdb.org/t/p/w185${castMember.profile_path}`, uri: `https://image.tmdb.org/t/p/w185${castMember.profile_path}`,
}} }}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
/> />
) : ( ) : (
<View style={{ <View style={{

View file

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

View file

@ -9,7 +9,7 @@ import {
} from 'react-native'; } from 'react-native';
import { BlurView as ExpoBlurView } from 'expo-blur'; import { BlurView as ExpoBlurView } from 'expo-blur';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { Image } from 'expo-image'; import FastImage from '@d11/react-native-fast-image';
import Animated, { import Animated, {
useAnimatedStyle, useAnimatedStyle,
interpolate, interpolate,
@ -96,11 +96,10 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
<View style={styles.headerTitleContainer}> <View style={styles.headerTitleContainer}>
{metadata.logo && !logoLoadError ? ( {metadata.logo && !logoLoadError ? (
<Image <FastImage
source={{ uri: metadata.logo }} source={{ uri: metadata.logo }}
style={styles.floatingHeaderLogo} style={styles.floatingHeaderLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
transition={150}
onError={() => { onError={() => {
logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`); logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`);
setLogoLoadError(true); setLogoLoadError(true);
@ -146,11 +145,10 @@ const FloatingHeader: React.FC<FloatingHeaderProps> = ({
<View style={styles.headerTitleContainer}> <View style={styles.headerTitleContainer}>
{metadata.logo && !logoLoadError ? ( {metadata.logo && !logoLoadError ? (
<Image <FastImage
source={{ uri: metadata.logo }} source={{ uri: metadata.logo }}
style={styles.floatingHeaderLogo} style={styles.floatingHeaderLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
transition={150}
onError={() => { onError={() => {
logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`); logger.warn(`[FloatingHeader] Logo failed to load: ${metadata.logo}`);
setLogoLoadError(true); setLogoLoadError(true);

View file

@ -13,7 +13,7 @@ import { useFocusEffect, useIsFocused } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient'; 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 ExpoBlurView } from 'expo-blur';
import { BlurView as CommunityBlurView } from '@react-native-community/blur'; import { BlurView as CommunityBlurView } from '@react-native-community/blur';
import Constants, { ExecutionEnvironment } from 'expo-constants'; 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.logoContainer, titleCardAnimatedStyle]}>
<Animated.View style={[styles.titleLogoContainer, logoAnimatedStyle]}> <Animated.View style={[styles.titleLogoContainer, logoAnimatedStyle]}>
{shouldLoadSecondaryData && metadata.logo && !logoLoadError ? ( {shouldLoadSecondaryData && metadata.logo && !logoLoadError ? (
<Image <FastImage
source={{ uri: metadata.logo }} source={{ uri: metadata.logo }}
style={isTablet ? styles.tabletTitleLogo : styles.titleLogo} style={isTablet ? styles.tabletTitleLogo : styles.titleLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
transition={150}
onError={() => { onError={() => {
runOnJS(setLogoLoadError)(true); runOnJS(setLogoLoadError)(true);
}} }}

View file

@ -7,7 +7,7 @@ import {
ActivityIndicator, ActivityIndicator,
} from 'react-native'; } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { Image } from 'expo-image'; import FastImage from '@d11/react-native-fast-image';
import Animated, { import Animated, {
FadeIn, FadeIn,
useAnimatedStyle, useAnimatedStyle,
@ -162,10 +162,10 @@ function formatRuntime(runtime: string): string {
)} )}
{metadata.imdbRating && !isMDBEnabled && ( {metadata.imdbRating && !isMDBEnabled && (
<View style={styles.ratingContainer}> <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' }} source={{ uri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/IMDB_Logo_2016.svg/575px-IMDB_Logo_2016.svg.png' }}
style={styles.imdbLogo} style={styles.imdbLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
<Text style={[styles.ratingText, { color: currentTheme.colors.text }]}>{metadata.imdbRating}</Text> <Text style={[styles.ratingText, { color: currentTheme.colors.text }]}>{metadata.imdbRating}</Text>
</View> </View>

View file

@ -8,7 +8,7 @@ import {
ActivityIndicator, ActivityIndicator,
Dimensions, Dimensions,
} from 'react-native'; } from 'react-native';
import { Image } from 'expo-image'; import FastImage from '@d11/react-native-fast-image';
import { useNavigation, StackActions } from '@react-navigation/native'; import { useNavigation, StackActions } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native';
import { RootStackParamList } from '../../navigation/AppNavigator'; import { RootStackParamList } from '../../navigation/AppNavigator';
@ -97,11 +97,10 @@ export const MoreLikeThisSection: React.FC<MoreLikeThisSectionProps> = ({
style={styles.itemContainer} style={styles.itemContainer}
onPress={() => handleItemPress(item)} onPress={() => handleItemPress(item)}
> >
<Image <FastImage
source={{ uri: item.poster }} source={{ uri: item.poster }}
style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1 }]} style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1 }]}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
transition={200}
/> />
<Text style={[styles.title, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={2}> <Text style={[styles.title, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={2}>
{item.name} {item.name}

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList } from 'react-native'; 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 { MaterialIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { FlashList, FlashListRef } from '@shopify/flash-list'; import { FlashList, FlashListRef } from '@shopify/flash-list';
@ -454,10 +454,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
onPress={() => onSeasonChange(season)} onPress={() => onSeasonChange(season)}
> >
<View style={[styles.seasonPosterContainer, isTablet && styles.seasonPosterContainerTablet]}> <View style={[styles.seasonPosterContainer, isTablet && styles.seasonPosterContainerTablet]}>
<Image <FastImage
source={{ uri: seasonPoster }} source={{ uri: seasonPoster }}
style={styles.seasonPoster} style={styles.seasonPoster}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
/> />
{selectedSeason === season && ( {selectedSeason === season && (
<View style={[ <View style={[
@ -559,10 +559,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
styles.episodeImageContainer, styles.episodeImageContainer,
isTablet && styles.episodeImageContainerTablet isTablet && styles.episodeImageContainerTablet
]}> ]}>
<Image <FastImage
source={{ uri: episodeImage }} source={{ uri: episodeImage }}
style={styles.episodeImage} style={styles.episodeImage}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
/> />
<View style={styles.episodeNumberBadge}> <View style={styles.episodeNumberBadge}>
<Text style={styles.episodeNumberText}>{episodeString}</Text> <Text style={styles.episodeNumberText}>{episodeString}</Text>
@ -605,10 +605,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
]}> ]}>
{effectiveVote > 0 && ( {effectiveVote > 0 && (
<View style={styles.ratingContainer}> <View style={styles.ratingContainer}>
<Image <FastImage
source={{ uri: TMDB_LOGO }} source={{ uri: TMDB_LOGO }}
style={styles.tmdbLogo} style={styles.tmdbLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
<Text style={[styles.ratingText, { color: currentTheme.colors.textMuted }]}> <Text style={[styles.ratingText, { color: currentTheme.colors.textMuted }]}>
{effectiveVote.toFixed(1)} {effectiveVote.toFixed(1)}
@ -727,10 +727,10 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
</View> </View>
{/* Background Image */} {/* Background Image */}
<Image <FastImage
source={{ uri: episodeImage }} source={{ uri: episodeImage }}
style={styles.episodeBackgroundImage} style={styles.episodeBackgroundImage}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
/> />
{/* Standard Gradient Overlay */} {/* Standard Gradient Overlay */}

View file

@ -1,7 +1,8 @@
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; 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 { useSafeAreaInsets } from 'react-native-safe-area-context';
import Video, { VideoRef, SelectedTrack, SelectedTrackType, BufferingStrategyType, ViewType } from 'react-native-video'; 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 { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native';
import { RootStackParamList } from '../../navigation/AppNavigator'; import { RootStackParamList } from '../../navigation/AppNavigator';
import { PinchGestureHandler, PanGestureHandler, TapGestureHandler, State, PinchGestureHandlerGestureEvent, PanGestureHandlerGestureEvent, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'; import { PinchGestureHandler, PanGestureHandler, TapGestureHandler, State, PinchGestureHandlerGestureEvent, PanGestureHandlerGestureEvent, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler';
@ -586,22 +587,21 @@ const AndroidVideoPlayer: React.FC = () => {
backdropImageOpacityAnim.setValue(0); backdropImageOpacityAnim.setValue(0);
// Prefetch the image // Prefetch the image
Image.prefetch(backdrop) try {
.then(() => { FastImage.preload([{ uri: backdrop }]);
// Image loaded successfully, fade it in smoothly // Image prefetch initiated, fade it in smoothly
setIsBackdropLoaded(true); setIsBackdropLoaded(true);
Animated.timing(backdropImageOpacityAnim, { Animated.timing(backdropImageOpacityAnim, {
toValue: 1, toValue: 1,
duration: 400, duration: 400,
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
}) } catch (error) {
.catch((error) => { // If prefetch fails, still show the image but without animation
// If prefetch fails, still show the image but without animation if (__DEV__) logger.warn('[AndroidVideoPlayer] Backdrop prefetch failed, showing anyway:', error);
if (__DEV__) logger.warn('[AndroidVideoPlayer] Backdrop prefetch failed, showing anyway:', error); setIsBackdropLoaded(true);
setIsBackdropLoaded(true); backdropImageOpacityAnim.setValue(1);
backdropImageOpacityAnim.setValue(1); }
});
} else { } else {
// No backdrop provided, consider it "loaded" // No backdrop provided, consider it "loaded"
setIsBackdropLoaded(true); setIsBackdropLoaded(true);
@ -612,7 +612,11 @@ const AndroidVideoPlayer: React.FC = () => {
useEffect(() => { useEffect(() => {
const logoUrl = (metadata && (metadata as any).logo) as string | undefined; const logoUrl = (metadata && (metadata as any).logo) as string | undefined;
if (logoUrl && typeof logoUrl === 'string') { if (logoUrl && typeof logoUrl === 'string') {
Image.prefetch(logoUrl).catch(() => {}); try {
FastImage.preload([{ uri: logoUrl }]);
} catch (error) {
// Silently ignore logo prefetch errors
}
} }
}, [metadata]); }, [metadata]);
@ -3112,19 +3116,20 @@ const AndroidVideoPlayer: React.FC = () => {
pointerEvents={isOpeningAnimationComplete ? 'none' : 'auto'} pointerEvents={isOpeningAnimationComplete ? 'none' : 'auto'}
> >
{backdrop && ( {backdrop && (
<Animated.Image <Animated.View style={[
source={{ uri: backdrop }}
style={[
StyleSheet.absoluteFill, StyleSheet.absoluteFill,
{ {
width: screenDimensions.width, width: screenDimensions.width,
height: screenDimensions.height, height: screenDimensions.height,
opacity: backdropImageOpacityAnim opacity: backdropImageOpacityAnim
} }
]} ]}>
resizeMode="cover" <FastImage
blurRadius={0} source={{ uri: backdrop }}
/> style={StyleSheet.absoluteFillObject}
resizeMode={FastImage.resizeMode.cover}
/>
</Animated.View>
)} )}
<LinearGradient <LinearGradient
colors={[ colors={[
@ -3155,13 +3160,13 @@ const AndroidVideoPlayer: React.FC = () => {
opacity: logoOpacityAnim, opacity: logoOpacityAnim,
alignItems: 'center', alignItems: 'center',
}}> }}>
<Image <FastImage
source={{ uri: metadata.logo }} source={{ uri: metadata.logo }}
style={{ style={{
width: 300, width: 300,
height: 180, height: 180,
resizeMode: 'contain',
}} }}
resizeMode={FastImage.resizeMode.contain}
/> />
</Animated.View> </Animated.View>
</> </>
@ -3599,7 +3604,7 @@ const AndroidVideoPlayer: React.FC = () => {
shadowRadius: 8, shadowRadius: 8,
elevation: 5, elevation: 5,
}}> }}>
<Image <FastImage
source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }} source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }}
style={{ style={{
width: Math.min(120, screenDimensions.width * 0.18), width: Math.min(120, screenDimensions.width * 0.18),
@ -3607,7 +3612,7 @@ const AndroidVideoPlayer: React.FC = () => {
borderRadius: 12, borderRadius: 12,
backgroundColor: 'rgba(255,255,255,0.1)' backgroundColor: 'rgba(255,255,255,0.1)'
}} }}
resizeMode="cover" resizeMode={FastImage.resizeMode.cover}
/> />
</View> </View>
)} )}

View file

@ -1,7 +1,8 @@
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; 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 { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native'; import { useNavigation, useRoute, RouteProp, useFocusEffect } from '@react-navigation/native';
import FastImage from '@d11/react-native-fast-image';
import { RootStackParamList, RootStackNavigationProp } from '../../navigation/AppNavigator'; import { RootStackParamList, RootStackNavigationProp } from '../../navigation/AppNavigator';
import { PinchGestureHandler, PanGestureHandler, TapGestureHandler, State, PinchGestureHandlerGestureEvent, PanGestureHandlerGestureEvent, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'; import { PinchGestureHandler, PanGestureHandler, TapGestureHandler, State, PinchGestureHandlerGestureEvent, PanGestureHandlerGestureEvent, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler';
import RNImmersiveMode from 'react-native-immersive-mode'; import RNImmersiveMode from 'react-native-immersive-mode';
@ -317,22 +318,21 @@ const KSPlayerCore: React.FC = () => {
backdropImageOpacityAnim.setValue(0); backdropImageOpacityAnim.setValue(0);
// Prefetch the image // Prefetch the image
Image.prefetch(backdrop) try {
.then(() => { FastImage.preload([{ uri: backdrop }]);
// Image loaded successfully, fade it in smoothly // Image prefetch initiated, fade it in smoothly
setIsBackdropLoaded(true); setIsBackdropLoaded(true);
Animated.timing(backdropImageOpacityAnim, { Animated.timing(backdropImageOpacityAnim, {
toValue: 1, toValue: 1,
duration: 400, duration: 400,
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
}) } catch (error) {
.catch((error) => { // If prefetch fails, still show the image but without animation
// If prefetch fails, still show the image but without animation if (__DEV__) logger.warn('[VideoPlayer] Backdrop prefetch failed, showing anyway:', error);
if (__DEV__) logger.warn('[VideoPlayer] Backdrop prefetch failed, showing anyway:', error); setIsBackdropLoaded(true);
setIsBackdropLoaded(true); backdropImageOpacityAnim.setValue(1);
backdropImageOpacityAnim.setValue(1); }
});
} else { } else {
// No backdrop provided, consider it "loaded" // No backdrop provided, consider it "loaded"
setIsBackdropLoaded(true); setIsBackdropLoaded(true);
@ -343,7 +343,11 @@ const KSPlayerCore: React.FC = () => {
useEffect(() => { useEffect(() => {
const logoUrl = (metadata && (metadata as any).logo) as string | undefined; const logoUrl = (metadata && (metadata as any).logo) as string | undefined;
if (logoUrl && typeof logoUrl === 'string') { if (logoUrl && typeof logoUrl === 'string') {
Image.prefetch(logoUrl).catch(() => {}); try {
FastImage.preload([{ uri: logoUrl }]);
} catch (error) {
// Silently ignore logo prefetch errors
}
} }
}, [metadata]); }, [metadata]);
// Resolve current episode description for series // Resolve current episode description for series
@ -2431,19 +2435,20 @@ const KSPlayerCore: React.FC = () => {
pointerEvents={shouldHideOpeningOverlay ? 'none' : 'auto'} pointerEvents={shouldHideOpeningOverlay ? 'none' : 'auto'}
> >
{backdrop && ( {backdrop && (
<Animated.Image <Animated.View style={[
source={{ uri: backdrop }}
style={[
StyleSheet.absoluteFill, StyleSheet.absoluteFill,
{ {
width: screenDimensions.width, width: screenDimensions.width,
height: screenDimensions.height, height: screenDimensions.height,
opacity: backdropImageOpacityAnim opacity: backdropImageOpacityAnim
} }
]} ]}>
resizeMode="cover" <FastImage
blurRadius={0} source={{ uri: backdrop }}
/> style={StyleSheet.absoluteFillObject}
resizeMode={FastImage.resizeMode.cover}
/>
</Animated.View>
)} )}
<LinearGradient <LinearGradient
colors={[ colors={[
@ -2474,13 +2479,13 @@ const KSPlayerCore: React.FC = () => {
opacity: logoOpacityAnim, opacity: logoOpacityAnim,
alignItems: 'center', alignItems: 'center',
}}> }}>
<Image <FastImage
source={{ uri: metadata.logo }} source={{ uri: metadata.logo }}
style={{ style={{
width: 300, width: 300,
height: 180, height: 180,
resizeMode: 'contain',
}} }}
resizeMode={FastImage.resizeMode.contain}
/> />
</Animated.View> </Animated.View>
</> </>
@ -2817,7 +2822,7 @@ const KSPlayerCore: React.FC = () => {
shadowRadius: 8, shadowRadius: 8,
elevation: 5, elevation: 5,
}}> }}>
<Image <FastImage
source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }} source={{ uri: `https://image.tmdb.org/t/p/w300${selectedCastMember.profile_path}` }}
style={{ style={{
width: Math.min(120, screenDimensions.width * 0.18), width: Math.min(120, screenDimensions.width * 0.18),
@ -2825,7 +2830,7 @@ const KSPlayerCore: React.FC = () => {
borderRadius: 12, borderRadius: 12,
backgroundColor: 'rgba(255,255,255,0.1)' backgroundColor: 'rgba(255,255,255,0.1)'
}} }}
resizeMode="cover" resizeMode={FastImage.resizeMode.cover}
/> />
</View> </View>
)} )}

View file

@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react';
import { logger } from '../utils/logger'; import { logger } from '../utils/logger';
import { TMDBService } from '../services/tmdbService'; import { TMDBService } from '../services/tmdbService';
import { isTmdbUrl } from '../utils/logoUtils'; 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'; import AsyncStorage from '@react-native-async-storage/async-storage';
// Cache for image availability checks // Cache for image availability checks
@ -176,7 +176,7 @@ export const useMetadataAssets = (
if (logoUrl) { if (logoUrl) {
// Preload the image // Preload the image
await Image.prefetch(logoUrl); FastImage.preload([{ uri: logoUrl }]);
setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: logoUrl })); setMetadata((prevMetadata: any) => ({ ...prevMetadata!, logo: logoUrl }));
} }
@ -275,7 +275,7 @@ export const useMetadataAssets = (
// Preload the image // Preload the image
if (finalBanner) { if (finalBanner) {
await Image.prefetch(finalBanner); FastImage.preload([{ uri: finalBanner }]);
} }
} }
else if (details?.poster_path) { else if (details?.poster_path) {
@ -284,7 +284,7 @@ export const useMetadataAssets = (
// Preload the image // Preload the image
if (finalBanner) { if (finalBanner) {
await Image.prefetch(finalBanner); FastImage.preload([{ uri: finalBanner }]);
} }
} }
} catch (error) { } catch (error) {

View file

@ -18,7 +18,7 @@ import CustomAlert from '../components/CustomAlert';
import { useRoute, useNavigation, RouteProp, useFocusEffect } from '@react-navigation/native'; import { useRoute, useNavigation, RouteProp, useFocusEffect } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { useTheme } from '../contexts/ThemeContext'; 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'; import { BlurView as ExpoBlurView } from 'expo-blur';
// Lazy-safe community blur import (avoid bundling issues on web) // Lazy-safe community blur import (avoid bundling issues on web)
let AndroidBlurView: any = null; let AndroidBlurView: any = null;
@ -642,11 +642,10 @@ const AIChatScreen: React.FC = () => {
<SafeAreaView edges={['top','bottom']} style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}> <SafeAreaView edges={['top','bottom']} style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
{backdropUrl && ( {backdropUrl && (
<View style={StyleSheet.absoluteFill} pointerEvents="none"> <View style={StyleSheet.absoluteFill} pointerEvents="none">
<Image <FastImage
source={{ uri: backdropUrl }} source={{ uri: backdropUrl }}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
recyclingKey={backdropUrl || undefined}
/> />
{Platform.OS === 'android' && AndroidBlurView {Platform.OS === 'android' && AndroidBlurView
? <AndroidBlurView blurAmount={12} blurRadius={6} style={StyleSheet.absoluteFill} /> ? <AndroidBlurView blurAmount={12} blurRadius={6} style={StyleSheet.absoluteFill} />

View file

@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, StatusBar, Platform, Animated, Easing, TextInput, ActivityIndicator } from 'react-native'; 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 { useNavigation } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
@ -110,10 +110,10 @@ const AccountManageScreen: React.FC = () => {
<View style={styles.profileContainer}> <View style={styles.profileContainer}>
{avatarUrl && !avatarError ? ( {avatarUrl && !avatarError ? (
<View style={[styles.avatar, { overflow: 'hidden' }]}> <View style={[styles.avatar, { overflow: 'hidden' }]}>
<Image <FastImage
source={{ uri: avatarUrl }} source={{ uri: avatarUrl }}
style={styles.avatarImage} style={styles.avatarImage}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
onError={() => setAvatarError(true)} onError={() => setAvatarError(true)}
/> />
</View> </View>

View file

@ -21,7 +21,7 @@ import {
} from 'react-native'; } from 'react-native';
import { stremioService, Manifest } from '../services/stremioService'; import { stremioService, Manifest } from '../services/stremioService';
import { MaterialIcons } from '@expo/vector-icons'; 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 { LinearGradient } from 'expo-linear-gradient';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native';
@ -972,10 +972,10 @@ const AddonsScreen = () => {
<View style={styles.addonHeader}> <View style={styles.addonHeader}>
{logo ? ( {logo ? (
<ExpoImage <FastImage
source={{ uri: logo }} source={{ uri: logo }}
style={styles.addonIcon} style={styles.addonIcon}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
) : ( ) : (
<View style={styles.addonIconPlaceholder}> <View style={styles.addonIconPlaceholder}>
@ -1048,10 +1048,10 @@ const AddonsScreen = () => {
return ( return (
<View style={styles.communityAddonItem}> <View style={styles.communityAddonItem}>
{logo ? ( {logo ? (
<ExpoImage <FastImage
source={{ uri: logo }} source={{ uri: logo }}
style={styles.communityAddonIcon} style={styles.communityAddonIcon}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
) : ( ) : (
<View style={styles.communityAddonIconPlaceholder}> <View style={styles.communityAddonIconPlaceholder}>
@ -1240,10 +1240,10 @@ const AddonsScreen = () => {
<View style={styles.addonItem}> <View style={styles.addonItem}>
<View style={styles.addonHeader}> <View style={styles.addonHeader}>
{promoAddon.logo ? ( {promoAddon.logo ? (
<ExpoImage <FastImage
source={{ uri: promoAddon.logo }} source={{ uri: promoAddon.logo }}
style={styles.addonIcon} style={styles.addonIcon}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
) : ( ) : (
<View style={styles.addonIconPlaceholder}> <View style={styles.addonIconPlaceholder}>
@ -1318,10 +1318,10 @@ const AddonsScreen = () => {
<View style={styles.addonItem}> <View style={styles.addonItem}>
<View style={styles.addonHeader}> <View style={styles.addonHeader}>
{item.manifest.logo ? ( {item.manifest.logo ? (
<ExpoImage <FastImage
source={{ uri: item.manifest.logo }} source={{ uri: item.manifest.logo }}
style={styles.addonIcon} style={styles.addonIcon}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
) : ( ) : (
<View style={styles.addonIconPlaceholder}> <View style={styles.addonIconPlaceholder}>
@ -1419,10 +1419,10 @@ const AddonsScreen = () => {
<View style={styles.addonDetailHeader}> <View style={styles.addonDetailHeader}>
{/* @ts-ignore */} {/* @ts-ignore */}
{addonDetails.logo ? ( {addonDetails.logo ? (
<ExpoImage <FastImage
source={{ uri: addonDetails.logo }} source={{ uri: addonDetails.logo }}
style={styles.addonLogo} style={styles.addonLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
) : ( ) : (
<View style={styles.addonLogoPlaceholder}> <View style={styles.addonLogoPlaceholder}>

View file

@ -16,7 +16,7 @@ import {
import { InteractionManager } from 'react-native'; import { InteractionManager } from 'react-native';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NavigationProp } 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 { MaterialIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { useTheme } from '../contexts/ThemeContext'; import { useTheme } from '../contexts/ThemeContext';
@ -138,11 +138,10 @@ const CalendarScreen = () => {
onPress={() => handleSeriesPress(item.seriesId, item)} onPress={() => handleSeriesPress(item.seriesId, item)}
activeOpacity={0.7} activeOpacity={0.7}
> >
<Image <FastImage
source={{ uri: imageUrl }} source={{ uri: imageUrl || '' }}
style={styles.poster} style={styles.poster}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
transition={300}
/> />
</TouchableOpacity> </TouchableOpacity>

View file

@ -10,7 +10,7 @@ import {
FlatList, FlatList,
} from 'react-native'; } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { Image } from 'expo-image'; import FastImage from '@d11/react-native-fast-image';
import Animated, { import Animated, {
FadeIn, FadeIn,
FadeOut, FadeOut,
@ -379,12 +379,12 @@ const CastMoviesScreen: React.FC = () => {
backgroundColor: 'rgba(255, 255, 255, 0.05)', backgroundColor: 'rgba(255, 255, 255, 0.05)',
}}> }}>
{item.poster_path ? ( {item.poster_path ? (
<Image <FastImage
source={{ source={{
uri: `https://image.tmdb.org/t/p/w500${item.poster_path}`, uri: `https://image.tmdb.org/t/p/w500${item.poster_path}`,
}} }}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
/> />
) : ( ) : (
<View style={{ <View style={{
@ -589,12 +589,12 @@ const CastMoviesScreen: React.FC = () => {
backgroundColor: 'rgba(255, 255, 255, 0.08)', backgroundColor: 'rgba(255, 255, 255, 0.08)',
}}> }}>
{castMember?.profile_path ? ( {castMember?.profile_path ? (
<Image <FastImage
source={{ source={{
uri: `https://image.tmdb.org/t/p/w185${castMember.profile_path}`, uri: `https://image.tmdb.org/t/p/w185${castMember.profile_path}`,
}} }}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
/> />
) : ( ) : (
<View style={{ <View style={{

View file

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

View file

@ -463,7 +463,7 @@ const HomeScreen = () => {
})); }));
// Preload all images at once - FastImage handles batching internally // Preload all images at once - FastImage handles batching internally
await FastImage.preload(sources); FastImage.preload(sources);
} catch (error) { } catch (error) {
// Silently handle preload errors // Silently handle preload errors
if (__DEV__) console.warn('Image preload error:', error); 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 { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons'; 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 Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { catalogService } from '../services/catalogService'; import { catalogService } from '../services/catalogService';
@ -119,13 +119,10 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item:
<View> <View>
<View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}> <View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}>
{posterUrl ? ( {posterUrl ? (
<Image <FastImage
source={{ uri: posterUrl }} source={{ uri: posterUrl }}
style={styles.poster} style={styles.poster}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
cachePolicy="disk"
transition={0}
allowDownscaling
/> />
) : ( ) : (
<View style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1, justifyContent: 'center', alignItems: 'center' }]}> <View style={[styles.poster, { backgroundColor: currentTheme.colors.elevation1, justifyContent: 'center', alignItems: 'center' }]}>
@ -403,12 +400,10 @@ const LibraryScreen = () => {
> >
<View> <View>
<View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}> <View style={[styles.posterContainer, { shadowColor: currentTheme.colors.black }]}>
<Image <FastImage
source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }} source={{ uri: item.poster || 'https://via.placeholder.com/300x450' }}
style={styles.poster} style={styles.poster}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
cachePolicy="memory"
transition={300}
/> />
{item.watched && ( {item.watched && (
<View style={styles.watchedIndicator}> <View style={styles.watchedIndicator}>

View file

@ -16,7 +16,7 @@ import {
Animated, Animated,
} from 'react-native'; } from 'react-native';
import CustomAlert from '../components/CustomAlert'; 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 { SafeAreaView } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
@ -1598,10 +1598,10 @@ const PluginsScreen: React.FC = () => {
<View key={scraper.id} style={styles.scraperCard}> <View key={scraper.id} style={styles.scraperCard}>
<View style={styles.scraperCardHeader}> <View style={styles.scraperCardHeader}>
{scraper.logo ? ( {scraper.logo ? (
<Image <FastImage
source={{ uri: scraper.logo }} source={{ uri: scraper.logo }}
style={styles.scraperLogo} style={styles.scraperLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
/> />
) : ( ) : (
<View style={styles.scraperLogo} /> <View style={styles.scraperLogo} />

View file

@ -22,7 +22,7 @@ import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { catalogService, StreamingContent, GroupedSearchResults, AddonSearchResults } from '../services/catalogService'; 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 debounce from 'lodash/debounce';
import { DropUpMenu } from '../components/home/DropUpMenu'; import { DropUpMenu } from '../components/home/DropUpMenu';
import { DeviceEventEmitter, Share } from 'react-native'; import { DeviceEventEmitter, Share } from 'react-native';
@ -571,11 +571,10 @@ const SearchScreen = () => {
backgroundColor: currentTheme.colors.darkBackground, backgroundColor: currentTheme.colors.darkBackground,
borderColor: 'rgba(255,255,255,0.05)' borderColor: 'rgba(255,255,255,0.05)'
}]}> }]}>
<Image <FastImage
source={{ uri: item.poster || PLACEHOLDER_POSTER }} source={{ uri: item.poster || PLACEHOLDER_POSTER }}
style={styles.horizontalItemPoster} style={styles.horizontalItemPoster}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
transition={300}
/> />
{/* Bookmark and watched icons top right, bookmark to the left of watched */} {/* Bookmark and watched icons top right, bookmark to the left of watched */}
{inLibrary && ( {inLibrary && (

View file

@ -10,7 +10,6 @@ import {
StatusBar, StatusBar,
Platform, Platform,
Dimensions, Dimensions,
Image,
Button, Button,
Linking, Linking,
Clipboard Clipboard
@ -18,6 +17,7 @@ import {
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NavigationProp } 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 { MaterialIcons } from '@expo/vector-icons';
import { Picker } from '@react-native-picker/picker'; import { Picker } from '@react-native-picker/picker';
import { useSettings, DEFAULT_SETTINGS } from '../hooks/useSettings'; import { useSettings, DEFAULT_SETTINGS } from '../hooks/useSettings';
@ -805,10 +805,10 @@ const SettingsScreen: React.FC = () => {
activeOpacity={0.7} activeOpacity={0.7}
> >
<View style={styles.discordButtonContent}> <View style={styles.discordButtonContent}>
<Image <FastImage
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }} source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
style={styles.discordLogo} style={styles.discordLogo}
resizeMode="contain" resizeMode={FastImage.resizeMode.contain}
/> />
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}> <Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
Join Discord Join Discord
@ -821,10 +821,10 @@ const SettingsScreen: React.FC = () => {
onPress={() => Linking.openURL('https://ko-fi.com/tapframe')} onPress={() => Linking.openURL('https://ko-fi.com/tapframe')}
activeOpacity={0.7} activeOpacity={0.7}
> >
<Image <FastImage
source={require('../../assets/support_me_on_kofi_red.png')} source={require('../../assets/support_me_on_kofi_red.png')}
style={styles.kofiImage} style={styles.kofiImage}
resizeMode="contain" resizeMode={FastImage.resizeMode.contain}
/> />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -892,10 +892,10 @@ const SettingsScreen: React.FC = () => {
activeOpacity={0.7} activeOpacity={0.7}
> >
<View style={styles.discordButtonContent}> <View style={styles.discordButtonContent}>
<Image <FastImage
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }} source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
style={styles.discordLogo} style={styles.discordLogo}
resizeMode="contain" resizeMode={FastImage.resizeMode.contain}
/> />
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}> <Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
Join Discord Join Discord
@ -908,10 +908,10 @@ const SettingsScreen: React.FC = () => {
onPress={() => Linking.openURL('https://ko-fi.com/tapframe')} onPress={() => Linking.openURL('https://ko-fi.com/tapframe')}
activeOpacity={0.7} activeOpacity={0.7}
> >
<Image <FastImage
source={require('../../assets/support_me_on_kofi_red.png')} source={require('../../assets/support_me_on_kofi_red.png')}
style={styles.kofiImage} style={styles.kofiImage}
resizeMode="contain" resizeMode={FastImage.resizeMode.contain}
/> />
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View file

@ -10,7 +10,7 @@ import {
Platform, Platform,
StatusBar, StatusBar,
} from 'react-native'; } from 'react-native';
import { Image } from 'expo-image'; import FastImage from '@d11/react-native-fast-image';
import { BlurView } from 'expo-blur'; import { BlurView } from 'expo-blur';
import { useTheme } from '../contexts/ThemeContext'; import { useTheme } from '../contexts/ThemeContext';
import { TMDBService, TMDBShow as Show, TMDBSeason, TMDBEpisode } from '../services/tmdbService'; 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 }) => ( const ShowInfo = memo(({ show, theme }: { show: Show | null, theme: any }) => (
<View style={styles.showInfo}> <View style={styles.showInfo}>
<Image <FastImage
source={{ uri: `https://image.tmdb.org/t/p/w500${show?.poster_path}` }} source={{ uri: `https://image.tmdb.org/t/p/w500${show?.poster_path}` }}
style={styles.poster} style={styles.poster}
contentFit="cover" resizeMode={FastImage.resizeMode.cover}
transition={200}
/> />
<View style={styles.showDetails}> <View style={styles.showDetails}>
<Text style={[styles.showTitle, { color: theme.colors.white }]}>{show?.name}</Text> <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 { NavigationProp } from '@react-navigation/native';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient'; 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 { RootStackParamList, RootStackNavigationProp } from '../navigation/AppNavigator';
import { useMetadata } from '../hooks/useMetadata'; import { useMetadata } from '../hooks/useMetadata';
import { useMetadataAssets } from '../hooks/useMetadataAssets'; import { useMetadataAssets } from '../hooks/useMetadataAssets';
@ -116,10 +116,10 @@ const AnimatedImage = memo(({
return ( return (
<Animated.View style={[style, animatedStyle]}> <Animated.View style={[style, animatedStyle]}>
<Image <FastImage
source={source} source={source}
style={StyleSheet.absoluteFillObject} style={StyleSheet.absoluteFillObject}
contentFit={contentFit} resizeMode={FastImage.resizeMode.cover}
onLoad={onLoad} onLoad={onLoad}
/> />
</Animated.View> </Animated.View>
@ -336,10 +336,10 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
{/* Scraper Logo */} {/* Scraper Logo */}
{showLogos && scraperLogo && ( {showLogos && scraperLogo && (
<View style={styles.scraperLogoContainer}> <View style={styles.scraperLogoContainer}>
<Image <FastImage
source={{ uri: scraperLogo }} source={{ uri: scraperLogo }}
style={styles.scraperLogo} style={styles.scraperLogo}
resizeMode="contain" resizeMode={FastImage.resizeMode.contain}
/> />
</View> </View>
)} )}
@ -1968,10 +1968,10 @@ export const StreamsScreen = () => {
<View style={[styles.movieTitleContainer]}> <View style={[styles.movieTitleContainer]}>
<View style={styles.movieTitleContent}> <View style={styles.movieTitleContent}>
{metadata.logo && !movieLogoError ? ( {metadata.logo && !movieLogoError ? (
<Image <FastImage
source={{ uri: metadata.logo }} source={{ uri: metadata.logo }}
style={styles.movieLogo} style={styles.movieLogo}
contentFit="contain" resizeMode={FastImage.resizeMode.contain}
onError={() => setMovieLogoError(true)} onError={() => setMovieLogoError(true)}
/> />
) : ( ) : (
@ -2021,7 +2021,7 @@ export const StreamsScreen = () => {
</Text> </Text>
{effectiveEpisodeVote > 0 && ( {effectiveEpisodeVote > 0 && (
<View style={styles.streamsHeroRating}> <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}> <Text style={styles.streamsHeroRatingText}>
{effectiveEpisodeVote.toFixed(1)} {effectiveEpisodeVote.toFixed(1)}
</Text> </Text>

View file

@ -14,7 +14,6 @@ import {
Keyboard, Keyboard,
Clipboard, Clipboard,
Switch, Switch,
Image,
KeyboardAvoidingView, KeyboardAvoidingView,
TouchableWithoutFeedback, TouchableWithoutFeedback,
Modal, Modal,
@ -22,6 +21,7 @@ import {
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import FastImage from '@d11/react-native-fast-image';
import { tmdbService } from '../services/tmdbService'; import { tmdbService } from '../services/tmdbService';
import { useSettings } from '../hooks/useSettings'; import { useSettings } from '../hooks/useSettings';
import { logger } from '../utils/logger'; import { logger } from '../utils/logger';
@ -387,17 +387,17 @@ const TMDBSettingsScreen = () => {
return ( return (
<View style={styles.bannerContainer}> <View style={styles.bannerContainer}>
<Image <FastImage
source={{ uri: banner || undefined }} source={{ uri: banner || undefined }}
style={styles.bannerImage} style={styles.bannerImage}
resizeMode="cover" resizeMode={FastImage.resizeMode.cover}
/> />
<View style={styles.bannerOverlay} /> <View style={styles.bannerOverlay} />
{logo && ( {logo && (
<Image <FastImage
source={{ uri: logo }} source={{ uri: logo }}
style={styles.logoOverBanner} style={styles.logoOverBanner}
resizeMode="contain" resizeMode={FastImage.resizeMode.contain}
/> />
)} )}
{!logo && ( {!logo && (

View file

@ -5,7 +5,6 @@ import {
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
ActivityIndicator, ActivityIndicator,
Image,
SafeAreaView, SafeAreaView,
ScrollView, ScrollView,
StatusBar, StatusBar,
@ -16,6 +15,7 @@ import {
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { makeRedirectUri, useAuthRequest, ResponseType, Prompt, CodeChallengeMethod } from 'expo-auth-session'; import { makeRedirectUri, useAuthRequest, ResponseType, Prompt, CodeChallengeMethod } from 'expo-auth-session';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import FastImage from '@d11/react-native-fast-image';
import { traktService, TraktUser } from '../services/traktService'; import { traktService, TraktUser } from '../services/traktService';
import { useSettings } from '../hooks/useSettings'; import { useSettings } from '../hooks/useSettings';
import { logger } from '../utils/logger'; import { logger } from '../utils/logger';
@ -259,9 +259,10 @@ const TraktSettingsScreen: React.FC = () => {
<View style={styles.profileContainer}> <View style={styles.profileContainer}>
<View style={styles.profileHeader}> <View style={styles.profileHeader}>
{userProfile.avatar ? ( {userProfile.avatar ? (
<Image <FastImage
source={{ uri: userProfile.avatar }} source={{ uri: userProfile.avatar }}
style={styles.avatar} style={styles.avatar}
resizeMode={FastImage.resizeMode.cover}
/> />
) : ( ) : (
<View style={[styles.avatarPlaceholder, { backgroundColor: currentTheme.colors.primary }]}> <View style={[styles.avatarPlaceholder, { backgroundColor: currentTheme.colors.primary }]}>