mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-10 12:01:55 +00:00
new tmdb ratings api
This commit is contained in:
parent
96f79f7c72
commit
e033352752
6 changed files with 207 additions and 171 deletions
|
|
@ -459,7 +459,7 @@
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||||
PRODUCT_NAME = Nuvio;
|
PRODUCT_NAME = "Nuvio";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
@ -490,8 +490,8 @@
|
||||||
"-lc++",
|
"-lc++",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuviohub.app;
|
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app";
|
||||||
PRODUCT_NAME = Nuvio;
|
PRODUCT_NAME = "Nuvio";
|
||||||
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";
|
||||||
|
|
|
||||||
|
|
@ -1,99 +1,101 @@
|
||||||
<?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>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Nuvio</string>
|
<string>Nuvio</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(PRODUCT_NAME)</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.2.7</string>
|
<string>1.2.7</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>nuvio</string>
|
<string>nuvio</string>
|
||||||
<string>com.nuvio.app</string>
|
<string>com.nuvio.app</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>exp+nuvio</string>
|
<string>exp+nuvio</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>22</string>
|
<string>22</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>12.0</string>
|
<string>12.0</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSBonjourServices</key>
|
<key>NSBonjourServices</key>
|
||||||
<array>
|
<array>
|
||||||
<string>_http._tcp</string>
|
<string>_http._tcp</string>
|
||||||
</array>
|
</array>
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
||||||
<key>RCTNewArchEnabled</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<true/>
|
<string>This app does not require microphone access.</string>
|
||||||
<key>RCTRootViewBackgroundColor</key>
|
<key>RCTNewArchEnabled</key>
|
||||||
<integer>4278322180</integer>
|
<true/>
|
||||||
<key>UIBackgroundModes</key>
|
<key>RCTRootViewBackgroundColor</key>
|
||||||
<array>
|
<integer>4278322180</integer>
|
||||||
<string>audio</string>
|
<key>UIBackgroundModes</key>
|
||||||
</array>
|
<array>
|
||||||
<key>UIFileSharingEnabled</key>
|
<string>audio</string>
|
||||||
<true/>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<string>SplashScreen</string>
|
<true/>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<array>
|
<string>SplashScreen</string>
|
||||||
<string>arm64</string>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
</array>
|
<array>
|
||||||
<key>UIRequiresFullScreen</key>
|
<string>arm64</string>
|
||||||
<true/>
|
</array>
|
||||||
<key>UIStatusBarStyle</key>
|
<key>UIRequiresFullScreen</key>
|
||||||
<string>UIStatusBarStyleDefault</string>
|
<true/>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UIStatusBarStyle</key>
|
||||||
<array>
|
<string>UIStatusBarStyleDefault</string>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<array>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
</array>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<array>
|
</array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<array>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
</array>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<key>UIUserInterfaceStyle</key>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<string>Dark</string>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIUserInterfaceStyle</key>
|
||||||
<false/>
|
<string>Dark</string>
|
||||||
</dict>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
</plist>
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<?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>
|
||||||
</plist>
|
<key>aps-environment</key>
|
||||||
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
<array/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
src/screens/.ShowRatingsScreen.tsx.swp
Normal file
BIN
src/screens/.ShowRatingsScreen.tsx.swp
Normal file
Binary file not shown.
|
|
@ -28,7 +28,7 @@ if (Platform.OS === 'ios') {
|
||||||
liquidGlassAvailable = false;
|
liquidGlassAvailable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
import { TMDBService, TMDBShow as Show, TMDBSeason, TMDBEpisode } from '../services/tmdbService';
|
import { TMDBService, TMDBShow as Show, TMDBSeason, TMDBEpisode, IMDbRatings } from '../services/tmdbService';
|
||||||
import { RouteProp } from '@react-navigation/native';
|
import { RouteProp } from '@react-navigation/native';
|
||||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
@ -78,17 +78,17 @@ const getRatingColor = (rating: number): string => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Memoized components
|
// Memoized components
|
||||||
const RatingCell = memo(({ episode, ratingSource, getTVMazeRating, isCurrentSeason, theme }: {
|
const RatingCell = memo(({ episode, ratingSource, getTVMazeRating, getIMDbRating, theme }: {
|
||||||
episode: TMDBEpisode;
|
episode: TMDBEpisode;
|
||||||
ratingSource: RatingSource;
|
ratingSource: RatingSource;
|
||||||
getTVMazeRating: (seasonNumber: number, episodeNumber: number) => number | null;
|
getTVMazeRating: (seasonNumber: number, episodeNumber: number) => number | null;
|
||||||
isCurrentSeason: (episode: TMDBEpisode) => boolean;
|
getIMDbRating: (seasonNumber: number, episodeNumber: number) => number | null;
|
||||||
theme: any;
|
theme: any;
|
||||||
}) => {
|
}) => {
|
||||||
const getRatingForSource = useCallback((episode: TMDBEpisode): number | null => {
|
const getRatingForSource = useCallback((episode: TMDBEpisode): number | null => {
|
||||||
switch (ratingSource) {
|
switch (ratingSource) {
|
||||||
case 'imdb':
|
case 'imdb':
|
||||||
return episode.imdb_rating || null;
|
return getIMDbRating(episode.season_number, episode.episode_number);
|
||||||
case 'tmdb':
|
case 'tmdb':
|
||||||
return episode.vote_average || null;
|
return episode.vote_average || null;
|
||||||
case 'tvmaze':
|
case 'tvmaze':
|
||||||
|
|
@ -96,23 +96,25 @@ const RatingCell = memo(({ episode, ratingSource, getTVMazeRating, isCurrentSeas
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [ratingSource, getTVMazeRating]);
|
}, [ratingSource, getTVMazeRating, getIMDbRating]);
|
||||||
|
|
||||||
const isRatingPotentiallyInaccurate = useCallback((episode: TMDBEpisode): boolean => {
|
const isRatingPotentiallyInaccurate = useCallback((episode: TMDBEpisode): boolean => {
|
||||||
const rating = getRatingForSource(episode);
|
const rating = getRatingForSource(episode);
|
||||||
if (!rating) return false;
|
if (!rating) return false;
|
||||||
|
|
||||||
if (ratingSource === 'tmdb' && episode.imdb_rating) {
|
if (ratingSource === 'tmdb') {
|
||||||
const difference = Math.abs(rating - episode.imdb_rating);
|
const imdbRating = getIMDbRating(episode.season_number, episode.episode_number);
|
||||||
return difference >= 2;
|
if (imdbRating) {
|
||||||
|
const difference = Math.abs(rating - imdbRating);
|
||||||
|
return difference >= 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, [getRatingForSource, ratingSource]);
|
}, [getRatingForSource, ratingSource, getIMDbRating]);
|
||||||
|
|
||||||
const rating = getRatingForSource(episode);
|
const rating = getRatingForSource(episode);
|
||||||
const isInaccurate = isRatingPotentiallyInaccurate(episode);
|
const isInaccurate = isRatingPotentiallyInaccurate(episode);
|
||||||
const isCurrent = isCurrentSeason(episode);
|
|
||||||
|
|
||||||
if (!rating) {
|
if (!rating) {
|
||||||
if (!episode.air_date || new Date(episode.air_date) > new Date()) {
|
if (!episode.air_date || new Date(episode.air_date) > new Date()) {
|
||||||
|
|
@ -135,16 +137,15 @@ const RatingCell = memo(({ episode, ratingSource, getTVMazeRating, isCurrentSeas
|
||||||
styles.ratingCell,
|
styles.ratingCell,
|
||||||
{
|
{
|
||||||
backgroundColor: getRatingColor(rating),
|
backgroundColor: getRatingColor(rating),
|
||||||
opacity: isCurrent ? 0.7 : 1,
|
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
<Text style={styles.ratingText}>{rating.toFixed(1)}</Text>
|
<Text style={styles.ratingText}>{rating.toFixed(1)}</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
{(isInaccurate || isCurrent) && (
|
{isInaccurate && (
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={isCurrent ? "schedule" : "warning"}
|
name="warning"
|
||||||
size={12}
|
size={12}
|
||||||
color={isCurrent ? theme.colors.primary : theme.colors.warning}
|
color={theme.colors.warning}
|
||||||
style={styles.warningIcon}
|
style={styles.warningIcon}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -160,7 +161,7 @@ const RatingSourceToggle = memo(({ ratingSource, setRatingSource, theme }: {
|
||||||
<View style={styles.ratingSourceContainer}>
|
<View style={styles.ratingSourceContainer}>
|
||||||
<Text style={[styles.sectionTitle, { color: theme.colors.white }]}>Rating Source:</Text>
|
<Text style={[styles.sectionTitle, { color: theme.colors.white }]}>Rating Source:</Text>
|
||||||
<View style={styles.ratingSourceButtons}>
|
<View style={styles.ratingSourceButtons}>
|
||||||
{['imdb', 'tmdb', 'tvmaze'].map((source) => {
|
{['tmdb', 'imdb', 'tvmaze'].map((source) => {
|
||||||
const isActive = ratingSource === source;
|
const isActive = ratingSource === source;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|
@ -217,10 +218,11 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
||||||
const [show, setShow] = useState<Show | null>(null);
|
const [show, setShow] = useState<Show | null>(null);
|
||||||
const [seasons, setSeasons] = useState<TMDBSeason[]>([]);
|
const [seasons, setSeasons] = useState<TMDBSeason[]>([]);
|
||||||
const [tvmazeEpisodes, setTvmazeEpisodes] = useState<TVMazeEpisode[]>([]);
|
const [tvmazeEpisodes, setTvmazeEpisodes] = useState<TVMazeEpisode[]>([]);
|
||||||
|
const [imdbRatings, setImdbRatings] = useState<IMDbRatings>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [loadingSeasons, setLoadingSeasons] = useState(false);
|
const [loadingSeasons, setLoadingSeasons] = useState(false);
|
||||||
const [loadedSeasons, setLoadedSeasons] = useState<number[]>([]);
|
const [loadedSeasons, setLoadedSeasons] = useState<number[]>([]);
|
||||||
const [ratingSource, setRatingSource] = useState<RatingSource>('tmdb');
|
const [ratingSource, setRatingSource] = useState<RatingSource>('imdb');
|
||||||
const [visibleSeasonRange, setVisibleSeasonRange] = useState({ start: 0, end: 8 });
|
const [visibleSeasonRange, setVisibleSeasonRange] = useState({ start: 0, end: 8 });
|
||||||
const [loadingProgress, setLoadingProgress] = useState(0);
|
const [loadingProgress, setLoadingProgress] = useState(0);
|
||||||
const ratingsCache = useRef<{[key: string]: number | null}>({});
|
const ratingsCache = useRef<{[key: string]: number | null}>({});
|
||||||
|
|
@ -316,6 +318,12 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
||||||
if (showData) {
|
if (showData) {
|
||||||
setShow(showData);
|
setShow(showData);
|
||||||
|
|
||||||
|
// Fetch IMDb ratings for all seasons
|
||||||
|
const imdbRatingsData = await tmdb.getIMDbRatings(showId);
|
||||||
|
if (imdbRatingsData) {
|
||||||
|
setImdbRatings(imdbRatingsData);
|
||||||
|
}
|
||||||
|
|
||||||
// Get external IDs to fetch TVMaze data
|
// Get external IDs to fetch TVMaze data
|
||||||
const externalIds = await tmdb.getShowExternalIds(showId);
|
const externalIds = await tmdb.getShowExternalIds(showId);
|
||||||
if (externalIds?.imdb_id) {
|
if (externalIds?.imdb_id) {
|
||||||
|
|
@ -347,19 +355,22 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
||||||
return episode?.rating?.average || null;
|
return episode?.rating?.average || null;
|
||||||
}, [tvmazeEpisodes]);
|
}, [tvmazeEpisodes]);
|
||||||
|
|
||||||
const isCurrentSeason = useCallback((episode: TMDBEpisode): boolean => {
|
const getIMDbRating = useCallback((seasonNumber: number, episodeNumber: number): number | null => {
|
||||||
if (!seasons.length || !episode.air_date) return false;
|
// Flatten all episodes from all seasons and find the matching one
|
||||||
|
for (const season of imdbRatings) {
|
||||||
|
if (!season.episodes) continue;
|
||||||
|
|
||||||
|
const episode = season.episodes.find(
|
||||||
|
ep => ep.season_number === seasonNumber && ep.episode_number === episodeNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
if (episode) {
|
||||||
|
return episode.vote_average || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const latestSeasonNumber = Math.max(...seasons.map(s => s.season_number));
|
return null;
|
||||||
if (episode.season_number !== latestSeasonNumber) return false;
|
}, [imdbRatings]);
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const airDate = new Date(episode.air_date);
|
|
||||||
const monthsDiff = (now.getFullYear() - airDate.getFullYear()) * 12 +
|
|
||||||
(now.getMonth() - airDate.getMonth());
|
|
||||||
|
|
||||||
return monthsDiff <= 6;
|
|
||||||
}, [seasons]);
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -459,10 +470,6 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
||||||
<MaterialIcons name="warning" size={14} color={colors.warning} />
|
<MaterialIcons name="warning" size={14} color={colors.warning} />
|
||||||
<Text style={[styles.warningText, { color: colors.lightGray }]}>Rating differs significantly from IMDb</Text>
|
<Text style={[styles.warningText, { color: colors.lightGray }]}>Rating differs significantly from IMDb</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.warningLegend}>
|
|
||||||
<MaterialIcons name="schedule" size={14} color={colors.primary} />
|
|
||||||
<Text style={[styles.warningText, { color: colors.lightGray }]}>Current season (ratings may change)</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|
@ -535,7 +542,7 @@ const ShowRatingsScreen = ({ route }: Props) => {
|
||||||
episode={season.episodes[episodeIndex]}
|
episode={season.episodes[episodeIndex]}
|
||||||
ratingSource={ratingSource}
|
ratingSource={ratingSource}
|
||||||
getTVMazeRating={getTVMazeRating}
|
getTVMazeRating={getTVMazeRating}
|
||||||
isCurrentSeason={isCurrentSeason}
|
getIMDbRating={getIMDbRating}
|
||||||
theme={currentTheme}
|
theme={currentTheme}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,21 @@ export interface TMDBCollectionPart {
|
||||||
popularity: number;
|
popularity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Types for IMDb ratings API responses
|
||||||
|
export interface IMDbRatingEpisode {
|
||||||
|
vote_average: number;
|
||||||
|
episode_number: number;
|
||||||
|
name: string;
|
||||||
|
season_number: number;
|
||||||
|
tconst: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMDbRatingSeason {
|
||||||
|
episodes: IMDbRatingEpisode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IMDbRatings = IMDbRatingSeason[];
|
||||||
|
|
||||||
export class TMDBService {
|
export class TMDBService {
|
||||||
private static instance: TMDBService;
|
private static instance: TMDBService;
|
||||||
private static ratingCache: Map<string, number | null> = new Map();
|
private static ratingCache: Map<string, number | null> = new Map();
|
||||||
|
|
@ -351,6 +366,7 @@ export class TMDBService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get IMDb rating for an episode using OMDB API with caching
|
* Get IMDb rating for an episode using OMDB API with caching
|
||||||
|
* @deprecated This method is deprecated. Use getIMDbRatings instead for better accuracy and performance.
|
||||||
*/
|
*/
|
||||||
async getIMDbRating(showName: string, seasonNumber: number, episodeNumber: number): Promise<number | null> {
|
async getIMDbRating(showName: string, seasonNumber: number, episodeNumber: number): Promise<number | null> {
|
||||||
const cacheKey = this.generateRatingCacheKey(showName, seasonNumber, episodeNumber);
|
const cacheKey = this.generateRatingCacheKey(showName, seasonNumber, episodeNumber);
|
||||||
|
|
@ -387,7 +403,49 @@ export class TMDBService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get season details including all episodes with IMDb ratings
|
* Get IMDb ratings for all seasons and episodes
|
||||||
|
* This replaces the OMDB API approach and provides more accurate ratings
|
||||||
|
*/
|
||||||
|
async getIMDbRatings(tmdbId: number): Promise<IMDbRatings | null> {
|
||||||
|
const IMDB_RATINGS_API_BASE_URL = process.env.EXPO_PUBLIC_IMDB_RATINGS_API_BASE_URL;
|
||||||
|
|
||||||
|
if (!IMDB_RATINGS_API_BASE_URL) {
|
||||||
|
logger.error('[TMDB API] Missing EXPO_PUBLIC_IMDB_RATINGS_API_BASE_URL environment variable');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = this.generateCacheKey(`imdb_ratings_${tmdbId}`);
|
||||||
|
|
||||||
|
// Check cache
|
||||||
|
const cached = this.getCachedData<IMDbRatings>(cacheKey);
|
||||||
|
if (cached !== null) return cached;
|
||||||
|
|
||||||
|
const apiUrl = `${IMDB_RATINGS_API_BASE_URL}/api/shows/${tmdbId}/season-ratings`;
|
||||||
|
|
||||||
|
logger.log(`[TMDB API] 🌐 FETCHING: getIMDbRatings(${tmdbId})`);
|
||||||
|
try {
|
||||||
|
const response = await axios.get(apiUrl, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = response.data;
|
||||||
|
if (data && Array.isArray(data)) {
|
||||||
|
this.setCachedData(cacheKey, data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[TMDB API] Error fetching IMDb ratings:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get season details including all episodes
|
||||||
|
* Note: IMDb ratings are now fetched separately via getIMDbRatings() for better accuracy
|
||||||
*/
|
*/
|
||||||
async getSeasonDetails(tmdbId: number, seasonNumber: number, showName?: string, language: string = 'en-US'): Promise<TMDBSeason | null> {
|
async getSeasonDetails(tmdbId: number, seasonNumber: number, showName?: string, language: string = 'en-US'): Promise<TMDBSeason | null> {
|
||||||
const cacheKey = this.generateCacheKey(`tv_${tmdbId}_season_${seasonNumber}`, { language, showName });
|
const cacheKey = this.generateCacheKey(`tv_${tmdbId}_season_${seasonNumber}`, { language, showName });
|
||||||
|
|
@ -405,42 +463,6 @@ export class TMDBService {
|
||||||
});
|
});
|
||||||
|
|
||||||
const season = response.data;
|
const season = response.data;
|
||||||
|
|
||||||
// If show name is provided, fetch IMDb ratings for each episode in batches
|
|
||||||
if (showName) {
|
|
||||||
// Process episodes in batches of 5 to avoid rate limiting
|
|
||||||
const batchSize = 5;
|
|
||||||
const episodes = [...season.episodes];
|
|
||||||
const episodesWithRatings = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < episodes.length; i += batchSize) {
|
|
||||||
const batch = episodes.slice(i, i + batchSize);
|
|
||||||
const batchPromises = batch.map(async (episode: TMDBEpisode) => {
|
|
||||||
const imdbRating = await this.getIMDbRating(
|
|
||||||
showName,
|
|
||||||
episode.season_number,
|
|
||||||
episode.episode_number
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...episode,
|
|
||||||
imdb_rating: imdbRating
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const batchResults = await Promise.all(batchPromises);
|
|
||||||
episodesWithRatings.push(...batchResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
...season,
|
|
||||||
episodes: episodesWithRatings,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setCachedData(cacheKey, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setCachedData(cacheKey, season);
|
this.setCachedData(cacheKey, season);
|
||||||
return season;
|
return season;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue