alert orientation fix

This commit is contained in:
tapframe 2025-12-19 23:38:33 +05:30
parent 2d5b1263b5
commit 5804959ddf
11 changed files with 242 additions and 214 deletions

View file

@ -101,6 +101,7 @@ const AnnouncementOverlay: React.FC<AnnouncementOverlayProps> = ({
transparent
animationType="none"
statusBarTranslucent
supportedOrientations={['portrait', 'landscape']}
onRequestClose={handleClose}
>
<View style={styles.overlay}>

View file

@ -98,7 +98,7 @@ export const DropUpMenu = ({ visible, onClose, item, onOptionSelect, isSaved: is
const isWatched = !!isWatchedProp;
const inTraktWatchlist = isAuthenticated && isInWatchlist(item.id, item.type as 'movie' | 'show');
const inTraktCollection = isAuthenticated && isInCollection(item.id, item.type as 'movie' | 'show');
let menuOptions = [
{
icon: 'bookmark',
@ -152,6 +152,7 @@ export const DropUpMenu = ({ visible, onClose, item, onOptionSelect, isSaved: is
visible={visible}
transparent
animationType="none"
supportedOrientations={['portrait', 'landscape']}
onRequestClose={onClose}
>
<GestureHandlerRootView style={{ flex: 1 }}>
@ -162,7 +163,7 @@ export const DropUpMenu = ({ visible, onClose, item, onOptionSelect, isSaved: is
<View style={styles.dragHandle} />
<View style={styles.menuHeader}>
<FastImage
source={{
source={{
uri: item.poster,
priority: FastImage.priority.high,
cache: FastImage.cacheControl.immutable

View file

@ -1660,6 +1660,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
animationType="fade"
onRequestClose={closeEpisodeActionMenu}
statusBarTranslucent
supportedOrientations={['portrait', 'landscape']}
>
<Pressable
style={{

View file

@ -74,7 +74,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
// Enhanced responsive sizing for tablets and TV screens
const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('window').height;
// Determine device type based on width
const getDeviceType = useCallback(() => {
if (deviceWidth >= BREAKPOINTS.tv) return 'tv';
@ -82,13 +82,13 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet';
return 'phone';
}, [deviceWidth]);
const deviceType = getDeviceType();
const isTablet = deviceType === 'tablet';
const isLargeTablet = deviceType === 'largeTablet';
const isTV = deviceType === 'tv';
const isLargeScreen = isTablet || isLargeTablet || isTV;
// Enhanced spacing and padding
const horizontalPadding = useMemo(() => {
switch (deviceType) {
@ -102,7 +102,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
return 16; // phone
}
}, [deviceType]);
// Enhanced trailer card sizing
const trailerCardWidth = useMemo(() => {
switch (deviceType) {
@ -116,7 +116,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
return 200; // phone
}
}, [deviceType]);
const trailerCardSpacing = useMemo(() => {
switch (deviceType) {
case 'tv':
@ -293,7 +293,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
// Auto-select the first available category, preferring "Trailer"
const availableCategories = Object.keys(categorized);
const preferredCategory = availableCategories.includes('Trailer') ? 'Trailer' :
availableCategories.includes('Teaser') ? 'Teaser' : availableCategories[0];
availableCategories.includes('Teaser') ? 'Teaser' : availableCategories[0];
setSelectedCategory(preferredCategory);
}
} catch (err) {
@ -379,7 +379,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
} catch (error) {
logger.warn('TrailersSection', 'Error pausing hero trailer:', error);
}
setSelectedTrailer(trailer);
setModalVisible(true);
};
@ -499,15 +499,15 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
return (
<Animated.View style={[
styles.container,
styles.container,
sectionAnimatedStyle,
{ paddingHorizontal: horizontalPadding }
]}>
{/* Enhanced Header with Category Selector */}
<View style={styles.header}>
<Text style={[
styles.headerTitle,
{
styles.headerTitle,
{
color: currentTheme.colors.highEmphasis,
fontSize: isTV ? 28 : isLargeTablet ? 26 : isTablet ? 24 : 20
}
@ -519,8 +519,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
{trailerCategories.length > 0 && selectedCategory && (
<TouchableOpacity
style={[
styles.categorySelector,
{
styles.categorySelector,
{
borderColor: 'rgba(255,255,255,0.6)',
paddingHorizontal: isTV ? 14 : isLargeTablet ? 12 : isTablet ? 10 : 10,
paddingVertical: isTV ? 8 : isLargeTablet ? 6 : isTablet ? 5 : 5,
@ -533,8 +533,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
>
<Text
style={[
styles.categorySelectorText,
{
styles.categorySelectorText,
{
color: currentTheme.colors.highEmphasis,
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12,
maxWidth: isTV ? 150 : isLargeTablet ? 130 : isTablet ? 120 : 120
@ -559,6 +559,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
visible={dropdownVisible}
transparent={true}
animationType="fade"
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => setDropdownVisible(false)}
>
<TouchableOpacity
@ -587,7 +588,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
>
<View style={styles.dropdownItemContent}>
<View style={[
styles.categoryIconContainer,
styles.categoryIconContainer,
{
backgroundColor: currentTheme.colors.primary + '15',
width: isTV ? 36 : isLargeTablet ? 32 : isTablet ? 28 : 28,
@ -601,18 +602,18 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
color={currentTheme.colors.primary}
/>
</View>
<Text style={[
styles.dropdownItemText,
{
color: currentTheme.colors.highEmphasis,
fontSize: isTV ? 18 : isLargeTablet ? 17 : isTablet ? 16 : 16
}
]}>
<Text style={[
styles.dropdownItemText,
{
color: currentTheme.colors.highEmphasis,
fontSize: isTV ? 18 : isLargeTablet ? 17 : isTablet ? 16 : 16
}
]}>
{formatTrailerType(category)}
</Text>
<Text style={[
styles.dropdownItemCount,
{
styles.dropdownItemCount,
{
color: currentTheme.colors.textMuted,
fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12,
paddingHorizontal: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8,
@ -690,8 +691,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
<View style={styles.trailerInfoBelow}>
<Text
style={[
styles.trailerTitle,
{
styles.trailerTitle,
{
color: currentTheme.colors.highEmphasis,
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12,
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 18 : 16,
@ -704,8 +705,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
{trailer.displayName || trailer.name}
</Text>
<Text style={[
styles.trailerMeta,
{
styles.trailerMeta,
{
color: currentTheme.colors.textMuted,
fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 10
}

View file

@ -135,10 +135,10 @@ const AndroidVideoPlayer: React.FC = () => {
// Helper to get dynamic volume icon
const getVolumeIcon = (value: number) => {
if (value === 0) return 'volume-off';
if (value < 0.3) return 'volume-mute';
if (value < 0.6) return 'volume-down';
return 'volume-up';
if (value === 0) return 'volume-off';
if (value < 0.3) return 'volume-mute';
if (value < 0.6) return 'volume-down';
return 'volume-up';
};
// Helper to get dynamic brightness icon
@ -3432,8 +3432,6 @@ const AndroidVideoPlayer: React.FC = () => {
buffered={buffered}
formatTime={formatTime}
playerBackend={useVLC ? 'VLC' : 'ExoPlayer'}
nextLoadingTitle={nextLoadingTitle}
controlsFixedOffset={Math.min(Dimensions.get('window').width, Dimensions.get('window').height) >= 768 ? 120 : 100}
/>
{/* Combined Volume & Brightness Gesture Indicator - NEW PILL STYLE (No Bar) */}
@ -3441,45 +3439,45 @@ const AndroidVideoPlayer: React.FC = () => {
<View style={localStyles.gestureIndicatorContainer}>
{/* Dynamic Icon */}
<View
style={[
localStyles.iconWrapper,
{
// Conditional Background Color Logic
backgroundColor: gestureControls.showVolumeOverlay && volume === 0
? 'rgba(242, 184, 181)'
: 'rgba(59, 59, 59)'
}
]}
>
<MaterialIcons
name={
gestureControls.showVolumeOverlay
? getVolumeIcon(volume)
: getBrightnessIcon(brightness)
}
size={24} // Reduced size to fit inside a 32-40px circle better
color={
gestureControls.showVolumeOverlay && volume === 0
? 'rgba(96, 20, 16)' // Bright RED for MUTE icon itself
: 'rgba(255, 255, 255)' // White for all other states
}
/>
style={[
localStyles.iconWrapper,
{
// Conditional Background Color Logic
backgroundColor: gestureControls.showVolumeOverlay && volume === 0
? 'rgba(242, 184, 181)'
: 'rgba(59, 59, 59)'
}
]}
>
<MaterialIcons
name={
gestureControls.showVolumeOverlay
? getVolumeIcon(volume)
: getBrightnessIcon(brightness)
}
size={24} // Reduced size to fit inside a 32-40px circle better
color={
gestureControls.showVolumeOverlay && volume === 0
? 'rgba(96, 20, 16)' // Bright RED for MUTE icon itself
: 'rgba(255, 255, 255)' // White for all other states
}
/>
</View>
{/* Text Label: Shows "Muted" or percentage */}
<Text
style={[
localStyles.gestureText,
// Conditional Text Color Logic
gestureControls.showVolumeOverlay && volume === 0 && { color: 'rgba(242, 184, 181)' } // Light RED for "Muted"
]}
>
{/* Conditional Text Content Logic */}
{gestureControls.showVolumeOverlay && volume === 0
? "Muted" // Display "Muted" when volume is 0
: `${Math.round((gestureControls.showVolumeOverlay ? volume : brightness) * 100)}%` // Display percentage otherwise
}
</Text>
<Text
style={[
localStyles.gestureText,
// Conditional Text Color Logic
gestureControls.showVolumeOverlay && volume === 0 && { color: 'rgba(242, 184, 181)' } // Light RED for "Muted"
]}
>
{/* Conditional Text Content Logic */}
{gestureControls.showVolumeOverlay && volume === 0
? "Muted" // Display "Muted" when volume is 0
: `${Math.round((gestureControls.showVolumeOverlay ? volume : brightness) * 100)}%` // Display percentage otherwise
}
</Text>
</View>
)}
@ -4067,32 +4065,32 @@ const AndroidVideoPlayer: React.FC = () => {
// New styles for the gesture indicator
const localStyles = StyleSheet.create({
gestureIndicatorContainer: {
position: 'absolute',
top: '4%', // Adjust this for vertical position
alignSelf: 'center', // Adjust this for horizontal position
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(25, 25, 25)', // Dark pill background
borderRadius: 70,
paddingHorizontal: 15,
paddingVertical: 15,
zIndex: 2000, // Very high z-index to ensure visibility
minWidth: 120, // Adjusted min width since bar is removed
position: 'absolute',
top: '4%', // Adjust this for vertical position
alignSelf: 'center', // Adjust this for horizontal position
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(25, 25, 25)', // Dark pill background
borderRadius: 70,
paddingHorizontal: 15,
paddingVertical: 15,
zIndex: 2000, // Very high z-index to ensure visibility
minWidth: 120, // Adjusted min width since bar is removed
},
iconWrapper: {
borderRadius: 50, // Makes it a perfect circle (set to a high number)
width: 40, // Define the diameter of the circle
height: 40, // Define the diameter of the circle
justifyContent: 'center',
alignItems: 'center',
marginRight: 12, // Margin to separate icon circle from percentage text
borderRadius: 50, // Makes it a perfect circle (set to a high number)
width: 40, // Define the diameter of the circle
height: 40, // Define the diameter of the circle
justifyContent: 'center',
alignItems: 'center',
marginRight: 12, // Margin to separate icon circle from percentage text
},
gestureText: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: 'normal',
minWidth: 35,
textAlign: 'right',
color: '#FFFFFF',
fontSize: 18,
fontWeight: 'normal',
minWidth: 35,
textAlign: 'right',
},
});

View file

@ -1413,6 +1413,7 @@ const AddonsScreen = () => {
visible={showConfirmModal}
transparent
animationType="fade"
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => {
setShowConfirmModal(false);
setAddonDetails(null);

View file

@ -685,6 +685,7 @@ const CatalogSettingsScreen = () => {
animationType="fade"
transparent={true}
visible={isRenameModalVisible}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => {
setIsRenameModalVisible(false);
setCatalogToRename(null);

View file

@ -1946,6 +1946,7 @@ const PluginsScreen: React.FC = () => {
visible={showHelpModal}
transparent={true}
animationType="fade"
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => setShowHelpModal(false)}
>
<View style={styles.modalOverlay}>
@ -1978,6 +1979,7 @@ const PluginsScreen: React.FC = () => {
visible={showAddRepositoryModal}
transparent={true}
animationType="fade"
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => setShowAddRepositoryModal(false)}
>
<View style={styles.modalOverlay}>

View file

@ -33,7 +33,7 @@ const ProfilesScreen: React.FC = () => {
const navigation = useNavigation();
const { currentTheme } = useTheme();
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
const [profiles, setProfiles] = useState<Profile[]>([]);
const [showAddModal, setShowAddModal] = useState(false);
const [newProfileName, setNewProfileName] = useState('');
@ -52,7 +52,7 @@ const ProfilesScreen: React.FC = () => {
) => {
setAlertTitle(title);
setAlertMessage(message);
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => { } }]);
setAlertVisible(true);
};
@ -92,7 +92,7 @@ const ProfilesScreen: React.FC = () => {
}
});
});
return unsubscribe;
}, [navigation, refreshAuthStatus, isAuthenticated, loadProfiles]);
@ -112,7 +112,7 @@ const ProfilesScreen: React.FC = () => {
navigation.goBack();
return;
}
loadProfiles();
}, [isAuthenticated, loadProfiles, navigation]);
@ -141,7 +141,7 @@ const ProfilesScreen: React.FC = () => {
...profile,
isActive: profile.id === id
}));
setProfiles(updatedProfiles);
saveProfiles(updatedProfiles);
}, [profiles, saveProfiles]);
@ -164,14 +164,14 @@ const ProfilesScreen: React.FC = () => {
'Delete Profile',
'Are you sure you want to delete this profile? This action cannot be undone.',
[
{ label: 'Cancel', onPress: () => {} },
{
label: 'Delete',
{ label: 'Cancel', onPress: () => { } },
{
label: 'Delete',
onPress: () => {
const updatedProfiles = profiles.filter(profile => profile.id !== id);
setProfiles(updatedProfiles);
saveProfiles(updatedProfiles);
}
}
}
]
);
@ -183,10 +183,10 @@ const ProfilesScreen: React.FC = () => {
const renderItem = ({ item }: { item: Profile }) => (
<View style={styles.profileItem}>
<TouchableOpacity
<TouchableOpacity
style={[
styles.profileContent,
item.isActive && {
item.isActive && {
backgroundColor: `${currentTheme.colors.primary}30`,
borderColor: currentTheme.colors.primary
}
@ -194,10 +194,10 @@ const ProfilesScreen: React.FC = () => {
onPress={() => handleSelectProfile(item.id)}
>
<View style={styles.avatarContainer}>
<MaterialIcons
name="account-circle"
size={40}
color={item.isActive ? currentTheme.colors.primary : currentTheme.colors.text}
<MaterialIcons
name="account-circle"
size={40}
color={item.isActive ? currentTheme.colors.primary : currentTheme.colors.text}
/>
</View>
<View style={styles.profileInfo}>
@ -211,7 +211,7 @@ const ProfilesScreen: React.FC = () => {
)}
</View>
{!item.isActive && (
<TouchableOpacity
<TouchableOpacity
style={styles.deleteButton}
onPress={() => handleDeleteProfile(item.id)}
>
@ -225,7 +225,7 @@ const ProfilesScreen: React.FC = () => {
return (
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<StatusBar barStyle="light-content" backgroundColor="transparent" translucent />
<View style={styles.header}>
<TouchableOpacity
onPress={handleBack}
@ -281,6 +281,7 @@ const ProfilesScreen: React.FC = () => {
visible={showAddModal}
transparent
animationType="fade"
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => setShowAddModal(false)}
>
<View style={styles.modalOverlay}>
@ -288,11 +289,11 @@ const ProfilesScreen: React.FC = () => {
<Text style={[styles.modalTitle, { color: currentTheme.colors.text }]}>
Create New Profile
</Text>
<TextInput
style={[
styles.input,
{
{
backgroundColor: `${currentTheme.colors.textMuted}20`,
color: currentTheme.colors.text,
borderColor: currentTheme.colors.border
@ -304,9 +305,9 @@ const ProfilesScreen: React.FC = () => {
onChangeText={setNewProfileName}
autoFocus
/>
<View style={styles.modalButtons}>
<TouchableOpacity
<TouchableOpacity
style={[styles.modalButton, styles.cancelButton]}
onPress={() => {
setNewProfileName('');
@ -315,9 +316,9 @@ const ProfilesScreen: React.FC = () => {
>
<Text style={{ color: currentTheme.colors.textMuted }}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
<TouchableOpacity
style={[
styles.modalButton,
styles.modalButton,
styles.createButton,
{ backgroundColor: currentTheme.colors.primary }
]}

View file

@ -132,17 +132,27 @@ export const StreamsScreen = () => {
const { showSuccess, showInfo } = useToast();
// Add dimension listener and tablet detection
// Use a ref to track previous dimensions to avoid unnecessary re-renders
const [dimensions, setDimensions] = useState(Dimensions.get('window'));
const prevDimensionsRef = useRef({ width: dimensions.width, height: dimensions.height });
useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => {
setDimensions(window);
// Only update state if dimensions actually changed (with 1px tolerance)
const widthChanged = Math.abs(window.width - prevDimensionsRef.current.width) > 1;
const heightChanged = Math.abs(window.height - prevDimensionsRef.current.height) > 1;
if (widthChanged || heightChanged) {
prevDimensionsRef.current = { width: window.width, height: window.height };
setDimensions(window);
}
});
return () => subscription?.remove();
}, []);
// Memoize tablet detection to prevent recalculation on every render
const deviceWidth = dimensions.width;
const isTablet = deviceWidth >= 768;
const isTablet = useMemo(() => deviceWidth >= 768, [deviceWidth]);
// Add refs to prevent excessive updates and duplicate loads
const isMounted = useRef(true);
@ -303,6 +313,9 @@ export const StreamsScreen = () => {
}, []);
// Monitor streams loading and update available providers immediately
// Use a ref to track the previous providers to avoid unnecessary state updates
const prevProvidersRef = useRef<Set<string>>(new Set());
useEffect(() => {
// Skip processing if component is unmounting
if (!isMounted.current) return;
@ -317,14 +330,21 @@ export const StreamsScreen = () => {
if (providersWithStreams.length > 0) {
logger.log(`📊 Providers with streams: ${providersWithStreams.join(', ')}`);
const providersWithStreamsSet = new Set(providersWithStreams);
// Only update if we have new providers, don't remove existing ones during loading
setAvailableProviders(prevProviders => {
const newProviders = new Set([...prevProviders, ...providersWithStreamsSet]);
if (__DEV__) console.log('[StreamsScreen] availableProviders ->', Array.from(newProviders));
return newProviders;
});
// Check if we actually have new providers before triggering state update
const hasNewProviders = providersWithStreams.some(
provider => !prevProvidersRef.current.has(provider)
);
if (hasNewProviders) {
setAvailableProviders(prevProviders => {
const newProviders = new Set([...prevProviders, ...providersWithStreams]);
// Update ref to track current providers
prevProvidersRef.current = newProviders;
if (__DEV__) console.log('[StreamsScreen] availableProviders ->', Array.from(newProviders));
return newProviders;
});
}
}
// Update loading states for individual providers

View file

@ -36,27 +36,27 @@ const TMDB_API_KEY = '439c478a771f35c05022f9feabcca01c';
// Define example shows with their IMDB IDs and TMDB IDs
const EXAMPLE_SHOWS = [
{
name: 'Breaking Bad',
imdbId: 'tt0903747',
{
name: 'Breaking Bad',
imdbId: 'tt0903747',
tmdbId: '1396',
type: 'tv' as const
},
{
name: 'Friends',
imdbId: 'tt0108778',
{
name: 'Friends',
imdbId: 'tt0108778',
tmdbId: '1668',
type: 'tv' as const
},
{
name: 'Stranger Things',
imdbId: 'tt4574334',
{
name: 'Stranger Things',
imdbId: 'tt4574334',
tmdbId: '66732',
type: 'tv' as const
},
{
name: 'Avatar',
imdbId: 'tt0499549',
{
name: 'Avatar',
imdbId: 'tt0499549',
tmdbId: '19995',
type: 'movie' as const
},
@ -82,7 +82,7 @@ const TMDBSettingsScreen = () => {
const { settings, updateSetting } = useSettings();
const [languagePickerVisible, setLanguagePickerVisible] = useState(false);
const [languageSearch, setLanguageSearch] = useState('');
// Logo preview state
const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]);
const [tmdbLogo, setTmdbLogo] = useState<string | null>(null);
@ -126,7 +126,7 @@ const TMDBSettingsScreen = () => {
try {
const keys = await mmkvStorage.getAllKeys();
const tmdbKeys = keys.filter(key => key.startsWith('tmdb_cache_'));
let totalSize = 0;
for (const key of tmdbKeys) {
const value = mmkvStorage.getString(key);
@ -134,7 +134,7 @@ const TMDBSettingsScreen = () => {
totalSize += value.length;
}
}
// Convert to KB/MB
let sizeStr = '';
if (totalSize < 1024) {
@ -144,7 +144,7 @@ const TMDBSettingsScreen = () => {
} else {
sizeStr = `${(totalSize / (1024 * 1024)).toFixed(2)} MB`;
}
setCacheSize(sizeStr);
} catch (error) {
logger.error('[TMDBSettingsScreen] Error calculating cache size:', error);
@ -187,17 +187,17 @@ const TMDBSettingsScreen = () => {
mmkvStorage.getItem(TMDB_API_KEY_STORAGE_KEY),
mmkvStorage.getItem(USE_CUSTOM_TMDB_API_KEY)
]);
logger.log('[TMDBSettingsScreen] API key status:', savedKey ? 'Found' : 'Not found');
logger.log('[TMDBSettingsScreen] Use custom API setting:', savedUseCustomKey);
if (savedKey) {
setApiKey(savedKey);
setIsKeySet(true);
} else {
setIsKeySet(false);
}
setUseCustomKey(savedUseCustomKey === 'true');
} catch (error) {
logger.error('[TMDBSettingsScreen] Failed to load settings:', error);
@ -212,7 +212,7 @@ const TMDBSettingsScreen = () => {
const saveApiKey = async () => {
logger.log('[TMDBSettingsScreen] Starting API key save');
Keyboard.dismiss();
try {
const trimmedKey = apiKey.trim();
if (!trimmedKey) {
@ -299,27 +299,27 @@ const TMDBSettingsScreen = () => {
try {
await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, value ? 'true' : 'false');
setUseCustomKey(value);
if (!value) {
// If switching to built-in key, show confirmation
logger.log('[TMDBSettingsScreen] Switching to built-in API key');
setTestResult({
success: true,
message: 'Now using the built-in TMDb API key.'
setTestResult({
success: true,
message: 'Now using the built-in TMDb API key.'
});
} else if (apiKey && isKeySet) {
// If switching to custom key and we have a key
logger.log('[TMDBSettingsScreen] Switching to custom API key');
setTestResult({
success: true,
message: 'Now using your custom TMDb API key.'
setTestResult({
success: true,
message: 'Now using your custom TMDb API key.'
});
} else {
// If switching to custom key but don't have a key yet
logger.log('[TMDBSettingsScreen] No custom key available yet');
setTestResult({
success: false,
message: 'Please enter and save your custom TMDb API key.'
setTestResult({
success: false,
message: 'Please enter and save your custom TMDb API key.'
});
}
} catch (error) {
@ -355,27 +355,27 @@ const TMDBSettingsScreen = () => {
setLoadingLogos(true);
setTmdbLogo(null);
setTmdbBanner(null);
try {
const tmdbId = show.tmdbId;
const contentType = show.type;
logger.log(`[TMDBSettingsScreen] Fetching ${show.name} with TMDB ID: ${tmdbId}`);
const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en';
const apiKey = TMDB_API_KEY;
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}/images?api_key=${apiKey}`);
const imagesData = await response.json();
if (imagesData.logos && imagesData.logos.length > 0) {
let logoPath: string | null = null;
let logoLanguage = preferredTmdbLanguage;
// Try to find logo in preferred language
const preferredLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === preferredTmdbLanguage);
if (preferredLogo) {
logoPath = preferredLogo.file_path;
logoLanguage = preferredTmdbLanguage;
@ -383,7 +383,7 @@ const TMDBSettingsScreen = () => {
} else {
// Fallback to English
const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === 'en');
if (englishLogo) {
logoPath = englishLogo.file_path;
logoLanguage = 'en';
@ -395,7 +395,7 @@ const TMDBSettingsScreen = () => {
setIsPreviewFallback(true);
}
}
if (logoPath) {
setTmdbLogo(`https://image.tmdb.org/t/p/original${logoPath}`);
setPreviewLanguage(logoLanguage);
@ -407,7 +407,7 @@ const TMDBSettingsScreen = () => {
setPreviewLanguage('');
setIsPreviewFallback(false);
}
// Get TMDB banner (backdrop)
if (imagesData.backdrops && imagesData.backdrops.length > 0) {
const backdropPath = imagesData.backdrops[0].file_path;
@ -415,7 +415,7 @@ const TMDBSettingsScreen = () => {
} else {
const detailsResponse = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}?api_key=${apiKey}`);
const details = await detailsResponse.json();
if (details.backdrop_path) {
setTmdbBanner(`https://image.tmdb.org/t/p/original${details.backdrop_path}`);
}
@ -444,17 +444,17 @@ const TMDBSettingsScreen = () => {
</View>
);
}
return (
<View style={styles.bannerContainer}>
<FastImage
<FastImage
source={{ uri: banner || undefined }}
style={styles.bannerImage}
resizeMode={FastImage.resizeMode.cover}
/>
<View style={styles.bannerOverlay} />
{logo && (
<FastImage
<FastImage
source={{ uri: logo }}
style={styles.logoOverBanner}
resizeMode={FastImage.resizeMode.contain}
@ -491,7 +491,7 @@ const TMDBSettingsScreen = () => {
if (__DEV__) console.error('Error loading selected show:', e);
}
};
loadSelectedShow();
}, []);
@ -512,7 +512,7 @@ const TMDBSettingsScreen = () => {
}
return (
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
<StatusBar barStyle="light-content" />
<View style={[styles.headerContainer, { paddingTop: topSpacing }]}>
<View style={styles.header}>
@ -520,7 +520,7 @@ const TMDBSettingsScreen = () => {
style={styles.backButton}
onPress={() => navigation.goBack()}
>
<MaterialIcons name="chevron-left" size={28} color={currentTheme.colors.primary} />
<MaterialIcons name="chevron-left" size={28} color={currentTheme.colors.primary} />
<Text style={[styles.backText, { color: currentTheme.colors.primary }]}>Settings</Text>
</TouchableOpacity>
</View>
@ -602,7 +602,7 @@ const TMDBSettingsScreen = () => {
{/* Logo Preview */}
<View style={styles.divider} />
<Text style={[styles.settingTitle, { color: currentTheme.colors.text, marginBottom: 8 }]}>Logo Preview</Text>
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis, marginBottom: 12 }]}>
Preview shows how localized logos will appear in the selected language.
@ -610,8 +610,8 @@ const TMDBSettingsScreen = () => {
{/* Show selector */}
<Text style={[styles.selectorLabel, { color: currentTheme.colors.mediumEmphasis }]}>Example:</Text>
<ScrollView
horizontal
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.showsScrollContent}
style={styles.showsScrollView}
@ -627,7 +627,7 @@ const TMDBSettingsScreen = () => {
onPress={() => handleShowSelect(show)}
activeOpacity={0.7}
>
<Text
<Text
style={[
styles.showItemText,
{ color: currentTheme.colors.mediumEmphasis },
@ -795,7 +795,7 @@ const TMDBSettingsScreen = () => {
{/* Cache Management Section */}
<View style={styles.divider} />
<View style={styles.settingRow}>
<View style={styles.settingTextContainer}>
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Cache Size</Text>
@ -828,6 +828,7 @@ const TMDBSettingsScreen = () => {
visible={languagePickerVisible}
transparent
animationType="slide"
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => setLanguagePickerVisible(false)}
>
<TouchableWithoutFeedback onPress={() => setLanguagePickerVisible(false)}>
@ -955,42 +956,42 @@ const TMDBSettingsScreen = () => {
return (
<>
{filteredLanguages.map(({ code, label, native }) => (
<TouchableOpacity
key={code}
onPress={() => { updateSetting('tmdbLanguagePreference', code); setLanguagePickerVisible(false); }}
style={[
styles.languageItem,
settings.tmdbLanguagePreference === code && styles.selectedLanguageItem
]}
activeOpacity={0.7}
>
<View style={styles.languageContent}>
<View style={styles.languageInfo}>
<Text style={[
styles.languageName,
settings.tmdbLanguagePreference === code && styles.selectedLanguageName,
{
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.text,
}
]}>
{native}
</Text>
<Text style={[
styles.languageCode,
settings.tmdbLanguagePreference === code && styles.selectedLanguageCode,
{
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.mediumEmphasis,
}
]}>
{label} {code.toUpperCase()}
</Text>
</View>
{settings.tmdbLanguagePreference === code && (
<View style={styles.checkmarkContainer}>
<MaterialIcons name="check-circle" size={24} color={currentTheme.colors.primary} />
</View>
)}
</View>
<TouchableOpacity
key={code}
onPress={() => { updateSetting('tmdbLanguagePreference', code); setLanguagePickerVisible(false); }}
style={[
styles.languageItem,
settings.tmdbLanguagePreference === code && styles.selectedLanguageItem
]}
activeOpacity={0.7}
>
<View style={styles.languageContent}>
<View style={styles.languageInfo}>
<Text style={[
styles.languageName,
settings.tmdbLanguagePreference === code && styles.selectedLanguageName,
{
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.text,
}
]}>
{native}
</Text>
<Text style={[
styles.languageCode,
settings.tmdbLanguagePreference === code && styles.selectedLanguageCode,
{
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.mediumEmphasis,
}
]}>
{label} {code.toUpperCase()}
</Text>
</View>
{settings.tmdbLanguagePreference === code && (
<View style={styles.checkmarkContainer}>
<MaterialIcons name="check-circle" size={24} color={currentTheme.colors.primary} />
</View>
)}
</View>
</TouchableOpacity>
))}
{languageSearch.length > 0 && filteredLanguages.length === 0 && (