perfomnce optimziations

This commit is contained in:
tapframe 2025-09-08 17:28:10 +05:30
parent 6560f5a6a7
commit 02bfd85b5a
10 changed files with 142 additions and 369 deletions

View file

@ -40,10 +40,10 @@ Sentry.init({
// For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/
sendDefaultPii: true,
// Configure Session Replay
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1,
integrations: [Sentry.mobileReplayIntegration(), Sentry.feedbackIntegration()],
// Configure Session Replay conservatively to avoid startup overhead in production
replaysSessionSampleRate: __DEV__ ? 0.1 : 0,
replaysOnErrorSampleRate: __DEV__ ? 1 : 0,
integrations: [Sentry.feedbackIntegration()],
// uncomment the line below to enable Spotlight (https://spotlightjs.com)
// spotlight: __DEV__,

312
package-lock.json generated
View file

@ -22,7 +22,7 @@
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@sentry/react-native": "~6.10.0",
"@shopify/flash-list": "1.7.3",
"@shopify/flash-list": "2.0.3",
"@supabase/supabase-js": "^2.54.0",
"@types/lodash": "^4.17.16",
"@types/react-native-video": "^5.0.20",
@ -4204,108 +4204,6 @@
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-arm": {
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.4.tgz",
"integrity": "sha512-lBn0oeeg62h68/4Eo6zbPq99Idz5t0VRV48rEU/WKeM4MtQCvG/iGGQ3lBFW2yNiUBzXZIK9poXLEcgbwmcRVw==",
"cpu": [
"arm"
],
"license": "BSD-3-Clause",
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-arm64": {
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.4.tgz",
"integrity": "sha512-Ex8vRnryyzC/9e43daEmEqPS+9uirY/l6Hw2lAvhBblFaL7PTWNx52H+8GnYGd9Zy2H3rWNyBDYfHwnErg38zA==",
"cpu": [
"arm64"
],
"license": "BSD-3-Clause",
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-i686": {
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.4.tgz",
"integrity": "sha512-IBJg0aHjsLCL4LvcFa3cXIjA+4t5kPqBT9y+PoDu4goIFxYD8zl7mbUdGJutvJafTk8Akf4ss4JJXQBjg019zA==",
"cpu": [
"x86",
"ia32"
],
"license": "BSD-3-Clause",
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-x64": {
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.4.tgz",
"integrity": "sha512-gXI5OEiOSNiAEz7VCE6AZcAgHJ47mlgal3+NmbE8XcHmFOnyDws9FNie6PJAy8KZjXi3nqoBP9JVAbnmOix3uA==",
"cpu": [
"x64"
],
"license": "BSD-3-Clause",
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-i686": {
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.4.tgz",
"integrity": "sha512-vZuR3UPHKqOMniyrijrrsNwn9usaRysXq78F6WV0cL0ZyPLAmY+KBnTDSFk1Oig2pURnzaTm+RtcZu2fc8mlzg==",
"cpu": [
"x86",
"ia32"
],
"license": "BSD-3-Clause",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-x64": {
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.4.tgz",
"integrity": "sha512-OIBj3uaQ6nAERSm5Dcf8UIhyElEEwMNsZEEppQpN4IKl0mrwb/57AznM23Dvpu6GR8WGbVQUSolt879YZR5E9g==",
"cpu": [
"x64"
],
"license": "BSD-3-Clause",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/core": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz",
@ -4385,12 +4283,11 @@
}
},
"node_modules/@shopify/flash-list": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.7.3.tgz",
"integrity": "sha512-RLhNptm02aqpqZvjj9pJPcU+EVYxOAJhPRCmDOaUbUP86+636w+plsbjpBPSYGvPZhPj56RtZ9FBlvolPeEmYA==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-2.0.3.tgz",
"integrity": "sha512-jUlHuZFoPdqRCDvOqsb2YkTttRPyV8Tb/EjCx3gE2wjr4UTM+fE0Ltv9bwBg0K7yo/SxRNXaW7xu5utusRb0xA==",
"license": "MIT",
"dependencies": {
"recyclerlistview": "4.2.1",
"tslib": "2.8.1"
},
"peerDependencies": {
@ -9903,26 +9800,6 @@
"lightningcss-win32-x64-msvc": "1.27.0"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz",
"integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz",
@ -9943,166 +9820,6 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz",
"integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz",
"integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==",
"cpu": [
"arm"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz",
"integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz",
"integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz",
"integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz",
"integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz",
"integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz",
"integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@ -13048,21 +12765,6 @@
"node": ">= 4"
}
},
"node_modules/recyclerlistview": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.1.tgz",
"integrity": "sha512-NtVYjofwgUCt1rEsTp6jHQg/47TWjnO92TU2kTVgJ9wsc/ely4HnizHHa+f/dI7qaw4+zcSogElrLjhMltN2/g==",
"license": "Apache-2.0",
"dependencies": {
"lodash.debounce": "4.0.8",
"prop-types": "15.8.1",
"ts-object-utils": "0.0.5"
},
"peerDependencies": {
"react": ">= 15.2.1",
"react-native": ">= 0.30.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -14746,12 +14448,6 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"license": "Apache-2.0"
},
"node_modules/ts-object-utils": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz",
"integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==",
"license": "ISC"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",

