mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
alert orientation fix
This commit is contained in:
parent
2d5b1263b5
commit
5804959ddf
11 changed files with 242 additions and 214 deletions
|
|
@ -101,6 +101,7 @@ const AnnouncementOverlay: React.FC<AnnouncementOverlayProps> = ({
|
||||||
transparent
|
transparent
|
||||||
animationType="none"
|
animationType="none"
|
||||||
statusBarTranslucent
|
statusBarTranslucent
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={handleClose}
|
onRequestClose={handleClose}
|
||||||
>
|
>
|
||||||
<View style={styles.overlay}>
|
<View style={styles.overlay}>
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ export const DropUpMenu = ({ visible, onClose, item, onOptionSelect, isSaved: is
|
||||||
const isWatched = !!isWatchedProp;
|
const isWatched = !!isWatchedProp;
|
||||||
const inTraktWatchlist = isAuthenticated && isInWatchlist(item.id, item.type as 'movie' | 'show');
|
const inTraktWatchlist = isAuthenticated && isInWatchlist(item.id, item.type as 'movie' | 'show');
|
||||||
const inTraktCollection = isAuthenticated && isInCollection(item.id, item.type as 'movie' | 'show');
|
const inTraktCollection = isAuthenticated && isInCollection(item.id, item.type as 'movie' | 'show');
|
||||||
|
|
||||||
let menuOptions = [
|
let menuOptions = [
|
||||||
{
|
{
|
||||||
icon: 'bookmark',
|
icon: 'bookmark',
|
||||||
|
|
@ -152,6 +152,7 @@ export const DropUpMenu = ({ visible, onClose, item, onOptionSelect, isSaved: is
|
||||||
visible={visible}
|
visible={visible}
|
||||||
transparent
|
transparent
|
||||||
animationType="none"
|
animationType="none"
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
>
|
>
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||||
|
|
@ -162,7 +163,7 @@ export const DropUpMenu = ({ visible, onClose, item, onOptionSelect, isSaved: is
|
||||||
<View style={styles.dragHandle} />
|
<View style={styles.dragHandle} />
|
||||||
<View style={styles.menuHeader}>
|
<View style={styles.menuHeader}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{
|
source={{
|
||||||
uri: item.poster,
|
uri: item.poster,
|
||||||
priority: FastImage.priority.high,
|
priority: FastImage.priority.high,
|
||||||
cache: FastImage.cacheControl.immutable
|
cache: FastImage.cacheControl.immutable
|
||||||
|
|
|
||||||
|
|
@ -1660,6 +1660,7 @@ const SeriesContentComponent: React.FC<SeriesContentProps> = ({
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
onRequestClose={closeEpisodeActionMenu}
|
onRequestClose={closeEpisodeActionMenu}
|
||||||
statusBarTranslucent
|
statusBarTranslucent
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
>
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
// Enhanced responsive sizing for tablets and TV screens
|
// Enhanced responsive sizing for tablets and TV screens
|
||||||
const deviceWidth = Dimensions.get('window').width;
|
const deviceWidth = Dimensions.get('window').width;
|
||||||
const deviceHeight = Dimensions.get('window').height;
|
const deviceHeight = Dimensions.get('window').height;
|
||||||
|
|
||||||
// Determine device type based on width
|
// Determine device type based on width
|
||||||
const getDeviceType = useCallback(() => {
|
const getDeviceType = useCallback(() => {
|
||||||
if (deviceWidth >= BREAKPOINTS.tv) return 'tv';
|
if (deviceWidth >= BREAKPOINTS.tv) return 'tv';
|
||||||
|
|
@ -82,13 +82,13 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet';
|
if (deviceWidth >= BREAKPOINTS.tablet) return 'tablet';
|
||||||
return 'phone';
|
return 'phone';
|
||||||
}, [deviceWidth]);
|
}, [deviceWidth]);
|
||||||
|
|
||||||
const deviceType = getDeviceType();
|
const deviceType = getDeviceType();
|
||||||
const isTablet = deviceType === 'tablet';
|
const isTablet = deviceType === 'tablet';
|
||||||
const isLargeTablet = deviceType === 'largeTablet';
|
const isLargeTablet = deviceType === 'largeTablet';
|
||||||
const isTV = deviceType === 'tv';
|
const isTV = deviceType === 'tv';
|
||||||
const isLargeScreen = isTablet || isLargeTablet || isTV;
|
const isLargeScreen = isTablet || isLargeTablet || isTV;
|
||||||
|
|
||||||
// Enhanced spacing and padding
|
// Enhanced spacing and padding
|
||||||
const horizontalPadding = useMemo(() => {
|
const horizontalPadding = useMemo(() => {
|
||||||
switch (deviceType) {
|
switch (deviceType) {
|
||||||
|
|
@ -102,7 +102,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
return 16; // phone
|
return 16; // phone
|
||||||
}
|
}
|
||||||
}, [deviceType]);
|
}, [deviceType]);
|
||||||
|
|
||||||
// Enhanced trailer card sizing
|
// Enhanced trailer card sizing
|
||||||
const trailerCardWidth = useMemo(() => {
|
const trailerCardWidth = useMemo(() => {
|
||||||
switch (deviceType) {
|
switch (deviceType) {
|
||||||
|
|
@ -116,7 +116,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
return 200; // phone
|
return 200; // phone
|
||||||
}
|
}
|
||||||
}, [deviceType]);
|
}, [deviceType]);
|
||||||
|
|
||||||
const trailerCardSpacing = useMemo(() => {
|
const trailerCardSpacing = useMemo(() => {
|
||||||
switch (deviceType) {
|
switch (deviceType) {
|
||||||
case 'tv':
|
case 'tv':
|
||||||
|
|
@ -293,7 +293,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
// Auto-select the first available category, preferring "Trailer"
|
// Auto-select the first available category, preferring "Trailer"
|
||||||
const availableCategories = Object.keys(categorized);
|
const availableCategories = Object.keys(categorized);
|
||||||
const preferredCategory = availableCategories.includes('Trailer') ? 'Trailer' :
|
const preferredCategory = availableCategories.includes('Trailer') ? 'Trailer' :
|
||||||
availableCategories.includes('Teaser') ? 'Teaser' : availableCategories[0];
|
availableCategories.includes('Teaser') ? 'Teaser' : availableCategories[0];
|
||||||
setSelectedCategory(preferredCategory);
|
setSelectedCategory(preferredCategory);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -379,7 +379,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('TrailersSection', 'Error pausing hero trailer:', error);
|
logger.warn('TrailersSection', 'Error pausing hero trailer:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedTrailer(trailer);
|
setSelectedTrailer(trailer);
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
@ -499,15 +499,15 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[
|
<Animated.View style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
sectionAnimatedStyle,
|
sectionAnimatedStyle,
|
||||||
{ paddingHorizontal: horizontalPadding }
|
{ paddingHorizontal: horizontalPadding }
|
||||||
]}>
|
]}>
|
||||||
{/* Enhanced Header with Category Selector */}
|
{/* Enhanced Header with Category Selector */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.headerTitle,
|
styles.headerTitle,
|
||||||
{
|
{
|
||||||
color: currentTheme.colors.highEmphasis,
|
color: currentTheme.colors.highEmphasis,
|
||||||
fontSize: isTV ? 28 : isLargeTablet ? 26 : isTablet ? 24 : 20
|
fontSize: isTV ? 28 : isLargeTablet ? 26 : isTablet ? 24 : 20
|
||||||
}
|
}
|
||||||
|
|
@ -519,8 +519,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
{trailerCategories.length > 0 && selectedCategory && (
|
{trailerCategories.length > 0 && selectedCategory && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.categorySelector,
|
styles.categorySelector,
|
||||||
{
|
{
|
||||||
borderColor: 'rgba(255,255,255,0.6)',
|
borderColor: 'rgba(255,255,255,0.6)',
|
||||||
paddingHorizontal: isTV ? 14 : isLargeTablet ? 12 : isTablet ? 10 : 10,
|
paddingHorizontal: isTV ? 14 : isLargeTablet ? 12 : isTablet ? 10 : 10,
|
||||||
paddingVertical: isTV ? 8 : isLargeTablet ? 6 : isTablet ? 5 : 5,
|
paddingVertical: isTV ? 8 : isLargeTablet ? 6 : isTablet ? 5 : 5,
|
||||||
|
|
@ -533,8 +533,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.categorySelectorText,
|
styles.categorySelectorText,
|
||||||
{
|
{
|
||||||
color: currentTheme.colors.highEmphasis,
|
color: currentTheme.colors.highEmphasis,
|
||||||
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12,
|
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12,
|
||||||
maxWidth: isTV ? 150 : isLargeTablet ? 130 : isTablet ? 120 : 120
|
maxWidth: isTV ? 150 : isLargeTablet ? 130 : isTablet ? 120 : 120
|
||||||
|
|
@ -559,6 +559,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
visible={dropdownVisible}
|
visible={dropdownVisible}
|
||||||
transparent={true}
|
transparent={true}
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={() => setDropdownVisible(false)}
|
onRequestClose={() => setDropdownVisible(false)}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|
@ -587,7 +588,7 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
>
|
>
|
||||||
<View style={styles.dropdownItemContent}>
|
<View style={styles.dropdownItemContent}>
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.categoryIconContainer,
|
styles.categoryIconContainer,
|
||||||
{
|
{
|
||||||
backgroundColor: currentTheme.colors.primary + '15',
|
backgroundColor: currentTheme.colors.primary + '15',
|
||||||
width: isTV ? 36 : isLargeTablet ? 32 : isTablet ? 28 : 28,
|
width: isTV ? 36 : isLargeTablet ? 32 : isTablet ? 28 : 28,
|
||||||
|
|
@ -601,18 +602,18 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
color={currentTheme.colors.primary}
|
color={currentTheme.colors.primary}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.dropdownItemText,
|
styles.dropdownItemText,
|
||||||
{
|
{
|
||||||
color: currentTheme.colors.highEmphasis,
|
color: currentTheme.colors.highEmphasis,
|
||||||
fontSize: isTV ? 18 : isLargeTablet ? 17 : isTablet ? 16 : 16
|
fontSize: isTV ? 18 : isLargeTablet ? 17 : isTablet ? 16 : 16
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
{formatTrailerType(category)}
|
{formatTrailerType(category)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.dropdownItemCount,
|
styles.dropdownItemCount,
|
||||||
{
|
{
|
||||||
color: currentTheme.colors.textMuted,
|
color: currentTheme.colors.textMuted,
|
||||||
fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12,
|
fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 12,
|
||||||
paddingHorizontal: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8,
|
paddingHorizontal: isTV ? 10 : isLargeTablet ? 8 : isTablet ? 8 : 8,
|
||||||
|
|
@ -690,8 +691,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
<View style={styles.trailerInfoBelow}>
|
<View style={styles.trailerInfoBelow}>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.trailerTitle,
|
styles.trailerTitle,
|
||||||
{
|
{
|
||||||
color: currentTheme.colors.highEmphasis,
|
color: currentTheme.colors.highEmphasis,
|
||||||
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12,
|
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12,
|
||||||
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 18 : 16,
|
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 18 : 16,
|
||||||
|
|
@ -704,8 +705,8 @@ const TrailersSection: React.FC<TrailersSectionProps> = memo(({
|
||||||
{trailer.displayName || trailer.name}
|
{trailer.displayName || trailer.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.trailerMeta,
|
styles.trailerMeta,
|
||||||
{
|
{
|
||||||
color: currentTheme.colors.textMuted,
|
color: currentTheme.colors.textMuted,
|
||||||
fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 10
|
fontSize: isTV ? 14 : isLargeTablet ? 13 : isTablet ? 12 : 10
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,10 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
|
|
||||||
// Helper to get dynamic volume icon
|
// Helper to get dynamic volume icon
|
||||||
const getVolumeIcon = (value: number) => {
|
const getVolumeIcon = (value: number) => {
|
||||||
if (value === 0) return 'volume-off';
|
if (value === 0) return 'volume-off';
|
||||||
if (value < 0.3) return 'volume-mute';
|
if (value < 0.3) return 'volume-mute';
|
||||||
if (value < 0.6) return 'volume-down';
|
if (value < 0.6) return 'volume-down';
|
||||||
return 'volume-up';
|
return 'volume-up';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to get dynamic brightness icon
|
// Helper to get dynamic brightness icon
|
||||||
|
|
@ -3432,8 +3432,6 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
buffered={buffered}
|
buffered={buffered}
|
||||||
formatTime={formatTime}
|
formatTime={formatTime}
|
||||||
playerBackend={useVLC ? 'VLC' : 'ExoPlayer'}
|
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) */}
|
{/* Combined Volume & Brightness Gesture Indicator - NEW PILL STYLE (No Bar) */}
|
||||||
|
|
@ -3441,45 +3439,45 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
<View style={localStyles.gestureIndicatorContainer}>
|
<View style={localStyles.gestureIndicatorContainer}>
|
||||||
{/* Dynamic Icon */}
|
{/* Dynamic Icon */}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
localStyles.iconWrapper,
|
localStyles.iconWrapper,
|
||||||
{
|
{
|
||||||
// Conditional Background Color Logic
|
// Conditional Background Color Logic
|
||||||
backgroundColor: gestureControls.showVolumeOverlay && volume === 0
|
backgroundColor: gestureControls.showVolumeOverlay && volume === 0
|
||||||
? 'rgba(242, 184, 181)'
|
? 'rgba(242, 184, 181)'
|
||||||
: 'rgba(59, 59, 59)'
|
: 'rgba(59, 59, 59)'
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={
|
name={
|
||||||
gestureControls.showVolumeOverlay
|
gestureControls.showVolumeOverlay
|
||||||
? getVolumeIcon(volume)
|
? getVolumeIcon(volume)
|
||||||
: getBrightnessIcon(brightness)
|
: getBrightnessIcon(brightness)
|
||||||
}
|
}
|
||||||
size={24} // Reduced size to fit inside a 32-40px circle better
|
size={24} // Reduced size to fit inside a 32-40px circle better
|
||||||
color={
|
color={
|
||||||
gestureControls.showVolumeOverlay && volume === 0
|
gestureControls.showVolumeOverlay && volume === 0
|
||||||
? 'rgba(96, 20, 16)' // Bright RED for MUTE icon itself
|
? 'rgba(96, 20, 16)' // Bright RED for MUTE icon itself
|
||||||
: 'rgba(255, 255, 255)' // White for all other states
|
: 'rgba(255, 255, 255)' // White for all other states
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Text Label: Shows "Muted" or percentage */}
|
{/* Text Label: Shows "Muted" or percentage */}
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
localStyles.gestureText,
|
localStyles.gestureText,
|
||||||
// Conditional Text Color Logic
|
// Conditional Text Color Logic
|
||||||
gestureControls.showVolumeOverlay && volume === 0 && { color: 'rgba(242, 184, 181)' } // Light RED for "Muted"
|
gestureControls.showVolumeOverlay && volume === 0 && { color: 'rgba(242, 184, 181)' } // Light RED for "Muted"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{/* Conditional Text Content Logic */}
|
{/* Conditional Text Content Logic */}
|
||||||
{gestureControls.showVolumeOverlay && volume === 0
|
{gestureControls.showVolumeOverlay && volume === 0
|
||||||
? "Muted" // Display "Muted" when volume is 0
|
? "Muted" // Display "Muted" when volume is 0
|
||||||
: `${Math.round((gestureControls.showVolumeOverlay ? volume : brightness) * 100)}%` // Display percentage otherwise
|
: `${Math.round((gestureControls.showVolumeOverlay ? volume : brightness) * 100)}%` // Display percentage otherwise
|
||||||
}
|
}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -4067,32 +4065,32 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
// New styles for the gesture indicator
|
// New styles for the gesture indicator
|
||||||
const localStyles = StyleSheet.create({
|
const localStyles = StyleSheet.create({
|
||||||
gestureIndicatorContainer: {
|
gestureIndicatorContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '4%', // Adjust this for vertical position
|
top: '4%', // Adjust this for vertical position
|
||||||
alignSelf: 'center', // Adjust this for horizontal position
|
alignSelf: 'center', // Adjust this for horizontal position
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(25, 25, 25)', // Dark pill background
|
backgroundColor: 'rgba(25, 25, 25)', // Dark pill background
|
||||||
borderRadius: 70,
|
borderRadius: 70,
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
paddingVertical: 15,
|
paddingVertical: 15,
|
||||||
zIndex: 2000, // Very high z-index to ensure visibility
|
zIndex: 2000, // Very high z-index to ensure visibility
|
||||||
minWidth: 120, // Adjusted min width since bar is removed
|
minWidth: 120, // Adjusted min width since bar is removed
|
||||||
},
|
},
|
||||||
iconWrapper: {
|
iconWrapper: {
|
||||||
borderRadius: 50, // Makes it a perfect circle (set to a high number)
|
borderRadius: 50, // Makes it a perfect circle (set to a high number)
|
||||||
width: 40, // Define the diameter of the circle
|
width: 40, // Define the diameter of the circle
|
||||||
height: 40, // Define the diameter of the circle
|
height: 40, // Define the diameter of the circle
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginRight: 12, // Margin to separate icon circle from percentage text
|
marginRight: 12, // Margin to separate icon circle from percentage text
|
||||||
},
|
},
|
||||||
gestureText: {
|
gestureText: {
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
minWidth: 35,
|
minWidth: 35,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1413,6 +1413,7 @@ const AddonsScreen = () => {
|
||||||
visible={showConfirmModal}
|
visible={showConfirmModal}
|
||||||
transparent
|
transparent
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
setShowConfirmModal(false);
|
setShowConfirmModal(false);
|
||||||
setAddonDetails(null);
|
setAddonDetails(null);
|
||||||
|
|
|
||||||
|
|
@ -685,6 +685,7 @@ const CatalogSettingsScreen = () => {
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
transparent={true}
|
transparent={true}
|
||||||
visible={isRenameModalVisible}
|
visible={isRenameModalVisible}
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
setIsRenameModalVisible(false);
|
setIsRenameModalVisible(false);
|
||||||
setCatalogToRename(null);
|
setCatalogToRename(null);
|
||||||
|
|
|
||||||
|
|
@ -1946,6 +1946,7 @@ const PluginsScreen: React.FC = () => {
|
||||||
visible={showHelpModal}
|
visible={showHelpModal}
|
||||||
transparent={true}
|
transparent={true}
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={() => setShowHelpModal(false)}
|
onRequestClose={() => setShowHelpModal(false)}
|
||||||
>
|
>
|
||||||
<View style={styles.modalOverlay}>
|
<View style={styles.modalOverlay}>
|
||||||
|
|
@ -1978,6 +1979,7 @@ const PluginsScreen: React.FC = () => {
|
||||||
visible={showAddRepositoryModal}
|
visible={showAddRepositoryModal}
|
||||||
transparent={true}
|
transparent={true}
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={() => setShowAddRepositoryModal(false)}
|
onRequestClose={() => setShowAddRepositoryModal(false)}
|
||||||
>
|
>
|
||||||
<View style={styles.modalOverlay}>
|
<View style={styles.modalOverlay}>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
|
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
|
||||||
|
|
||||||
const [profiles, setProfiles] = useState<Profile[]>([]);
|
const [profiles, setProfiles] = useState<Profile[]>([]);
|
||||||
const [showAddModal, setShowAddModal] = useState(false);
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
const [newProfileName, setNewProfileName] = useState('');
|
const [newProfileName, setNewProfileName] = useState('');
|
||||||
|
|
@ -52,7 +52,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
) => {
|
) => {
|
||||||
setAlertTitle(title);
|
setAlertTitle(title);
|
||||||
setAlertMessage(message);
|
setAlertMessage(message);
|
||||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
|
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => { } }]);
|
||||||
setAlertVisible(true);
|
setAlertVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [navigation, refreshAuthStatus, isAuthenticated, loadProfiles]);
|
}, [navigation, refreshAuthStatus, isAuthenticated, loadProfiles]);
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
navigation.goBack();
|
navigation.goBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadProfiles();
|
loadProfiles();
|
||||||
}, [isAuthenticated, loadProfiles, navigation]);
|
}, [isAuthenticated, loadProfiles, navigation]);
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
...profile,
|
...profile,
|
||||||
isActive: profile.id === id
|
isActive: profile.id === id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setProfiles(updatedProfiles);
|
setProfiles(updatedProfiles);
|
||||||
saveProfiles(updatedProfiles);
|
saveProfiles(updatedProfiles);
|
||||||
}, [profiles, saveProfiles]);
|
}, [profiles, saveProfiles]);
|
||||||
|
|
@ -164,14 +164,14 @@ const ProfilesScreen: React.FC = () => {
|
||||||
'Delete Profile',
|
'Delete Profile',
|
||||||
'Are you sure you want to delete this profile? This action cannot be undone.',
|
'Are you sure you want to delete this profile? This action cannot be undone.',
|
||||||
[
|
[
|
||||||
{ label: 'Cancel', onPress: () => {} },
|
{ label: 'Cancel', onPress: () => { } },
|
||||||
{
|
{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
const updatedProfiles = profiles.filter(profile => profile.id !== id);
|
const updatedProfiles = profiles.filter(profile => profile.id !== id);
|
||||||
setProfiles(updatedProfiles);
|
setProfiles(updatedProfiles);
|
||||||
saveProfiles(updatedProfiles);
|
saveProfiles(updatedProfiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
@ -183,10 +183,10 @@ const ProfilesScreen: React.FC = () => {
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: Profile }) => (
|
const renderItem = ({ item }: { item: Profile }) => (
|
||||||
<View style={styles.profileItem}>
|
<View style={styles.profileItem}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.profileContent,
|
styles.profileContent,
|
||||||
item.isActive && {
|
item.isActive && {
|
||||||
backgroundColor: `${currentTheme.colors.primary}30`,
|
backgroundColor: `${currentTheme.colors.primary}30`,
|
||||||
borderColor: currentTheme.colors.primary
|
borderColor: currentTheme.colors.primary
|
||||||
}
|
}
|
||||||
|
|
@ -194,10 +194,10 @@ const ProfilesScreen: React.FC = () => {
|
||||||
onPress={() => handleSelectProfile(item.id)}
|
onPress={() => handleSelectProfile(item.id)}
|
||||||
>
|
>
|
||||||
<View style={styles.avatarContainer}>
|
<View style={styles.avatarContainer}>
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name="account-circle"
|
name="account-circle"
|
||||||
size={40}
|
size={40}
|
||||||
color={item.isActive ? currentTheme.colors.primary : currentTheme.colors.text}
|
color={item.isActive ? currentTheme.colors.primary : currentTheme.colors.text}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.profileInfo}>
|
<View style={styles.profileInfo}>
|
||||||
|
|
@ -211,7 +211,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{!item.isActive && (
|
{!item.isActive && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.deleteButton}
|
style={styles.deleteButton}
|
||||||
onPress={() => handleDeleteProfile(item.id)}
|
onPress={() => handleDeleteProfile(item.id)}
|
||||||
>
|
>
|
||||||
|
|
@ -225,7 +225,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
<SafeAreaView style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||||
<StatusBar barStyle="light-content" backgroundColor="transparent" translucent />
|
<StatusBar barStyle="light-content" backgroundColor="transparent" translucent />
|
||||||
|
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={handleBack}
|
onPress={handleBack}
|
||||||
|
|
@ -281,6 +281,7 @@ const ProfilesScreen: React.FC = () => {
|
||||||
visible={showAddModal}
|
visible={showAddModal}
|
||||||
transparent
|
transparent
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={() => setShowAddModal(false)}
|
onRequestClose={() => setShowAddModal(false)}
|
||||||
>
|
>
|
||||||
<View style={styles.modalOverlay}>
|
<View style={styles.modalOverlay}>
|
||||||
|
|
@ -288,11 +289,11 @@ const ProfilesScreen: React.FC = () => {
|
||||||
<Text style={[styles.modalTitle, { color: currentTheme.colors.text }]}>
|
<Text style={[styles.modalTitle, { color: currentTheme.colors.text }]}>
|
||||||
Create New Profile
|
Create New Profile
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[
|
style={[
|
||||||
styles.input,
|
styles.input,
|
||||||
{
|
{
|
||||||
backgroundColor: `${currentTheme.colors.textMuted}20`,
|
backgroundColor: `${currentTheme.colors.textMuted}20`,
|
||||||
color: currentTheme.colors.text,
|
color: currentTheme.colors.text,
|
||||||
borderColor: currentTheme.colors.border
|
borderColor: currentTheme.colors.border
|
||||||
|
|
@ -304,9 +305,9 @@ const ProfilesScreen: React.FC = () => {
|
||||||
onChangeText={setNewProfileName}
|
onChangeText={setNewProfileName}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={styles.modalButtons}>
|
<View style={styles.modalButtons}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.modalButton, styles.cancelButton]}
|
style={[styles.modalButton, styles.cancelButton]}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setNewProfileName('');
|
setNewProfileName('');
|
||||||
|
|
@ -315,9 +316,9 @@ const ProfilesScreen: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Text style={{ color: currentTheme.colors.textMuted }}>Cancel</Text>
|
<Text style={{ color: currentTheme.colors.textMuted }}>Cancel</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.modalButton,
|
styles.modalButton,
|
||||||
styles.createButton,
|
styles.createButton,
|
||||||
{ backgroundColor: currentTheme.colors.primary }
|
{ backgroundColor: currentTheme.colors.primary }
|
||||||
]}
|
]}
|
||||||
|
|
|
||||||
|
|
@ -132,17 +132,27 @@ export const StreamsScreen = () => {
|
||||||
const { showSuccess, showInfo } = useToast();
|
const { showSuccess, showInfo } = useToast();
|
||||||
|
|
||||||
// Add dimension listener and tablet detection
|
// 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 [dimensions, setDimensions] = useState(Dimensions.get('window'));
|
||||||
|
const prevDimensionsRef = useRef({ width: dimensions.width, height: dimensions.height });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = Dimensions.addEventListener('change', ({ window }) => {
|
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();
|
return () => subscription?.remove();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Memoize tablet detection to prevent recalculation on every render
|
||||||
const deviceWidth = dimensions.width;
|
const deviceWidth = dimensions.width;
|
||||||
const isTablet = deviceWidth >= 768;
|
const isTablet = useMemo(() => deviceWidth >= 768, [deviceWidth]);
|
||||||
|
|
||||||
// Add refs to prevent excessive updates and duplicate loads
|
// Add refs to prevent excessive updates and duplicate loads
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
|
|
@ -303,6 +313,9 @@ export const StreamsScreen = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Monitor streams loading and update available providers immediately
|
// 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(() => {
|
useEffect(() => {
|
||||||
// Skip processing if component is unmounting
|
// Skip processing if component is unmounting
|
||||||
if (!isMounted.current) return;
|
if (!isMounted.current) return;
|
||||||
|
|
@ -317,14 +330,21 @@ export const StreamsScreen = () => {
|
||||||
|
|
||||||
if (providersWithStreams.length > 0) {
|
if (providersWithStreams.length > 0) {
|
||||||
logger.log(`📊 Providers with streams: ${providersWithStreams.join(', ')}`);
|
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
|
// Check if we actually have new providers before triggering state update
|
||||||
setAvailableProviders(prevProviders => {
|
const hasNewProviders = providersWithStreams.some(
|
||||||
const newProviders = new Set([...prevProviders, ...providersWithStreamsSet]);
|
provider => !prevProvidersRef.current.has(provider)
|
||||||
if (__DEV__) console.log('[StreamsScreen] availableProviders ->', Array.from(newProviders));
|
);
|
||||||
return newProviders;
|
|
||||||
});
|
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
|
// Update loading states for individual providers
|
||||||
|
|
|
||||||
|
|
@ -36,27 +36,27 @@ const TMDB_API_KEY = '439c478a771f35c05022f9feabcca01c';
|
||||||
|
|
||||||
// Define example shows with their IMDB IDs and TMDB IDs
|
// Define example shows with their IMDB IDs and TMDB IDs
|
||||||
const EXAMPLE_SHOWS = [
|
const EXAMPLE_SHOWS = [
|
||||||
{
|
{
|
||||||
name: 'Breaking Bad',
|
name: 'Breaking Bad',
|
||||||
imdbId: 'tt0903747',
|
imdbId: 'tt0903747',
|
||||||
tmdbId: '1396',
|
tmdbId: '1396',
|
||||||
type: 'tv' as const
|
type: 'tv' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Friends',
|
name: 'Friends',
|
||||||
imdbId: 'tt0108778',
|
imdbId: 'tt0108778',
|
||||||
tmdbId: '1668',
|
tmdbId: '1668',
|
||||||
type: 'tv' as const
|
type: 'tv' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Stranger Things',
|
name: 'Stranger Things',
|
||||||
imdbId: 'tt4574334',
|
imdbId: 'tt4574334',
|
||||||
tmdbId: '66732',
|
tmdbId: '66732',
|
||||||
type: 'tv' as const
|
type: 'tv' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Avatar',
|
name: 'Avatar',
|
||||||
imdbId: 'tt0499549',
|
imdbId: 'tt0499549',
|
||||||
tmdbId: '19995',
|
tmdbId: '19995',
|
||||||
type: 'movie' as const
|
type: 'movie' as const
|
||||||
},
|
},
|
||||||
|
|
@ -82,7 +82,7 @@ const TMDBSettingsScreen = () => {
|
||||||
const { settings, updateSetting } = useSettings();
|
const { settings, updateSetting } = useSettings();
|
||||||
const [languagePickerVisible, setLanguagePickerVisible] = useState(false);
|
const [languagePickerVisible, setLanguagePickerVisible] = useState(false);
|
||||||
const [languageSearch, setLanguageSearch] = useState('');
|
const [languageSearch, setLanguageSearch] = useState('');
|
||||||
|
|
||||||
// Logo preview state
|
// Logo preview state
|
||||||
const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]);
|
const [selectedShow, setSelectedShow] = useState(EXAMPLE_SHOWS[0]);
|
||||||
const [tmdbLogo, setTmdbLogo] = useState<string | null>(null);
|
const [tmdbLogo, setTmdbLogo] = useState<string | null>(null);
|
||||||
|
|
@ -126,7 +126,7 @@ const TMDBSettingsScreen = () => {
|
||||||
try {
|
try {
|
||||||
const keys = await mmkvStorage.getAllKeys();
|
const keys = await mmkvStorage.getAllKeys();
|
||||||
const tmdbKeys = keys.filter(key => key.startsWith('tmdb_cache_'));
|
const tmdbKeys = keys.filter(key => key.startsWith('tmdb_cache_'));
|
||||||
|
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
for (const key of tmdbKeys) {
|
for (const key of tmdbKeys) {
|
||||||
const value = mmkvStorage.getString(key);
|
const value = mmkvStorage.getString(key);
|
||||||
|
|
@ -134,7 +134,7 @@ const TMDBSettingsScreen = () => {
|
||||||
totalSize += value.length;
|
totalSize += value.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to KB/MB
|
// Convert to KB/MB
|
||||||
let sizeStr = '';
|
let sizeStr = '';
|
||||||
if (totalSize < 1024) {
|
if (totalSize < 1024) {
|
||||||
|
|
@ -144,7 +144,7 @@ const TMDBSettingsScreen = () => {
|
||||||
} else {
|
} else {
|
||||||
sizeStr = `${(totalSize / (1024 * 1024)).toFixed(2)} MB`;
|
sizeStr = `${(totalSize / (1024 * 1024)).toFixed(2)} MB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCacheSize(sizeStr);
|
setCacheSize(sizeStr);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[TMDBSettingsScreen] Error calculating cache size:', error);
|
logger.error('[TMDBSettingsScreen] Error calculating cache size:', error);
|
||||||
|
|
@ -187,17 +187,17 @@ const TMDBSettingsScreen = () => {
|
||||||
mmkvStorage.getItem(TMDB_API_KEY_STORAGE_KEY),
|
mmkvStorage.getItem(TMDB_API_KEY_STORAGE_KEY),
|
||||||
mmkvStorage.getItem(USE_CUSTOM_TMDB_API_KEY)
|
mmkvStorage.getItem(USE_CUSTOM_TMDB_API_KEY)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
logger.log('[TMDBSettingsScreen] API key status:', savedKey ? 'Found' : 'Not found');
|
logger.log('[TMDBSettingsScreen] API key status:', savedKey ? 'Found' : 'Not found');
|
||||||
logger.log('[TMDBSettingsScreen] Use custom API setting:', savedUseCustomKey);
|
logger.log('[TMDBSettingsScreen] Use custom API setting:', savedUseCustomKey);
|
||||||
|
|
||||||
if (savedKey) {
|
if (savedKey) {
|
||||||
setApiKey(savedKey);
|
setApiKey(savedKey);
|
||||||
setIsKeySet(true);
|
setIsKeySet(true);
|
||||||
} else {
|
} else {
|
||||||
setIsKeySet(false);
|
setIsKeySet(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setUseCustomKey(savedUseCustomKey === 'true');
|
setUseCustomKey(savedUseCustomKey === 'true');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[TMDBSettingsScreen] Failed to load settings:', error);
|
logger.error('[TMDBSettingsScreen] Failed to load settings:', error);
|
||||||
|
|
@ -212,7 +212,7 @@ const TMDBSettingsScreen = () => {
|
||||||
const saveApiKey = async () => {
|
const saveApiKey = async () => {
|
||||||
logger.log('[TMDBSettingsScreen] Starting API key save');
|
logger.log('[TMDBSettingsScreen] Starting API key save');
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const trimmedKey = apiKey.trim();
|
const trimmedKey = apiKey.trim();
|
||||||
if (!trimmedKey) {
|
if (!trimmedKey) {
|
||||||
|
|
@ -299,27 +299,27 @@ const TMDBSettingsScreen = () => {
|
||||||
try {
|
try {
|
||||||
await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, value ? 'true' : 'false');
|
await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, value ? 'true' : 'false');
|
||||||
setUseCustomKey(value);
|
setUseCustomKey(value);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// If switching to built-in key, show confirmation
|
// If switching to built-in key, show confirmation
|
||||||
logger.log('[TMDBSettingsScreen] Switching to built-in API key');
|
logger.log('[TMDBSettingsScreen] Switching to built-in API key');
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Now using the built-in TMDb API key.'
|
message: 'Now using the built-in TMDb API key.'
|
||||||
});
|
});
|
||||||
} else if (apiKey && isKeySet) {
|
} else if (apiKey && isKeySet) {
|
||||||
// If switching to custom key and we have a key
|
// If switching to custom key and we have a key
|
||||||
logger.log('[TMDBSettingsScreen] Switching to custom API key');
|
logger.log('[TMDBSettingsScreen] Switching to custom API key');
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Now using your custom TMDb API key.'
|
message: 'Now using your custom TMDb API key.'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// If switching to custom key but don't have a key yet
|
// If switching to custom key but don't have a key yet
|
||||||
logger.log('[TMDBSettingsScreen] No custom key available yet');
|
logger.log('[TMDBSettingsScreen] No custom key available yet');
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Please enter and save your custom TMDb API key.'
|
message: 'Please enter and save your custom TMDb API key.'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -355,27 +355,27 @@ const TMDBSettingsScreen = () => {
|
||||||
setLoadingLogos(true);
|
setLoadingLogos(true);
|
||||||
setTmdbLogo(null);
|
setTmdbLogo(null);
|
||||||
setTmdbBanner(null);
|
setTmdbBanner(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tmdbId = show.tmdbId;
|
const tmdbId = show.tmdbId;
|
||||||
const contentType = show.type;
|
const contentType = show.type;
|
||||||
|
|
||||||
logger.log(`[TMDBSettingsScreen] Fetching ${show.name} with TMDB ID: ${tmdbId}`);
|
logger.log(`[TMDBSettingsScreen] Fetching ${show.name} with TMDB ID: ${tmdbId}`);
|
||||||
|
|
||||||
const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en';
|
const preferredTmdbLanguage = settings.tmdbLanguagePreference || 'en';
|
||||||
|
|
||||||
const apiKey = TMDB_API_KEY;
|
const apiKey = TMDB_API_KEY;
|
||||||
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
|
const endpoint = contentType === 'tv' ? 'tv' : 'movie';
|
||||||
const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}/images?api_key=${apiKey}`);
|
const response = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}/images?api_key=${apiKey}`);
|
||||||
const imagesData = await response.json();
|
const imagesData = await response.json();
|
||||||
|
|
||||||
if (imagesData.logos && imagesData.logos.length > 0) {
|
if (imagesData.logos && imagesData.logos.length > 0) {
|
||||||
let logoPath: string | null = null;
|
let logoPath: string | null = null;
|
||||||
let logoLanguage = preferredTmdbLanguage;
|
let logoLanguage = preferredTmdbLanguage;
|
||||||
|
|
||||||
// Try to find logo in preferred language
|
// 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);
|
const preferredLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === preferredTmdbLanguage);
|
||||||
|
|
||||||
if (preferredLogo) {
|
if (preferredLogo) {
|
||||||
logoPath = preferredLogo.file_path;
|
logoPath = preferredLogo.file_path;
|
||||||
logoLanguage = preferredTmdbLanguage;
|
logoLanguage = preferredTmdbLanguage;
|
||||||
|
|
@ -383,7 +383,7 @@ const TMDBSettingsScreen = () => {
|
||||||
} else {
|
} else {
|
||||||
// Fallback to English
|
// Fallback to English
|
||||||
const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === 'en');
|
const englishLogo = imagesData.logos.find((logo: { iso_639_1: string; file_path: string }) => logo.iso_639_1 === 'en');
|
||||||
|
|
||||||
if (englishLogo) {
|
if (englishLogo) {
|
||||||
logoPath = englishLogo.file_path;
|
logoPath = englishLogo.file_path;
|
||||||
logoLanguage = 'en';
|
logoLanguage = 'en';
|
||||||
|
|
@ -395,7 +395,7 @@ const TMDBSettingsScreen = () => {
|
||||||
setIsPreviewFallback(true);
|
setIsPreviewFallback(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logoPath) {
|
if (logoPath) {
|
||||||
setTmdbLogo(`https://image.tmdb.org/t/p/original${logoPath}`);
|
setTmdbLogo(`https://image.tmdb.org/t/p/original${logoPath}`);
|
||||||
setPreviewLanguage(logoLanguage);
|
setPreviewLanguage(logoLanguage);
|
||||||
|
|
@ -407,7 +407,7 @@ const TMDBSettingsScreen = () => {
|
||||||
setPreviewLanguage('');
|
setPreviewLanguage('');
|
||||||
setIsPreviewFallback(false);
|
setIsPreviewFallback(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get TMDB banner (backdrop)
|
// Get TMDB banner (backdrop)
|
||||||
if (imagesData.backdrops && imagesData.backdrops.length > 0) {
|
if (imagesData.backdrops && imagesData.backdrops.length > 0) {
|
||||||
const backdropPath = imagesData.backdrops[0].file_path;
|
const backdropPath = imagesData.backdrops[0].file_path;
|
||||||
|
|
@ -415,7 +415,7 @@ const TMDBSettingsScreen = () => {
|
||||||
} else {
|
} else {
|
||||||
const detailsResponse = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}?api_key=${apiKey}`);
|
const detailsResponse = await fetch(`https://api.themoviedb.org/3/${endpoint}/${tmdbId}?api_key=${apiKey}`);
|
||||||
const details = await detailsResponse.json();
|
const details = await detailsResponse.json();
|
||||||
|
|
||||||
if (details.backdrop_path) {
|
if (details.backdrop_path) {
|
||||||
setTmdbBanner(`https://image.tmdb.org/t/p/original${details.backdrop_path}`);
|
setTmdbBanner(`https://image.tmdb.org/t/p/original${details.backdrop_path}`);
|
||||||
}
|
}
|
||||||
|
|
@ -444,17 +444,17 @@ const TMDBSettingsScreen = () => {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.bannerContainer}>
|
<View style={styles.bannerContainer}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: banner || undefined }}
|
source={{ uri: banner || undefined }}
|
||||||
style={styles.bannerImage}
|
style={styles.bannerImage}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
<View style={styles.bannerOverlay} />
|
<View style={styles.bannerOverlay} />
|
||||||
{logo && (
|
{logo && (
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: logo }}
|
source={{ uri: logo }}
|
||||||
style={styles.logoOverBanner}
|
style={styles.logoOverBanner}
|
||||||
resizeMode={FastImage.resizeMode.contain}
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
|
|
@ -491,7 +491,7 @@ const TMDBSettingsScreen = () => {
|
||||||
if (__DEV__) console.error('Error loading selected show:', e);
|
if (__DEV__) console.error('Error loading selected show:', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadSelectedShow();
|
loadSelectedShow();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -512,7 +512,7 @@ const TMDBSettingsScreen = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||||
<StatusBar barStyle="light-content" />
|
<StatusBar barStyle="light-content" />
|
||||||
<View style={[styles.headerContainer, { paddingTop: topSpacing }]}>
|
<View style={[styles.headerContainer, { paddingTop: topSpacing }]}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
|
|
@ -520,7 +520,7 @@ const TMDBSettingsScreen = () => {
|
||||||
style={styles.backButton}
|
style={styles.backButton}
|
||||||
onPress={() => navigation.goBack()}
|
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>
|
<Text style={[styles.backText, { color: currentTheme.colors.primary }]}>Settings</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -602,7 +602,7 @@ const TMDBSettingsScreen = () => {
|
||||||
|
|
||||||
{/* Logo Preview */}
|
{/* Logo Preview */}
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
|
|
||||||
<Text style={[styles.settingTitle, { color: currentTheme.colors.text, marginBottom: 8 }]}>Logo Preview</Text>
|
<Text style={[styles.settingTitle, { color: currentTheme.colors.text, marginBottom: 8 }]}>Logo Preview</Text>
|
||||||
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis, marginBottom: 12 }]}>
|
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis, marginBottom: 12 }]}>
|
||||||
Preview shows how localized logos will appear in the selected language.
|
Preview shows how localized logos will appear in the selected language.
|
||||||
|
|
@ -610,8 +610,8 @@ const TMDBSettingsScreen = () => {
|
||||||
|
|
||||||
{/* Show selector */}
|
{/* Show selector */}
|
||||||
<Text style={[styles.selectorLabel, { color: currentTheme.colors.mediumEmphasis }]}>Example:</Text>
|
<Text style={[styles.selectorLabel, { color: currentTheme.colors.mediumEmphasis }]}>Example:</Text>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
horizontal
|
horizontal
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
contentContainerStyle={styles.showsScrollContent}
|
contentContainerStyle={styles.showsScrollContent}
|
||||||
style={styles.showsScrollView}
|
style={styles.showsScrollView}
|
||||||
|
|
@ -627,7 +627,7 @@ const TMDBSettingsScreen = () => {
|
||||||
onPress={() => handleShowSelect(show)}
|
onPress={() => handleShowSelect(show)}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.showItemText,
|
styles.showItemText,
|
||||||
{ color: currentTheme.colors.mediumEmphasis },
|
{ color: currentTheme.colors.mediumEmphasis },
|
||||||
|
|
@ -795,7 +795,7 @@ const TMDBSettingsScreen = () => {
|
||||||
|
|
||||||
{/* Cache Management Section */}
|
{/* Cache Management Section */}
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
|
|
||||||
<View style={styles.settingRow}>
|
<View style={styles.settingRow}>
|
||||||
<View style={styles.settingTextContainer}>
|
<View style={styles.settingTextContainer}>
|
||||||
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Cache Size</Text>
|
<Text style={[styles.settingTitle, { color: currentTheme.colors.text }]}>Cache Size</Text>
|
||||||
|
|
@ -828,6 +828,7 @@ const TMDBSettingsScreen = () => {
|
||||||
visible={languagePickerVisible}
|
visible={languagePickerVisible}
|
||||||
transparent
|
transparent
|
||||||
animationType="slide"
|
animationType="slide"
|
||||||
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
onRequestClose={() => setLanguagePickerVisible(false)}
|
onRequestClose={() => setLanguagePickerVisible(false)}
|
||||||
>
|
>
|
||||||
<TouchableWithoutFeedback onPress={() => setLanguagePickerVisible(false)}>
|
<TouchableWithoutFeedback onPress={() => setLanguagePickerVisible(false)}>
|
||||||
|
|
@ -955,42 +956,42 @@ const TMDBSettingsScreen = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{filteredLanguages.map(({ code, label, native }) => (
|
{filteredLanguages.map(({ code, label, native }) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={code}
|
key={code}
|
||||||
onPress={() => { updateSetting('tmdbLanguagePreference', code); setLanguagePickerVisible(false); }}
|
onPress={() => { updateSetting('tmdbLanguagePreference', code); setLanguagePickerVisible(false); }}
|
||||||
style={[
|
style={[
|
||||||
styles.languageItem,
|
styles.languageItem,
|
||||||
settings.tmdbLanguagePreference === code && styles.selectedLanguageItem
|
settings.tmdbLanguagePreference === code && styles.selectedLanguageItem
|
||||||
]}
|
]}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<View style={styles.languageContent}>
|
<View style={styles.languageContent}>
|
||||||
<View style={styles.languageInfo}>
|
<View style={styles.languageInfo}>
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.languageName,
|
styles.languageName,
|
||||||
settings.tmdbLanguagePreference === code && styles.selectedLanguageName,
|
settings.tmdbLanguagePreference === code && styles.selectedLanguageName,
|
||||||
{
|
{
|
||||||
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.text,
|
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.text,
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
{native}
|
{native}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.languageCode,
|
styles.languageCode,
|
||||||
settings.tmdbLanguagePreference === code && styles.selectedLanguageCode,
|
settings.tmdbLanguagePreference === code && styles.selectedLanguageCode,
|
||||||
{
|
{
|
||||||
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.mediumEmphasis,
|
color: settings.tmdbLanguagePreference === code ? currentTheme.colors.primary : currentTheme.colors.mediumEmphasis,
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
{label} • {code.toUpperCase()}
|
{label} • {code.toUpperCase()}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{settings.tmdbLanguagePreference === code && (
|
{settings.tmdbLanguagePreference === code && (
|
||||||
<View style={styles.checkmarkContainer}>
|
<View style={styles.checkmarkContainer}>
|
||||||
<MaterialIcons name="check-circle" size={24} color={currentTheme.colors.primary} />
|
<MaterialIcons name="check-circle" size={24} color={currentTheme.colors.primary} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
{languageSearch.length > 0 && filteredLanguages.length === 0 && (
|
{languageSearch.length > 0 && filteredLanguages.length === 0 && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue