diff --git a/src/screens/HomeScreenSettings.tsx b/src/screens/HomeScreenSettings.tsx
index 3bfb199..ea31486 100644
--- a/src/screens/HomeScreenSettings.tsx
+++ b/src/screens/HomeScreenSettings.tsx
@@ -339,19 +339,21 @@ const HomeScreenSettings: React.FC = () => {
{settings.showHeroSection && (
<>
-
- Hero Layout
- handleUpdateSetting('heroStyle', val as any)}
- />
- Full-width banner, swipeable cards, or Apple TV style
-
+ {!isTabletDevice && (
+
+ Hero Layout
+ handleUpdateSetting('heroStyle', val as any)}
+ />
+ Full-width banner, swipeable cards, or Apple TV style
+
+ )}
Featured Source
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index 29893ac..5a34f06 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -2,21 +2,16 @@ import { useFocusEffect } from '@react-navigation/native';
import React, { useCallback, useState, useEffect, useRef } from 'react';
import { useRealtimeConfig } from '../hooks/useRealtimeConfig';
-
import {
View,
Text,
StyleSheet,
TouchableOpacity,
- Switch,
ScrollView,
- SafeAreaView,
StatusBar,
Platform,
Dimensions,
- Button,
Linking,
- Clipboard
} from 'react-native';
import { mmkvStorage } from '../services/mmkvStorage';
import { useNavigation } from '@react-navigation/native';
@@ -24,32 +19,32 @@ import { NavigationProp } from '@react-navigation/native';
import FastImage from '@d11/react-native-fast-image';
import LottieView from 'lottie-react-native';
import { Feather } from '@expo/vector-icons';
-import { Picker } from '@react-native-picker/picker';
import { useSettings, DEFAULT_SETTINGS } from '../hooks/useSettings';
import { RootStackParamList } from '../navigation/AppNavigator';
import { stremioService } from '../services/stremioService';
import { useCatalogContext } from '../contexts/CatalogContext';
import { useTraktContext } from '../contexts/TraktContext';
import { useTheme } from '../contexts/ThemeContext';
-import { catalogService } from '../services/catalogService';
import { fetchTotalDownloads } from '../services/githubReleaseService';
import * as WebBrowser from 'expo-web-browser';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import * as Sentry from '@sentry/react-native';
import { getDisplayedAppVersion } from '../utils/version';
import CustomAlert from '../components/CustomAlert';
import ScreenHeader from '../components/common/ScreenHeader';
-import PluginIcon from '../components/icons/PluginIcon';
import TraktIcon from '../components/icons/TraktIcon';
-import TMDBIcon from '../components/icons/TMDBIcon';
-import MDBListIcon from '../components/icons/MDBListIcon';
import { campaignService } from '../services/campaignService';
import { useScrollToTop } from '../contexts/ScrollToTopContext';
-const { width, height } = Dimensions.get('window');
-const isTablet = width >= 768;
+// Import reusable content components from settings screens
+import { PlaybackSettingsContent } from './settings/PlaybackSettingsScreen';
+import { ContentDiscoverySettingsContent } from './settings/ContentDiscoverySettingsScreen';
+import { AppearanceSettingsContent } from './settings/AppearanceSettingsScreen';
+import { IntegrationsSettingsContent } from './settings/IntegrationsSettingsScreen';
+import { AboutSettingsContent, AboutFooter } from './settings/AboutSettingsScreen';
+import { SettingsCard, SettingItem, ChevronRight, CustomSwitch } from './settings/SettingsComponents';
-const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
+const { width } = Dimensions.get('window');
+const isTablet = width >= 768;
// Settings categories for tablet sidebar
const SETTINGS_CATEGORIES = [
@@ -57,7 +52,6 @@ const SETTINGS_CATEGORIES = [
{ id: 'content', title: 'Content & Discovery', icon: 'compass' as string },
{ id: 'appearance', title: 'Appearance', icon: 'sliders' as string },
{ id: 'integrations', title: 'Integrations', icon: 'layers' as string },
- { id: 'ai', title: 'AI Assistant', icon: 'cpu' as string },
{ id: 'playback', title: 'Playback', icon: 'play-circle' as string },
{ id: 'backup', title: 'Backup & Restore', icon: 'archive' as string },
{ id: 'updates', title: 'Updates', icon: 'refresh-ccw' as string },
@@ -66,134 +60,6 @@ const SETTINGS_CATEGORIES = [
{ id: 'cache', title: 'Cache', icon: 'database' as string },
];
-// Card component with minimalistic style
-interface SettingsCardProps {
- children: React.ReactNode;
- title?: string;
- isTablet?: boolean;
-}
-
-const SettingsCard: React.FC = ({ children, title, isTablet = false }) => {
- const { currentTheme } = useTheme();
-
- return (
-
- {title && (
-
- {title}
-
- )}
-
- {children}
-
-
- );
-};
-
-interface SettingItemProps {
- title: string;
- description?: string;
- icon?: string;
- customIcon?: React.ReactNode;
- renderControl?: () => React.ReactNode;
- isLast?: boolean;
- onPress?: () => void;
- badge?: string | number;
- isTablet?: boolean;
-}
-
-const SettingItem: React.FC = ({
- title,
- description,
- icon,
- customIcon,
- renderControl,
- isLast = false,
- onPress,
- badge,
- isTablet = false
-}) => {
- const { currentTheme } = useTheme();
-
- return (
-
-
- {customIcon ? (
- customIcon
- ) : (
-
- )}
-
-
-
-
- {title}
-
- {description && (
-
- {description}
-
- )}
-
- {badge && (
-
- {String(badge)}
-
- )}
-
- {renderControl && (
-
- {renderControl()}
-
- )}
-
- );
-};
-
// Tablet Sidebar Component
interface SidebarProps {
selectedCategory: string;
@@ -306,6 +172,7 @@ const SettingsScreen: React.FC = () => {
})();
return () => { mounted = false; };
}, []);
+
const navigation = useNavigation>();
const { lastUpdate } = useCatalogContext();
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
@@ -316,13 +183,9 @@ const SettingsScreen: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState('account');
// States for dynamic content
- const [addonCount, setAddonCount] = useState(0);
- const [catalogCount, setCatalogCount] = useState(0);
const [mdblistKeySet, setMdblistKeySet] = useState(false);
- const [openRouterKeySet, setOpenRouterKeySet] = useState(false);
const [totalDownloads, setTotalDownloads] = useState(0);
const [displayDownloads, setDisplayDownloads] = useState(null);
- const [isCountingUp, setIsCountingUp] = useState(false);
// Use Realtime Config Hook
const settingsConfig = useRealtimeConfig();
@@ -338,91 +201,45 @@ const SettingsScreen: React.FC = () => {
useScrollToTop('Settings', scrollToTop);
- // Add a useEffect to check Trakt authentication status on focus
+ // Refresh Trakt auth status on focus
useEffect(() => {
- // This will reload the Trakt auth status whenever the settings screen is focused
const unsubscribe = navigation.addListener('focus', () => {
- // Force a re-render when returning to this screen
- // This will reflect the updated isAuthenticated state from the TraktContext
- // Refresh auth status
- if (isAuthenticated || userProfile) {
- // Just to be cautious, log the current state
- if (__DEV__) console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
- }
refreshAuthStatus();
});
-
return unsubscribe;
- }, [navigation, isAuthenticated, userProfile, refreshAuthStatus]);
+ }, [navigation, refreshAuthStatus]);
const loadData = useCallback(async () => {
try {
- // Load addon count and get their catalogs
- const addons = await stremioService.getInstalledAddonsAsync();
- setAddonCount(addons.length);
-
- // Count total available catalogs
- let totalCatalogs = 0;
- addons.forEach(addon => {
- if (addon.catalogs && addon.catalogs.length > 0) {
- totalCatalogs += addon.catalogs.length;
- }
- });
-
- // Load saved catalog settings
- const catalogSettingsJson = await mmkvStorage.getItem('catalog_settings');
- if (catalogSettingsJson) {
- const catalogSettings = JSON.parse(catalogSettingsJson);
- // Filter out _lastUpdate key and count only explicitly disabled catalogs
- const disabledCount = Object.entries(catalogSettings)
- .filter(([key, value]) => key !== '_lastUpdate' && value === false)
- .length;
- // Since catalogs are enabled by default, subtract disabled ones from total
- setCatalogCount(totalCatalogs - disabledCount);
- } else {
- // If no settings saved, all catalogs are enabled by default
- setCatalogCount(totalCatalogs);
- }
-
// Check MDBList API key status
const mdblistKey = await mmkvStorage.getItem('mdblist_api_key');
setMdblistKeySet(!!mdblistKey);
- // Check OpenRouter API key status
- const openRouterKey = await mmkvStorage.getItem('openrouter_api_key');
- setOpenRouterKeySet(!!openRouterKey);
-
- // Load GitHub total downloads (initial load only, polling happens in useEffect)
+ // Load GitHub total downloads
const downloads = await fetchTotalDownloads();
if (downloads !== null) {
setTotalDownloads(downloads);
setDisplayDownloads(downloads);
}
-
} catch (error) {
if (__DEV__) console.error('Error loading settings data:', error);
}
}, []);
- // Load data initially and when catalogs are updated
useEffect(() => {
loadData();
}, [loadData, lastUpdate]);
- // Add focus listener to reload data when screen comes into focus
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
loadData();
});
-
return unsubscribe;
}, [navigation, loadData]);
- // Poll GitHub downloads every 10 seconds when on the About section
+ // Poll GitHub downloads
useEffect(() => {
- // Only poll when viewing the About section (where downloads counter is shown)
const shouldPoll = isTablet ? selectedCategory === 'about' : true;
-
if (!shouldPoll) return;
const pollInterval = setInterval(async () => {
@@ -434,28 +251,25 @@ const SettingsScreen: React.FC = () => {
} catch (error) {
if (__DEV__) console.error('Error polling downloads:', error);
}
- }, 3600000); // 3600000 milliseconds (1 hour)
+ }, 3600000);
return () => clearInterval(pollInterval);
- }, [selectedCategory, isTablet, totalDownloads]);
+ }, [selectedCategory, totalDownloads]);
// Animate counting up when totalDownloads changes
useEffect(() => {
if (totalDownloads === null || displayDownloads === null) return;
if (totalDownloads === displayDownloads) return;
- setIsCountingUp(true);
const start = displayDownloads;
const end = totalDownloads;
- const duration = 2000; // 2 seconds animation
+ const duration = 2000;
const startTime = Date.now();
const animate = () => {
const now = Date.now();
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
-
- // Ease out quad for smooth deceleration
const easeProgress = 1 - Math.pow(1 - progress, 2);
const current = Math.floor(start + (end - start) * easeProgress);
@@ -465,31 +279,12 @@ const SettingsScreen: React.FC = () => {
requestAnimationFrame(animate);
} else {
setDisplayDownloads(end);
- setIsCountingUp(false);
}
};
requestAnimationFrame(animate);
}, [totalDownloads]);
- const handleResetSettings = useCallback(() => {
- openAlert(
- 'Reset Settings',
- 'Are you sure you want to reset all settings to default values?',
- [
- { label: 'Cancel', onPress: () => { } },
- {
- label: 'Reset',
- onPress: () => {
- (Object.keys(DEFAULT_SETTINGS) as Array).forEach(key => {
- updateSetting(key, DEFAULT_SETTINGS[key]);
- });
- }
- }
- ]
- );
- }, [updateSetting]);
-
const handleClearMDBListCache = () => {
openAlert(
'Clear MDBList Cache',
@@ -512,24 +307,6 @@ const SettingsScreen: React.FC = () => {
);
};
- const CustomSwitch = ({ value, onValueChange }: { value: boolean, onValueChange: (value: boolean) => void }) => (
-
- );
-
- const ChevronRight = () => (
-
- );
-
// Helper to check item visibility
const isItemVisible = (itemId: string) => {
if (!settingsConfig?.items) return true;
@@ -546,8 +323,7 @@ const SettingsScreen: React.FC = () => {
return true;
});
-
-
+ // Render tablet category content using reusable components
const renderCategoryContent = (categoryId: string) => {
switch (categoryId) {
case 'account':
@@ -558,7 +334,7 @@ const SettingsScreen: React.FC = () => {
title="Trakt"
description={isAuthenticated ? `@${userProfile?.username || 'User'}` : "Sign in to sync"}
customIcon={}
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('TraktSettings')}
isLast={true}
isTablet={isTablet}
@@ -568,272 +344,19 @@ const SettingsScreen: React.FC = () => {
);
case 'content':
- return (
-
- {isItemVisible('addons') && (
- navigation.navigate('Addons')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('debrid') && (
- navigation.navigate('DebridIntegration')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('plugins') && (
- }
- renderControl={ChevronRight}
- onPress={() => navigation.navigate('ScraperSettings')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('catalogs') && (
- navigation.navigate('CatalogSettings')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('home_screen') && (
- navigation.navigate('HomeScreenSettings')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('show_discover') && (
- (
- updateSetting('showDiscover', value)}
- />
- )}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('continue_watching') && (
- navigation.navigate('ContinueWatchingSettings')}
- isLast={true}
- isTablet={isTablet}
- />
- )}
-
- );
+ return ;
case 'appearance':
- return (
-
- {isItemVisible('theme') && (
- navigation.navigate('ThemeSettings')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('episode_layout') && (
- (
- updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
- />
- )}
- isLast={isTablet}
- isTablet={isTablet}
- />
- )}
- {!isTablet && isItemVisible('streams_backdrop') && (
- (
- updateSetting('enableStreamsBackdrop', value)}
- />
- )}
- isLast={true}
- isTablet={isTablet}
- />
- )}
-
- );
+ return ;
case 'integrations':
- return (
-
- {isItemVisible('mdblist') && (
- }
- renderControl={ChevronRight}
- onPress={() => navigation.navigate('MDBListSettings')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('tmdb') && (
- }
- renderControl={ChevronRight}
- onPress={() => navigation.navigate('TMDBSettings')}
- isLast={true}
- isTablet={isTablet}
- />
- )}
-
- );
-
- case 'ai':
- return (
-
- {isItemVisible('openrouter') && (
- navigation.navigate('AISettings')}
- isLast={true}
- isTablet={isTablet}
- />
- )}
-
- );
+ return ;
case 'playback':
- return (
-
- {isItemVisible('video_player') && (
- navigation.navigate('PlayerSettings')}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('show_trailers') && (
- (
- updateSetting('showTrailers', value)}
- trackColor={{ false: 'rgba(255,255,255,0.2)', true: currentTheme.colors.primary }}
- thumbColor={settings?.showTrailers ? '#fff' : '#f4f3f4'}
- />
- )}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('enable_downloads') && (
- (
- updateSetting('enableDownloads', value)}
- trackColor={{ false: 'rgba(255,255,255,0.2)', true: currentTheme.colors.primary }}
- thumbColor={settings?.enableDownloads ? '#fff' : '#f4f3f4'}
- />
- )}
- isTablet={isTablet}
- />
- )}
- {isItemVisible('notifications') && (
- navigation.navigate('NotificationSettings')}
- isLast={true}
- isTablet={isTablet}
- />
- )}
-
- );
+ return ;
case 'about':
- return (
-
- Linking.openURL('https://tapframe.github.io/NuvioStreaming/#privacy-policy')}
- renderControl={ChevronRight}
- isTablet={isTablet}
- />
- Sentry.showFeedbackWidget()}
- renderControl={ChevronRight}
- isTablet={isTablet}
- />
-
- navigation.navigate('Contributors')}
- isLast={true}
- isTablet={isTablet}
- />
-
- );
+ return ;
case 'developer':
return __DEV__ ? (
@@ -842,7 +365,7 @@ const SettingsScreen: React.FC = () => {
title="Test Onboarding"
icon="play-circle"
onPress={() => navigation.navigate('Onboarding')}
- renderControl={ChevronRight}
+ renderControl={() => }
isTablet={isTablet}
/>
{
openAlert('Error', 'Failed to reset onboarding.');
}
}}
- renderControl={ChevronRight}
+ renderControl={() => }
isTablet={isTablet}
/>
{
openAlert('Error', 'Failed to reset announcement.');
}
}}
- renderControl={ChevronRight}
+ renderControl={() => }
isTablet={isTablet}
/>
{
await campaignService.resetCampaigns();
openAlert('Success', 'Campaign history reset. Restart app to see posters again.');
}}
- renderControl={ChevronRight}
+ renderControl={() => }
isTablet={isTablet}
/>
{
title="Backup & Restore"
description="Create and restore app backups"
icon="archive"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('Backup')}
isLast={true}
isTablet={isTablet}
@@ -949,7 +472,7 @@ const SettingsScreen: React.FC = () => {
title="App Updates"
description="Check for updates and manage app version"
icon="refresh-ccw"
- renderControl={ChevronRight}
+ renderControl={() => }
badge={Platform.OS === 'android' && hasUpdateBadge ? 1 : undefined}
onPress={async () => {
if (Platform.OS === 'android') {
@@ -969,15 +492,13 @@ const SettingsScreen: React.FC = () => {
}
};
- // Keep headers below floating top navigator on tablets by adding extra offset
+ // Keep headers below floating top navigator on tablets
const tabletNavOffset = isTablet ? 64 : 0;
+ // TABLET LAYOUT
if (isTablet) {
return (
-
+
{
{renderCategoryContent(selectedCategory)}
{selectedCategory === 'about' && (
- <>
- {displayDownloads !== null && (
-
-
- {displayDownloads.toLocaleString()}
-
-
- downloads and counting
-
-
- )}
-
-
- WebBrowser.openBrowserAsync('https://ko-fi.com/tapframe', {
- presentationStyle: Platform.OS === 'ios' ? WebBrowser.WebBrowserPresentationStyle.FORM_SHEET : WebBrowser.WebBrowserPresentationStyle.FORM_SHEET
- })}
- activeOpacity={0.7}
- >
-
-
-
-
- Linking.openURL('https://discord.gg/KVgDTjhA4H')}
- activeOpacity={0.7}
- >
-
-
-
- Discord
-
-
-
-
- Linking.openURL('https://www.reddit.com/r/Nuvio/')}
- activeOpacity={0.7}
- >
-
-
-
- Reddit
-
-
-
-
-
-
- {/* Monkey Animation */}
-
-
-
-
-
-
-
-
-
-
- Made with ❤️ by Tapframe and Friends
-
-
- >
+
)}
@@ -1107,18 +540,12 @@ const SettingsScreen: React.FC = () => {
);
}
- // Mobile Layout - Simplified navigation hub
+ // MOBILE LAYOUT - Simplified navigation hub
return (
-
+
-
+
-
{
title="Trakt"
description={isAuthenticated ? `@${userProfile?.username || 'User'}` : "Sign in to sync"}
customIcon={}
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('TraktSettings')}
isLast
/>
@@ -1155,7 +582,7 @@ const SettingsScreen: React.FC = () => {
title="Content & Discovery"
description="Addons, catalogs, and sources"
icon="compass"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('ContentDiscoverySettings')}
/>
)}
@@ -1164,7 +591,7 @@ const SettingsScreen: React.FC = () => {
title="Appearance"
description={currentTheme.name}
icon="sliders"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('AppearanceSettings')}
/>
)}
@@ -1173,7 +600,7 @@ const SettingsScreen: React.FC = () => {
title="Integrations"
description="MDBList, TMDB, AI"
icon="layers"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('IntegrationsSettings')}
/>
)}
@@ -1182,7 +609,7 @@ const SettingsScreen: React.FC = () => {
title="Playback"
description="Player, trailers, downloads"
icon="play-circle"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('PlaybackSettings')}
isLast
/>
@@ -1201,7 +628,7 @@ const SettingsScreen: React.FC = () => {
title="Backup & Restore"
description="Create and restore app backups"
icon="archive"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('Backup')}
/>
)}
@@ -1211,7 +638,7 @@ const SettingsScreen: React.FC = () => {
description="Check for updates"
icon="refresh-ccw"
badge={Platform.OS === 'android' && hasUpdateBadge ? 1 : undefined}
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={async () => {
if (Platform.OS === 'android') {
try { await mmkvStorage.removeItem('@update_badge_pending'); } catch { }
@@ -1243,7 +670,7 @@ const SettingsScreen: React.FC = () => {
title="About Nuvio"
description={getDisplayedAppVersion()}
icon="info"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('AboutSettings')}
isLast
/>
@@ -1256,7 +683,7 @@ const SettingsScreen: React.FC = () => {
title="Developer Tools"
description="Testing and debug options"
icon="code"
- renderControl={ChevronRight}
+ renderControl={() => }
onPress={() => navigation.navigate('DeveloperSettings')}
isLast
/>
@@ -1388,7 +815,6 @@ const styles = StyleSheet.create({
paddingTop: 8,
paddingBottom: 32,
},
-
// Tablet-specific styles
tabletContainer: {
flex: 1,
@@ -1449,139 +875,7 @@ const styles = StyleSheet.create({
paddingTop: 8,
paddingBottom: 40,
},
-
- // Common card styles
- cardContainer: {
- width: '100%',
- marginBottom: 24,
- },
- tabletCardContainer: {
- marginBottom: 28,
- },
- cardTitle: {
- fontSize: 12,
- fontWeight: '600',
- letterSpacing: 1,
- marginLeft: Math.max(16, width * 0.045),
- marginBottom: 10,
- textTransform: 'uppercase',
- },
- tabletCardTitle: {
- fontSize: 12,
- marginLeft: 4,
- marginBottom: 12,
- },
- card: {
- marginHorizontal: Math.max(16, width * 0.04),
- borderRadius: 14,
- overflow: 'hidden',
- width: undefined,
- },
- tabletCard: {
- marginHorizontal: 0,
- borderRadius: 16,
- },
- settingItem: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingVertical: 14,
- paddingHorizontal: Math.max(14, width * 0.04),
- borderBottomWidth: StyleSheet.hairlineWidth,
- minHeight: Math.max(60, width * 0.15),
- width: '100%',
- },
- tabletSettingItem: {
- paddingVertical: 16,
- paddingHorizontal: 20,
- minHeight: 68,
- },
- settingItemBorder: {
- // Border styling handled directly in the component with borderBottomWidth
- },
- settingIconContainer: {
- marginRight: 14,
- width: 38,
- height: 38,
- borderRadius: 10,
- alignItems: 'center',
- justifyContent: 'center',
- },
- tabletSettingIconContainer: {
- width: 42,
- height: 42,
- borderRadius: 11,
- marginRight: 16,
- },
- settingContent: {
- flex: 1,
- flexDirection: 'row',
- alignItems: 'center',
- },
- settingTextContainer: {
- flex: 1,
- },
- settingTitle: {
- fontSize: Math.min(16, width * 0.04),
- fontWeight: '500',
- marginBottom: 2,
- letterSpacing: -0.2,
- },
- tabletSettingTitle: {
- fontSize: 17,
- fontWeight: '500',
- marginBottom: 3,
- },
- settingDescription: {
- fontSize: Math.min(13, width * 0.034),
- opacity: 0.7,
- },
- tabletSettingDescription: {
- fontSize: 14,
- opacity: 0.6,
- },
- settingControl: {
- justifyContent: 'center',
- alignItems: 'center',
- paddingLeft: 10,
- },
- badge: {
- height: 20,
- minWidth: 20,
- borderRadius: 10,
- alignItems: 'center',
- justifyContent: 'center',
- paddingHorizontal: 6,
- marginRight: 8,
- },
- badgeText: {
- color: 'white',
- fontSize: 11,
- fontWeight: '700',
- },
- segmentedControl: {
- flexDirection: 'row',
- backgroundColor: 'rgba(255,255,255,0.08)',
- borderRadius: 8,
- padding: 2,
- },
- segment: {
- paddingHorizontal: 16,
- paddingVertical: 8,
- borderRadius: 6,
- minWidth: 60,
- alignItems: 'center',
- },
- segmentActive: {
- backgroundColor: 'rgba(255,255,255,0.16)',
- },
- segmentText: {
- fontSize: 13,
- fontWeight: '500',
- },
- segmentTextActive: {
- color: 'white',
- fontWeight: '600',
- },
+ // Footer and social styles
footer: {
alignItems: 'center',
justifyContent: 'center',
@@ -1593,7 +887,6 @@ const styles = StyleSheet.create({
opacity: 0.5,
letterSpacing: 0.2,
},
- // Support buttons
discordContainer: {
marginTop: 12,
marginBottom: 24,
@@ -1643,14 +936,6 @@ const styles = StyleSheet.create({
letterSpacing: 1.5,
textTransform: 'uppercase',
},
- loadingSpinner: {
- width: 16,
- height: 16,
- borderWidth: 2,
- borderRadius: 8,
- borderTopColor: 'transparent',
- marginRight: 8,
- },
monkeyContainer: {
alignItems: 'center',
justifyContent: 'center',
diff --git a/src/screens/settings/AboutSettingsScreen.tsx b/src/screens/settings/AboutSettingsScreen.tsx
index f5b0e56..35a8324 100644
--- a/src/screens/settings/AboutSettingsScreen.tsx
+++ b/src/screens/settings/AboutSettingsScreen.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
-import { View, Text, StyleSheet, ScrollView, StatusBar, TouchableOpacity, Platform, Linking } from 'react-native';
+import { View, Text, StyleSheet, ScrollView, StatusBar, TouchableOpacity, Platform, Linking, Dimensions } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -14,53 +14,186 @@ import { getDisplayedAppVersion } from '../../utils/version';
import ScreenHeader from '../../components/common/ScreenHeader';
import { SettingsCard, SettingItem, ChevronRight } from './SettingsComponents';
+const { width } = Dimensions.get('window');
+
+interface AboutSettingsContentProps {
+ isTablet?: boolean;
+ displayDownloads?: number | null;
+}
+
+/**
+ * Reusable AboutSettingsContent component
+ * Can be used inline (tablets) or wrapped in a screen (mobile)
+ */
+export const AboutSettingsContent: React.FC = ({
+ isTablet = false,
+ displayDownloads: externalDisplayDownloads
+}) => {
+ const navigation = useNavigation>();
+ const { currentTheme } = useTheme();
+
+ const [internalDisplayDownloads, setInternalDisplayDownloads] = useState(null);
+
+ // Use external downloads if provided (for tablet inline use), otherwise load internally
+ const displayDownloads = externalDisplayDownloads ?? internalDisplayDownloads;
+
+ useEffect(() => {
+ // Only load downloads internally if not provided externally
+ if (externalDisplayDownloads === undefined) {
+ const loadDownloads = async () => {
+ const downloads = await fetchTotalDownloads();
+ if (downloads !== null) {
+ setInternalDisplayDownloads(downloads);
+ }
+ };
+ loadDownloads();
+ }
+ }, [externalDisplayDownloads]);
+
+ return (
+ <>
+
+ Linking.openURL('https://tapframe.github.io/NuvioStreaming/#privacy-policy')}
+ renderControl={() => }
+ isTablet={isTablet}
+ />
+ Sentry.showFeedbackWidget()}
+ renderControl={() => }
+ isTablet={isTablet}
+ />
+
+ }
+ onPress={() => navigation.navigate('Contributors')}
+ isLast
+ isTablet={isTablet}
+ />
+
+ >
+ );
+};
+
+/**
+ * Reusable AboutFooter component - Downloads counter, social links, branding
+ */
+export const AboutFooter: React.FC<{ displayDownloads: number | null }> = ({ displayDownloads }) => {
+ const { currentTheme } = useTheme();
+
+ return (
+ <>
+ {displayDownloads !== null && (
+
+
+ {displayDownloads.toLocaleString()}
+
+
+ downloads and counting
+
+
+ )}
+
+
+ WebBrowser.openBrowserAsync('https://ko-fi.com/tapframe', {
+ presentationStyle: Platform.OS === 'ios' ? WebBrowser.WebBrowserPresentationStyle.FORM_SHEET : WebBrowser.WebBrowserPresentationStyle.FORM_SHEET
+ })}
+ activeOpacity={0.7}
+ >
+
+
+
+
+ Linking.openURL('https://discord.gg/KVgDTjhA4H')}
+ activeOpacity={0.7}
+ >
+
+
+
+ Discord
+
+
+
+
+ Linking.openURL('https://www.reddit.com/r/Nuvio/')}
+ activeOpacity={0.7}
+ >
+
+
+
+ Reddit
+
+
+
+
+
+
+ {/* Monkey Animation */}
+
+
+
+
+
+
+
+
+
+
+ Made with ❤️ by Tapframe and Friends
+
+
+ >
+ );
+};
+
+/**
+ * AboutSettingsScreen - Wrapper for mobile navigation
+ */
const AboutSettingsScreen: React.FC = () => {
const navigation = useNavigation>();
const { currentTheme } = useTheme();
const insets = useSafeAreaInsets();
-
- const [totalDownloads, setTotalDownloads] = useState(null);
- const [displayDownloads, setDisplayDownloads] = useState(null);
-
- useEffect(() => {
- const loadDownloads = async () => {
- const downloads = await fetchTotalDownloads();
- if (downloads !== null) {
- setTotalDownloads(downloads);
- setDisplayDownloads(downloads);
- }
- };
- loadDownloads();
- }, []);
-
- // Animate counting up when totalDownloads changes
- useEffect(() => {
- if (totalDownloads === null || displayDownloads === null) return;
- if (totalDownloads === displayDownloads) return;
-
- const start = displayDownloads;
- const end = totalDownloads;
- const duration = 2000;
- const startTime = Date.now();
-
- const animate = () => {
- const now = Date.now();
- const elapsed = now - startTime;
- const progress = Math.min(elapsed / duration, 1);
- const easeProgress = 1 - Math.pow(1 - progress, 2);
- const current = Math.floor(start + (end - start) * easeProgress);
-
- setDisplayDownloads(current);
-
- if (progress < 1) {
- requestAnimationFrame(animate);
- } else {
- setDisplayDownloads(end);
- }
- };
-
- requestAnimationFrame(animate);
- }, [totalDownloads]);
+ const screenIsTablet = width >= 768;
return (
@@ -72,34 +205,7 @@ const AboutSettingsScreen: React.FC = () => {
showsVerticalScrollIndicator={false}
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 40 }]}
>
-
- Linking.openURL('https://tapframe.github.io/NuvioStreaming/#privacy-policy')}
- renderControl={() => }
- />
- Sentry.showFeedbackWidget()}
- renderControl={() => }
- />
-
- }
- onPress={() => navigation.navigate('Contributors')}
- isLast
- />
-
-
+
diff --git a/src/screens/settings/AppearanceSettingsScreen.tsx b/src/screens/settings/AppearanceSettingsScreen.tsx
index 5a67e02..5c1d002 100644
--- a/src/screens/settings/AppearanceSettingsScreen.tsx
+++ b/src/screens/settings/AppearanceSettingsScreen.tsx
@@ -1,6 +1,6 @@
-import React, { useState, useCallback } from 'react';
-import { View, StyleSheet, ScrollView, StatusBar, Platform, Dimensions } from 'react-native';
-import { useNavigation, useFocusEffect } from '@react-navigation/native';
+import React from 'react';
+import { View, StyleSheet, ScrollView, StatusBar, Dimensions } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from '../../contexts/ThemeContext';
@@ -11,13 +11,19 @@ import { SettingsCard, SettingItem, CustomSwitch, ChevronRight } from './Setting
import { useRealtimeConfig } from '../../hooks/useRealtimeConfig';
const { width } = Dimensions.get('window');
-const isTablet = width >= 768;
-const AppearanceSettingsScreen: React.FC = () => {
+interface AppearanceSettingsContentProps {
+ isTablet?: boolean;
+}
+
+/**
+ * Reusable AppearanceSettingsContent component
+ * Can be used inline (tablets) or wrapped in a screen (mobile)
+ */
+export const AppearanceSettingsContent: React.FC = ({ isTablet = false }) => {
const navigation = useNavigation>();
const { currentTheme } = useTheme();
const { settings, updateSetting } = useSettings();
- const insets = useSafeAreaInsets();
const config = useRealtimeConfig();
const isItemVisible = (itemId: string) => {
@@ -34,6 +40,71 @@ const AppearanceSettingsScreen: React.FC = () => {
});
};
+ return (
+ <>
+ {hasVisibleItems(['theme']) && (
+
+ {isItemVisible('theme') && (
+ }
+ onPress={() => navigation.navigate('ThemeSettings')}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+
+ {hasVisibleItems(['episode_layout', 'streams_backdrop']) && (
+
+ {isItemVisible('episode_layout') && (
+ (
+ updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
+ />
+ )}
+ isLast={isTablet || !isItemVisible('streams_backdrop')}
+ isTablet={isTablet}
+ />
+ )}
+ {!isTablet && isItemVisible('streams_backdrop') && (
+ (
+ updateSetting('enableStreamsBackdrop', value)}
+ />
+ )}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+ >
+ );
+};
+
+/**
+ * AppearanceSettingsScreen - Wrapper for mobile navigation
+ */
+const AppearanceSettingsScreen: React.FC = () => {
+ const navigation = useNavigation>();
+ const { currentTheme } = useTheme();
+ const insets = useSafeAreaInsets();
+ const screenIsTablet = width >= 768;
+
return (
@@ -44,53 +115,7 @@ const AppearanceSettingsScreen: React.FC = () => {
showsVerticalScrollIndicator={false}
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
>
- {hasVisibleItems(['theme']) && (
-
- {isItemVisible('theme') && (
- }
- onPress={() => navigation.navigate('ThemeSettings')}
- isLast
- />
- )}
-
- )}
-
- {hasVisibleItems(['episode_layout', 'streams_backdrop']) && (
-
- {isItemVisible('episode_layout') && (
- (
- updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
- />
- )}
- isLast={isTablet || !isItemVisible('streams_backdrop')}
- />
- )}
- {!isTablet && isItemVisible('streams_backdrop') && (
- (
- updateSetting('enableStreamsBackdrop', value)}
- />
- )}
- isLast
- />
- )}
-
- )}
+
);
diff --git a/src/screens/settings/ContentDiscoverySettingsScreen.tsx b/src/screens/settings/ContentDiscoverySettingsScreen.tsx
index a928799..9ef8c71 100644
--- a/src/screens/settings/ContentDiscoverySettingsScreen.tsx
+++ b/src/screens/settings/ContentDiscoverySettingsScreen.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react';
-import { View, StyleSheet, ScrollView, StatusBar, Platform } from 'react-native';
+import { View, StyleSheet, ScrollView, StatusBar, Platform, Dimensions } from 'react-native';
import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -13,15 +13,24 @@ import PluginIcon from '../../components/icons/PluginIcon';
import { SettingsCard, SettingItem, CustomSwitch, ChevronRight } from './SettingsComponents';
import { useRealtimeConfig } from '../../hooks/useRealtimeConfig';
-const ContentDiscoverySettingsScreen: React.FC = () => {
+const { width } = Dimensions.get('window');
+
+interface ContentDiscoverySettingsContentProps {
+ isTablet?: boolean;
+}
+
+/**
+ * Reusable ContentDiscoverySettingsContent component
+ * Can be used inline (tablets) or wrapped in a screen (mobile)
+ */
+export const ContentDiscoverySettingsContent: React.FC = ({ isTablet = false }) => {
const navigation = useNavigation>();
const { currentTheme } = useTheme();
const { settings, updateSetting } = useSettings();
- const insets = useSafeAreaInsets();
+ const config = useRealtimeConfig();
const [addonCount, setAddonCount] = useState(0);
const [catalogCount, setCatalogCount] = useState(0);
- const config = useRealtimeConfig();
const loadData = useCallback(async () => {
try {
@@ -67,6 +76,112 @@ const ContentDiscoverySettingsScreen: React.FC = () => {
return itemIds.some(id => isItemVisible(id));
};
+ return (
+ <>
+ {hasVisibleItems(['addons', 'debrid', 'plugins']) && (
+
+ {isItemVisible('addons') && (
+ }
+ onPress={() => navigation.navigate('Addons')}
+ isTablet={isTablet}
+ />
+ )}
+ {isItemVisible('debrid') && (
+ }
+ onPress={() => navigation.navigate('DebridIntegration')}
+ isTablet={isTablet}
+ />
+ )}
+ {isItemVisible('plugins') && (
+ }
+ renderControl={() => }
+ onPress={() => navigation.navigate('ScraperSettings')}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+
+ {hasVisibleItems(['catalogs', 'home_screen', 'continue_watching']) && (
+
+ {isItemVisible('catalogs') && (
+ }
+ onPress={() => navigation.navigate('CatalogSettings')}
+ isTablet={isTablet}
+ />
+ )}
+ {isItemVisible('home_screen') && (
+ }
+ onPress={() => navigation.navigate('HomeScreenSettings')}
+ isTablet={isTablet}
+ />
+ )}
+ {isItemVisible('continue_watching') && (
+ }
+ onPress={() => navigation.navigate('ContinueWatchingSettings')}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+
+ {hasVisibleItems(['show_discover']) && (
+
+ {isItemVisible('show_discover') && (
+ (
+ updateSetting('showDiscover', value)}
+ />
+ )}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+ >
+ );
+};
+
+/**
+ * ContentDiscoverySettingsScreen - Wrapper for mobile navigation
+ */
+const ContentDiscoverySettingsScreen: React.FC = () => {
+ const navigation = useNavigation>();
+ const { currentTheme } = useTheme();
+ const insets = useSafeAreaInsets();
+ const screenIsTablet = width >= 768;
+
return (
@@ -77,90 +192,7 @@ const ContentDiscoverySettingsScreen: React.FC = () => {
showsVerticalScrollIndicator={false}
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
>
- {hasVisibleItems(['addons', 'debrid', 'plugins']) && (
-
- {isItemVisible('addons') && (
- }
- onPress={() => navigation.navigate('Addons')}
- />
- )}
- {isItemVisible('debrid') && (
- }
- onPress={() => navigation.navigate('DebridIntegration')}
- />
- )}
- {isItemVisible('plugins') && (
- }
- renderControl={() => }
- onPress={() => navigation.navigate('ScraperSettings')}
- isLast
- />
- )}
-
- )}
-
- {hasVisibleItems(['catalogs', 'home_screen', 'continue_watching']) && (
-
- {isItemVisible('catalogs') && (
- }
- onPress={() => navigation.navigate('CatalogSettings')}
- />
- )}
- {isItemVisible('home_screen') && (
- }
- onPress={() => navigation.navigate('HomeScreenSettings')}
- />
- )}
- {isItemVisible('continue_watching') && (
- }
- onPress={() => navigation.navigate('ContinueWatchingSettings')}
- isLast
- />
- )}
-
- )}
-
- {hasVisibleItems(['show_discover']) && (
-
- {isItemVisible('show_discover') && (
- (
- updateSetting('showDiscover', value)}
- />
- )}
- isLast
- />
- )}
-
- )}
+
);
diff --git a/src/screens/settings/IntegrationsSettingsScreen.tsx b/src/screens/settings/IntegrationsSettingsScreen.tsx
index 887caee..6af002f 100644
--- a/src/screens/settings/IntegrationsSettingsScreen.tsx
+++ b/src/screens/settings/IntegrationsSettingsScreen.tsx
@@ -1,5 +1,5 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { View, StyleSheet, ScrollView, StatusBar } from 'react-native';
+import React, { useState, useCallback } from 'react';
+import { View, StyleSheet, ScrollView, StatusBar, Dimensions } from 'react-native';
import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -12,14 +12,23 @@ import TMDBIcon from '../../components/icons/TMDBIcon';
import { SettingsCard, SettingItem, ChevronRight } from './SettingsComponents';
import { useRealtimeConfig } from '../../hooks/useRealtimeConfig';
-const IntegrationsSettingsScreen: React.FC = () => {
+const { width } = Dimensions.get('window');
+
+interface IntegrationsSettingsContentProps {
+ isTablet?: boolean;
+}
+
+/**
+ * Reusable IntegrationsSettingsContent component
+ * Can be used inline (tablets) or wrapped in a screen (mobile)
+ */
+export const IntegrationsSettingsContent: React.FC = ({ isTablet = false }) => {
const navigation = useNavigation>();
const { currentTheme } = useTheme();
- const insets = useSafeAreaInsets();
+ const config = useRealtimeConfig();
const [mdblistKeySet, setMdblistKeySet] = useState(false);
const [openRouterKeySet, setOpenRouterKeySet] = useState(false);
- const config = useRealtimeConfig();
const loadData = useCallback(async () => {
try {
@@ -50,6 +59,62 @@ const IntegrationsSettingsScreen: React.FC = () => {
return itemIds.some(id => isItemVisible(id));
};
+ return (
+ <>
+ {hasVisibleItems(['mdblist', 'tmdb']) && (
+
+ {isItemVisible('mdblist') && (
+ }
+ renderControl={() => }
+ onPress={() => navigation.navigate('MDBListSettings')}
+ isTablet={isTablet}
+ />
+ )}
+ {isItemVisible('tmdb') && (
+ }
+ renderControl={() => }
+ onPress={() => navigation.navigate('TMDBSettings')}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+
+ {hasVisibleItems(['openrouter']) && (
+
+ {isItemVisible('openrouter') && (
+ }
+ onPress={() => navigation.navigate('AISettings')}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+ >
+ );
+};
+
+/**
+ * IntegrationsSettingsScreen - Wrapper for mobile navigation
+ */
+const IntegrationsSettingsScreen: React.FC = () => {
+ const navigation = useNavigation>();
+ const { currentTheme } = useTheme();
+ const insets = useSafeAreaInsets();
+ const screenIsTablet = width >= 768;
+
return (
@@ -60,44 +125,7 @@ const IntegrationsSettingsScreen: React.FC = () => {
showsVerticalScrollIndicator={false}
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
>
- {hasVisibleItems(['mdblist', 'tmdb']) && (
-
- {isItemVisible('mdblist') && (
- }
- renderControl={() => }
- onPress={() => navigation.navigate('MDBListSettings')}
- />
- )}
- {isItemVisible('tmdb') && (
- }
- renderControl={() => }
- onPress={() => navigation.navigate('TMDBSettings')}
- isLast
- />
- )}
-
- )}
-
- {hasVisibleItems(['openrouter']) && (
-
- {isItemVisible('openrouter') && (
- }
- onPress={() => navigation.navigate('AISettings')}
- isLast
- />
- )}
-
- )}
+
);
diff --git a/src/screens/settings/PlaybackSettingsScreen.tsx b/src/screens/settings/PlaybackSettingsScreen.tsx
index 892aa07..0ebf343 100644
--- a/src/screens/settings/PlaybackSettingsScreen.tsx
+++ b/src/screens/settings/PlaybackSettingsScreen.tsx
@@ -1,5 +1,5 @@
import React, { useState, useCallback, useMemo, useRef } from 'react';
-import { View, StyleSheet, ScrollView, StatusBar, Platform, Text, TouchableOpacity } from 'react-native';
+import { View, StyleSheet, ScrollView, StatusBar, Platform, Text, TouchableOpacity, Dimensions } from 'react-native';
import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -12,6 +12,8 @@ import { useRealtimeConfig } from '../../hooks/useRealtimeConfig';
import { MaterialIcons } from '@expo/vector-icons';
import { BottomSheetModal, BottomSheetScrollView, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
+const { width } = Dimensions.get('window');
+
// Available languages for audio/subtitle selection
const AVAILABLE_LANGUAGES = [
{ code: 'en', name: 'English' },
@@ -54,11 +56,19 @@ const SUBTITLE_SOURCE_OPTIONS = [
{ value: 'any', label: 'Any Available', description: 'Use first available subtitle track' },
];
-const PlaybackSettingsScreen: React.FC = () => {
+// Props for the reusable content component
+interface PlaybackSettingsContentProps {
+ isTablet?: boolean;
+}
+
+/**
+ * Reusable PlaybackSettingsContent component
+ * Can be used inline (tablets) or wrapped in a screen (mobile)
+ */
+export const PlaybackSettingsContent: React.FC = ({ isTablet = false }) => {
const navigation = useNavigation>();
const { currentTheme } = useTheme();
const { settings, updateSetting } = useSettings();
- const insets = useSafeAreaInsets();
const config = useRealtimeConfig();
// Bottom sheet refs
@@ -139,117 +149,116 @@ const PlaybackSettingsScreen: React.FC = () => {
};
return (
-
-
- navigation.goBack()} />
-
-
- {hasVisibleItems(['video_player']) && (
-
- {isItemVisible('video_player') && (
- }
- onPress={() => navigation.navigate('PlayerSettings')}
- isLast
- />
- )}
-
- )}
-
- {/* Audio & Subtitle Preferences */}
-
- }
- onPress={openAudioLanguageSheet}
- />
- }
- onPress={openSubtitleLanguageSheet}
- />
- }
- onPress={openSubtitleSourceSheet}
- />
- (
- updateSetting('enableSubtitleAutoSelect', value)}
- />
- )}
- isLast
- />
+ <>
+ {hasVisibleItems(['video_player']) && (
+
+ {isItemVisible('video_player') && (
+ }
+ onPress={() => navigation.navigate('PlayerSettings')}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+ )}
- {hasVisibleItems(['show_trailers', 'enable_downloads']) && (
-
- {isItemVisible('show_trailers') && (
- (
- updateSetting('showTrailers', value)}
- />
- )}
- />
- )}
- {isItemVisible('enable_downloads') && (
- (
- updateSetting('enableDownloads', value)}
- />
- )}
- isLast
- />
- )}
-
- )}
+ {/* Audio & Subtitle Preferences */}
+
+ }
+ onPress={openAudioLanguageSheet}
+ isTablet={isTablet}
+ />
+ }
+ onPress={openSubtitleLanguageSheet}
+ isTablet={isTablet}
+ />
+ }
+ onPress={openSubtitleSourceSheet}
+ isTablet={isTablet}
+ />
+ (
+ updateSetting('enableSubtitleAutoSelect', value)}
+ />
+ )}
+ isLast
+ isTablet={isTablet}
+ />
+
- {hasVisibleItems(['notifications']) && (
-
- {isItemVisible('notifications') && (
- }
- onPress={() => navigation.navigate('NotificationSettings')}
- isLast
- />
- )}
-
- )}
-
+ {hasVisibleItems(['show_trailers', 'enable_downloads']) && (
+
+ {isItemVisible('show_trailers') && (
+ (
+ updateSetting('showTrailers', value)}
+ />
+ )}
+ isTablet={isTablet}
+ />
+ )}
+ {isItemVisible('enable_downloads') && (
+ (
+ updateSetting('enableDownloads', value)}
+ />
+ )}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
+
+ {hasVisibleItems(['notifications']) && (
+
+ {isItemVisible('notifications') && (
+ }
+ onPress={() => navigation.navigate('NotificationSettings')}
+ isLast
+ isTablet={isTablet}
+ />
+ )}
+
+ )}
{/* Audio Language Bottom Sheet */}
{
})}
+ >
+ );
+};
+
+/**
+ * PlaybackSettingsScreen - Wrapper for mobile navigation
+ * Uses PlaybackSettingsContent internally
+ */
+const PlaybackSettingsScreen: React.FC = () => {
+ const navigation = useNavigation>();
+ const { currentTheme } = useTheme();
+ const insets = useSafeAreaInsets();
+ const screenIsTablet = width >= 768;
+
+ return (
+
+
+ navigation.goBack()} />
+
+
+
+
);
};
diff --git a/src/screens/settings/index.ts b/src/screens/settings/index.ts
index f914786..e615c2e 100644
--- a/src/screens/settings/index.ts
+++ b/src/screens/settings/index.ts
@@ -1,7 +1,17 @@
+// Screen exports (default)
export { default as ContentDiscoverySettingsScreen } from './ContentDiscoverySettingsScreen';
export { default as AppearanceSettingsScreen } from './AppearanceSettingsScreen';
export { default as IntegrationsSettingsScreen } from './IntegrationsSettingsScreen';
export { default as PlaybackSettingsScreen } from './PlaybackSettingsScreen';
export { default as AboutSettingsScreen } from './AboutSettingsScreen';
export { default as DeveloperSettingsScreen } from './DeveloperSettingsScreen';
+
+// Reusable content component exports (for inline use on tablets)
+export { ContentDiscoverySettingsContent } from './ContentDiscoverySettingsScreen';
+export { AppearanceSettingsContent } from './AppearanceSettingsScreen';
+export { IntegrationsSettingsContent } from './IntegrationsSettingsScreen';
+export { PlaybackSettingsContent } from './PlaybackSettingsScreen';
+export { AboutSettingsContent, AboutFooter } from './AboutSettingsScreen';
+
+// Shared UI component exports
export { SettingsCard, SettingItem, CustomSwitch, ChevronRight } from './SettingsComponents';