View file

@ -22,7 +22,7 @@
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@sentry/react-native": "~6.10.0",
"@shopify/flash-list": "1.7.3",
"@shopify/flash-list": "2.0.3",
"@supabase/supabase-js": "^2.54.0",
"@types/lodash": "^4.17.16",
"@types/react-native-video": "^5.0.20",

View file

@ -1,5 +1,5 @@
import React, { useMemo, useState, useEffect, useCallback, memo } from 'react';
import { View, Text, StyleSheet, Dimensions, TouchableOpacity, ViewStyle, TextStyle, ImageStyle, FlatList, StyleProp } from 'react-native';
import { View, Text, StyleSheet, Dimensions, TouchableOpacity, ViewStyle, TextStyle, ImageStyle, FlatList, StyleProp, Platform } from 'react-native';
import Animated, { FadeIn, FadeOut, Easing, useSharedValue, withTiming, useAnimatedStyle, useAnimatedScrollHandler, useAnimatedReaction, runOnJS } from 'react-native-reanimated';
import { LinearGradient } from 'expo-linear-gradient';
import { Image as ExpoImage } from 'expo-image';
@ -157,9 +157,9 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
source={{ uri: item.banner || item.poster }}
style={styles.backgroundImage as ImageStyle}
contentFit="cover"
blurRadius={24}
blurRadius={Platform.OS === 'android' ? 12 : 20}
cachePolicy="memory-disk"
transition={300}
transition={200}
priority="high"
/>
<LinearGradient
@ -223,10 +223,11 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
decelerationRate="fast"
contentContainerStyle={contentPadding}
onScroll={scrollHandler}
scrollEventThrottle={16}
initialNumToRender={3}
scrollEventThrottle={32}
disableIntervalMomentum
initialNumToRender={2}
windowSize={3}
maxToRenderPerBatch={3}
maxToRenderPerBatch={2}
updateCellsBatchingPeriod={50}
removeClippedSubviews
getItemLayout={getItemLayout}

View file

@ -1,4 +1,5 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { InteractionManager } from 'react-native';
import accountService, { AuthUser } from '../services/AccountService';
import supabase from '../services/supabaseClient';
import syncService from '../services/SyncService';
@ -20,19 +21,29 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
useEffect(() => {
// Initial session (load full profile)
(async () => {
const u = await accountService.getCurrentUser();
setUser(u);
setLoading(false);
syncService.init();
if (u) {
await syncService.migrateLocalScopeToUser();
await syncService.subscribeRealtime();
// Pull first to hydrate local state, then push to avoid wiping server with empty local
await syncService.fullPull();
await syncService.fullPush();
}
})();
// Defer heavy work until after initial interactions to reduce launch CPU spike
const task = InteractionManager.runAfterInteractions(() => {
(async () => {
const u = await accountService.getCurrentUser();
setUser(u);
setLoading(false);
// Stage sync operations to avoid blocking the JS thread
syncService.init();
if (u) {
try {
await syncService.migrateLocalScopeToUser();
// Small yield to event loop
await new Promise(resolve => setTimeout(resolve, 50));
await syncService.subscribeRealtime();
await new Promise(resolve => setTimeout(resolve, 50));
// Pull first to hydrate local state, then push to avoid wiping server with empty local
await syncService.fullPull();
await new Promise(resolve => setTimeout(resolve, 50));
await syncService.fullPush();
} catch {}
}
})();
});
// Auth state listener
const { data: subscription } = supabase.auth.onAuthStateChange(async (_event, session) => {
@ -51,6 +62,7 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
return () => {
subscription.subscription.unsubscribe();
task.cancel();
};
}, []);

View file

@ -1141,20 +1141,24 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
const tmdbService = TMDBService.getInstance();
const fetchedTmdbId = await tmdbService.extractTMDBIdFromStremioId(id);
if (fetchedTmdbId) {
console.log('[useMetadata] extracted TMDB id from content id', { id, fetchedTmdbId });
setTmdbId(fetchedTmdbId);
// Fetch certification
const certification = await tmdbService.getCertification(type, fetchedTmdbId);
if (certification) {
console.log('[useMetadata] fetched certification via TMDB id (extract path)', { type, fetchedTmdbId, certification });
setMetadata(prev => prev ? {
...prev,
certification
} : null);
} else {
console.warn('[useMetadata] certification not returned from TMDB (extract path)', { type, fetchedTmdbId });
}
} else {
console.warn('Could not determine TMDB ID for recommendations.');
console.warn('[useMetadata] Could not determine TMDB ID for recommendations / certification', { id });
}
} catch (error) {
console.error('Error fetching TMDB ID:', error);
console.error('[useMetadata] Error fetching TMDB ID (extract path):', error);
}
}
};
@ -1164,6 +1168,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
useEffect(() => {
if (tmdbId) {
console.log('[useMetadata] tmdbId available; loading recommendations and enabling certification checks', { tmdbId });
loadRecommendations();
// Reset recommendations when tmdbId changes
return () => {
@ -1173,6 +1178,37 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
}
}, [tmdbId, loadRecommendations]);
// Ensure certification is attached whenever a TMDB id is known and metadata lacks it
useEffect(() => {
const maybeAttachCertification = async () => {
try {
if (!metadata) {
console.warn('[useMetadata] skip certification attach: metadata not ready');
return;
}
if (!tmdbId) {
console.warn('[useMetadata] skip certification attach: tmdbId not available yet');
return;
}
if ((metadata as any).certification) {
console.log('[useMetadata] certification already present on metadata; skipping fetch');
return;
}
const tmdbSvc = TMDBService.getInstance();
const cert = await tmdbSvc.getCertification(type, tmdbId);
if (cert) {
console.log('[useMetadata] fetched certification (attach path)', { type, tmdbId, cert });
setMetadata(prev => prev ? { ...prev, certification: cert } : prev);
} else {
console.warn('[useMetadata] TMDB returned no certification (attach path)', { type, tmdbId });
}
} catch (err) {
console.error('[useMetadata] error attaching certification', err);
}
};
maybeAttachCertification();
}, [tmdbId, metadata, type]);
// Reset tmdbId when id changes
useEffect(() => {
setTmdbId(null);

View file

@ -718,7 +718,10 @@ const MainTabs = () => {
elevation: 0,
backgroundColor: currentTheme.colors.darkBackground,
},
detachInactiveScreens: false,
// Ensure background tabs are frozen and detached
freezeOnBlur: true,
lazy: true,
detachInactiveScreens: true,
})}
>
<Tab.Screen
@ -926,6 +929,8 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
contentStyle: {
backgroundColor: currentTheme.colors.darkBackground,
},
// Freeze when blurred to stop timers/network without full unmount
freezeOnBlur: true,
}}
/>
<Stack.Screen
@ -948,6 +953,8 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
statusBarHidden: true,
statusBarAnimation: 'none',
}),
// Freeze when blurred to release resources safely
freezeOnBlur: true,
}}
/>
<Stack.Screen

View file

@ -158,7 +158,7 @@ const HomeScreen = () => {
let catalogIndex = 0;
// Limit concurrent catalog loading to prevent overwhelming the system
const MAX_CONCURRENT_CATALOGS = 5;
const MAX_CONCURRENT_CATALOGS = 3; // Lower concurrency to reduce CPU/network spikes
let activeCatalogLoads = 0;
const catalogQueue: (() => Promise<void>)[] = [];
@ -167,8 +167,10 @@ const HomeScreen = () => {
const catalogLoader = catalogQueue.shift();
if (catalogLoader) {
activeCatalogLoads++;
catalogLoader().finally(() => {
catalogLoader().finally(async () => {
activeCatalogLoads--;
// Yield to event loop to avoid JS thread starvation
await new Promise(resolve => setTimeout(resolve, 10));
processCatalogQueue(); // Process next in queue
});
}
@ -767,7 +769,6 @@ const HomeScreen = () => {
onEndReached={handleLoadMoreCatalogs}
onEndReachedThreshold={0.6}
scrollEventThrottle={32}
estimatedItemSize={220}
onScroll={event => {
const y = event.nativeEvent.contentOffset.y;
const dy = y - lastScrollYRef.current;

View file

@ -111,14 +111,17 @@ const MetadataScreen: React.FC = () => {
// Extract dominant color from hero image for dynamic background
const heroImageUri = useMemo(() => {
if (!settings.useDominantBackgroundColor) return null;
if (!metadata) return null;
return assetData.bannerImage || metadata.banner || metadata.poster || null;
}, [metadata, assetData.bannerImage]);
}, [settings.useDominantBackgroundColor, metadata, assetData.bannerImage]);
// Preload color extraction as soon as we have the URI
useEffect(() => {
if (heroImageUri) {
preloadDominantColor(heroImageUri);
InteractionManager.runAfterInteractions(() => {
preloadDominantColor(heroImageUri);
});
}
}, [heroImageUri]);
@ -188,7 +191,7 @@ const MetadataScreen: React.FC = () => {
// Debug logging for color extraction timing
useEffect(() => {
if (heroImageUri && dominantColor) {
if (__DEV__ && heroImageUri && dominantColor) {
console.log('[MetadataScreen] Dynamic background color:', {
dominantColor,
fallback: currentTheme.colors.darkBackground,
@ -266,7 +269,7 @@ const MetadataScreen: React.FC = () => {
if (relevantProgress.length === 0) return;
// Log only essential progress information for performance
console.log(`[MetadataScreen] Found ${relevantProgress.length} Trakt progress items for ${type}`);
if (__DEV__) console.log(`[MetadataScreen] Found ${relevantProgress.length} Trakt progress items for ${type}`);
// Find most recent progress if multiple episodes
if (type === 'series' && relevantProgress.length > 1) {
@ -275,7 +278,7 @@ const MetadataScreen: React.FC = () => {
)[0];
if (mostRecent.episode && mostRecent.show) {
console.log(`[MetadataScreen] Most recent: S${mostRecent.episode.season}E${mostRecent.episode.number} - ${mostRecent.progress.toFixed(1)}%`);
if (__DEV__) console.log(`[MetadataScreen] Most recent: S${mostRecent.episode.season}E${mostRecent.episode.number} - ${mostRecent.progress.toFixed(1)}%`);
}
}
@ -396,7 +399,7 @@ const MetadataScreen: React.FC = () => {
// DIRECT APPROACH: Just create the next episode ID directly
// This ensures we navigate to the next episode even if it's not yet in our episodes array
const nextEpisodeId = `${id}:${currentSeason}:${currentEpisode + 1}`;
console.log(`[MetadataScreen] Created next episode ID directly: ${nextEpisodeId}`);
if (__DEV__) console.log(`[MetadataScreen] Created next episode ID directly: ${nextEpisodeId}`);
// Still try to find the episode in our list to verify it exists
const nextEpisodeExists = episodes.some(ep =>
@ -404,9 +407,9 @@ const MetadataScreen: React.FC = () => {
);
if (nextEpisodeExists) {
console.log(`[MetadataScreen] Verified next episode S${currentSeason}E${currentEpisode + 1} exists in episodes list`);
if (__DEV__) console.log(`[MetadataScreen] Verified next episode S${currentSeason}E${currentEpisode + 1} exists in episodes list`);
} else {
console.log(`[MetadataScreen] Warning: Next episode S${currentSeason}E${currentEpisode + 1} not found in episodes list, but proceeding anyway`);
if (__DEV__) console.log(`[MetadataScreen] Warning: Next episode S${currentSeason}E${currentEpisode + 1} not found in episodes list, but proceeding anyway`);
}
targetEpisodeId = nextEpisodeId;
@ -416,7 +419,7 @@ const MetadataScreen: React.FC = () => {
// Fallback logic: if not finished or nextEp not found
if (!targetEpisodeId) {
targetEpisodeId = watchProgress?.episodeId || episodeId || (episodes.length > 0 ? buildEpisodeId(episodes[0]) : undefined);
console.log(`[MetadataScreen] Using fallback episode ID: ${targetEpisodeId}`);
if (__DEV__) console.log(`[MetadataScreen] Using fallback episode ID: ${targetEpisodeId}`);
}
if (targetEpisodeId) {
@ -426,7 +429,7 @@ const MetadataScreen: React.FC = () => {
if (epParts.length === 2) {
normalizedEpisodeId = `${id}:${epParts[0]}:${epParts[1]}`;
}
console.log(`[MetadataScreen] Navigating to streams with episodeId: ${normalizedEpisodeId}`);
if (__DEV__) console.log(`[MetadataScreen] Navigating to streams with episodeId: ${normalizedEpisodeId}`);
navigation.navigate('Streams', { id, type, episodeId: normalizedEpisodeId });
return;
}
@ -438,7 +441,7 @@ const MetadataScreen: React.FC = () => {
const p = episodeId.split(':');
fallbackEpisodeId = `${id}:${p[0]}:${p[1]}`;
}
console.log(`[MetadataScreen] Navigating with fallback episodeId: ${fallbackEpisodeId}`);
if (__DEV__) console.log(`[MetadataScreen] Navigating with fallback episodeId: ${fallbackEpisodeId}`);
navigation.navigate('Streams', { id, type, episodeId: fallbackEpisodeId });
}, [navigation, id, type, episodes, episodeId, watchProgressData.watchProgress]);

View file

@ -816,30 +816,47 @@ export class TMDBService {
*/
async getCertification(type: string, id: number): Promise<string | null> {
try {
// Different endpoints for movies and TV shows
const endpoint = type === 'movie' ? 'movie' : 'tv';
const response = await axios.get(`${BASE_URL}/${endpoint}/${id}/release_dates`, {
headers: await this.getHeaders(),
params: await this.getParams()
});
if (type === 'movie') {
const response = await axios.get(`${BASE_URL}/movie/${id}/release_dates`, {
headers: await this.getHeaders(),
params: await this.getParams()
});
if (response.data && response.data.results) {
// Try to find US certification first
const usRelease = response.data.results.find((r: any) => r.iso_3166_1 === 'US');
if (usRelease && usRelease.release_dates && usRelease.release_dates.length > 0) {
const certification = usRelease.release_dates.find((rd: any) => rd.certification)?.certification;
if (certification) return certification;
}
// Fallback to any certification if US is not available
for (const country of response.data.results) {
if (country.release_dates && country.release_dates.length > 0) {
const certification = country.release_dates.find((rd: any) => rd.certification)?.certification;
if (certification) return certification;
if (response.data && response.data.results) {
// Prefer US, then GB, then any
const countryPriority = ['US', 'GB'];
for (const code of countryPriority) {
const rel = response.data.results.find((r: any) => r.iso_3166_1 === code);
if (rel?.release_dates?.length) {
const cert = rel.release_dates.find((rd: any) => rd.certification)?.certification;
if (cert) return cert;
}
}
for (const country of response.data.results) {
const cert = country.release_dates?.find((rd: any) => rd.certification)?.certification;
if (cert) return cert;
}
}
return null;
} else {
// TV uses content ratings endpoint, not release_dates
const response = await axios.get(`${BASE_URL}/tv/${id}/content_ratings`, {
headers: await this.getHeaders(),
params: await this.getParams()
});
if (response.data && response.data.results) {
// Prefer US, then GB, then any
const countryPriority = ['US', 'GB'];
for (const code of countryPriority) {
const rating = response.data.results.find((r: any) => r.iso_3166_1 === code);
if (rating?.rating) return rating.rating;
}
const any = response.data.results.find((r: any) => !!r.rating);
if (any?.rating) return any.rating;
}
return null;
}
return null;
} catch (error) {
return null;
}