mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
refactored settingscreen
This commit is contained in:
parent
3285ecbe04
commit
bf75cca438
8 changed files with 665 additions and 1142 deletions
|
|
@ -339,19 +339,21 @@ const HomeScreenSettings: React.FC = () => {
|
|||
|
||||
{settings.showHeroSection && (
|
||||
<>
|
||||
<View style={styles.segmentCard}>
|
||||
<Text style={[styles.segmentTitle, { color: isDarkMode ? colors.mediumEmphasis : colors.textMutedDark }]}>Hero Layout</Text>
|
||||
<SegmentedControl
|
||||
options={[
|
||||
{ label: 'Legacy', value: 'legacy' },
|
||||
{ label: 'Carousel', value: 'carousel' },
|
||||
{ label: 'Apple TV', value: 'appletv' }
|
||||
]}
|
||||
value={settings.heroStyle}
|
||||
onChange={(val) => handleUpdateSetting('heroStyle', val as any)}
|
||||
/>
|
||||
<Text style={[styles.segmentHint, { color: isDarkMode ? colors.mediumEmphasis : colors.textMutedDark }]}>Full-width banner, swipeable cards, or Apple TV style</Text>
|
||||
</View>
|
||||
{!isTabletDevice && (
|
||||
<View style={styles.segmentCard}>
|
||||
<Text style={[styles.segmentTitle, { color: isDarkMode ? colors.mediumEmphasis : colors.textMutedDark }]}>Hero Layout</Text>
|
||||
<SegmentedControl
|
||||
options={[
|
||||
{ label: 'Legacy', value: 'legacy' },
|
||||
{ label: 'Carousel', value: 'carousel' },
|
||||
{ label: 'Apple TV', value: 'appletv' }
|
||||
]}
|
||||
value={settings.heroStyle}
|
||||
onChange={(val) => handleUpdateSetting('heroStyle', val as any)}
|
||||
/>
|
||||
<Text style={[styles.segmentHint, { color: isDarkMode ? colors.mediumEmphasis : colors.textMutedDark }]}>Full-width banner, swipeable cards, or Apple TV style</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.segmentCard}>
|
||||
<Text style={[styles.segmentTitle, { color: isDarkMode ? colors.mediumEmphasis : colors.textMutedDark }]}>Featured Source</Text>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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<AboutSettingsContentProps> = ({
|
||||
isTablet = false,
|
||||
displayDownloads: externalDisplayDownloads
|
||||
}) => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
const [internalDisplayDownloads, setInternalDisplayDownloads] = useState<number | null>(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 (
|
||||
<>
|
||||
<SettingsCard title="INFORMATION" isTablet={isTablet}>
|
||||
<SettingItem
|
||||
title="Privacy Policy"
|
||||
icon="lock"
|
||||
onPress={() => Linking.openURL('https://tapframe.github.io/NuvioStreaming/#privacy-policy')}
|
||||
renderControl={() => <ChevronRight />}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Report Issue"
|
||||
icon="alert-triangle"
|
||||
onPress={() => Sentry.showFeedbackWidget()}
|
||||
renderControl={() => <ChevronRight />}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Version"
|
||||
description={getDisplayedAppVersion()}
|
||||
icon="info"
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Contributors"
|
||||
description="View all contributors"
|
||||
icon="users"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('Contributors')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
</SettingsCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reusable AboutFooter component - Downloads counter, social links, branding
|
||||
*/
|
||||
export const AboutFooter: React.FC<{ displayDownloads: number | null }> = ({ displayDownloads }) => {
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
{displayDownloads !== null && (
|
||||
<View style={styles.downloadsContainer}>
|
||||
<Text style={[styles.downloadsNumber, { color: currentTheme.colors.primary }]}>
|
||||
{displayDownloads.toLocaleString()}
|
||||
</Text>
|
||||
<Text style={[styles.downloadsLabel, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
downloads and counting
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.communityContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.supportButton}
|
||||
onPress={() => WebBrowser.openBrowserAsync('https://ko-fi.com/tapframe', {
|
||||
presentationStyle: Platform.OS === 'ios' ? WebBrowser.WebBrowserPresentationStyle.FORM_SHEET : WebBrowser.WebBrowserPresentationStyle.FORM_SHEET
|
||||
})}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<FastImage
|
||||
source={require('../../../assets/support_me_on_kofi_red.png')}
|
||||
style={styles.kofiImage}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.socialRow}>
|
||||
<TouchableOpacity
|
||||
style={[styles.socialButton, { backgroundColor: currentTheme.colors.elevation1 }]}
|
||||
onPress={() => Linking.openURL('https://discord.gg/KVgDTjhA4H')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.socialButtonContent}>
|
||||
<FastImage
|
||||
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
|
||||
style={styles.socialLogo}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
<Text style={[styles.socialButtonText, { color: currentTheme.colors.highEmphasis }]}>
|
||||
Discord
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.socialButton, { backgroundColor: '#FF4500' + '15' }]}
|
||||
onPress={() => Linking.openURL('https://www.reddit.com/r/Nuvio/')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.socialButtonContent}>
|
||||
<FastImage
|
||||
source={{ uri: 'https://www.iconpacks.net/icons/2/free-reddit-logo-icon-2436-thumb.png' }}
|
||||
style={styles.socialLogo}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
<Text style={[styles.socialButtonText, { color: '#FF4500' }]}>
|
||||
Reddit
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Monkey Animation */}
|
||||
<View style={styles.monkeyContainer}>
|
||||
<LottieView
|
||||
source={require('../../assets/lottie/monito.json')}
|
||||
autoPlay
|
||||
loop
|
||||
style={styles.monkeyAnimation}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.brandLogoContainer}>
|
||||
<FastImage
|
||||
source={require('../../../assets/nuviotext.png')}
|
||||
style={styles.brandLogo}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
Made with ❤️ by Tapframe and Friends
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* AboutSettingsScreen - Wrapper for mobile navigation
|
||||
*/
|
||||
const AboutSettingsScreen: React.FC = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const [totalDownloads, setTotalDownloads] = useState<number | null>(null);
|
||||
const [displayDownloads, setDisplayDownloads] = useState<number | null>(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 (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
|
|
@ -72,34 +205,7 @@ const AboutSettingsScreen: React.FC = () => {
|
|||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 40 }]}
|
||||
>
|
||||
<SettingsCard title="INFORMATION">
|
||||
<SettingItem
|
||||
title="Privacy Policy"
|
||||
icon="lock"
|
||||
onPress={() => Linking.openURL('https://tapframe.github.io/NuvioStreaming/#privacy-policy')}
|
||||
renderControl={() => <ChevronRight />}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Report Issue"
|
||||
icon="alert-triangle"
|
||||
onPress={() => Sentry.showFeedbackWidget()}
|
||||
renderControl={() => <ChevronRight />}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Version"
|
||||
description={getDisplayedAppVersion()}
|
||||
icon="info"
|
||||
/>
|
||||
<SettingItem
|
||||
title="Contributors"
|
||||
description="View all contributors"
|
||||
icon="users"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('Contributors')}
|
||||
isLast
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
<AboutSettingsContent isTablet={screenIsTablet} />
|
||||
<View style={{ height: 24 }} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -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<AppearanceSettingsContentProps> = ({ isTablet = false }) => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
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']) && (
|
||||
<SettingsCard title="THEME" isTablet={isTablet}>
|
||||
{isItemVisible('theme') && (
|
||||
<SettingItem
|
||||
title="Theme"
|
||||
description={currentTheme.name}
|
||||
icon="sliders"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('ThemeSettings')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['episode_layout', 'streams_backdrop']) && (
|
||||
<SettingsCard title="LAYOUT" isTablet={isTablet}>
|
||||
{isItemVisible('episode_layout') && (
|
||||
<SettingItem
|
||||
title="Episode Layout"
|
||||
description={settings?.episodeLayoutStyle === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||
icon="grid"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.episodeLayoutStyle === 'horizontal'}
|
||||
onValueChange={(value) => updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
|
||||
/>
|
||||
)}
|
||||
isLast={isTablet || !isItemVisible('streams_backdrop')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
{!isTablet && isItemVisible('streams_backdrop') && (
|
||||
<SettingItem
|
||||
title="Streams Backdrop"
|
||||
description="Show blurred backdrop on mobile streams"
|
||||
icon="image"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.enableStreamsBackdrop ?? true}
|
||||
onValueChange={(value) => updateSetting('enableStreamsBackdrop', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* AppearanceSettingsScreen - Wrapper for mobile navigation
|
||||
*/
|
||||
const AppearanceSettingsScreen: React.FC = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const screenIsTablet = width >= 768;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
|
@ -44,53 +115,7 @@ const AppearanceSettingsScreen: React.FC = () => {
|
|||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
|
||||
>
|
||||
{hasVisibleItems(['theme']) && (
|
||||
<SettingsCard title="THEME">
|
||||
{isItemVisible('theme') && (
|
||||
<SettingItem
|
||||
title="Theme"
|
||||
description={currentTheme.name}
|
||||
icon="sliders"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('ThemeSettings')}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['episode_layout', 'streams_backdrop']) && (
|
||||
<SettingsCard title="LAYOUT">
|
||||
{isItemVisible('episode_layout') && (
|
||||
<SettingItem
|
||||
title="Episode Layout"
|
||||
description={settings?.episodeLayoutStyle === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||
icon="grid"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.episodeLayoutStyle === 'horizontal'}
|
||||
onValueChange={(value) => updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
|
||||
/>
|
||||
)}
|
||||
isLast={isTablet || !isItemVisible('streams_backdrop')}
|
||||
/>
|
||||
)}
|
||||
{!isTablet && isItemVisible('streams_backdrop') && (
|
||||
<SettingItem
|
||||
title="Streams Backdrop"
|
||||
description="Show blurred backdrop on mobile streams"
|
||||
icon="image"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.enableStreamsBackdrop ?? true}
|
||||
onValueChange={(value) => updateSetting('enableStreamsBackdrop', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
<AppearanceSettingsContent isTablet={screenIsTablet} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<ContentDiscoverySettingsContentProps> = ({ isTablet = false }) => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const { settings, updateSetting } = useSettings();
|
||||
const insets = useSafeAreaInsets();
|
||||
const config = useRealtimeConfig();
|
||||
|
||||
const [addonCount, setAddonCount] = useState<number>(0);
|
||||
const [catalogCount, setCatalogCount] = useState<number>(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']) && (
|
||||
<SettingsCard title="SOURCES" isTablet={isTablet}>
|
||||
{isItemVisible('addons') && (
|
||||
<SettingItem
|
||||
title="Addons"
|
||||
description={`${addonCount} installed`}
|
||||
icon="layers"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('Addons')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('debrid') && (
|
||||
<SettingItem
|
||||
title="Debrid Integration"
|
||||
description="Connect Torbox for premium streams"
|
||||
icon="link"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('DebridIntegration')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('plugins') && (
|
||||
<SettingItem
|
||||
title="Plugins"
|
||||
description="Manage plugins and repositories"
|
||||
customIcon={<PluginIcon size={isTablet ? 22 : 18} color={currentTheme.colors.primary} />}
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('ScraperSettings')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['catalogs', 'home_screen', 'continue_watching']) && (
|
||||
<SettingsCard title="CATALOGS" isTablet={isTablet}>
|
||||
{isItemVisible('catalogs') && (
|
||||
<SettingItem
|
||||
title="Catalogs"
|
||||
description={`${catalogCount} active`}
|
||||
icon="list"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('CatalogSettings')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('home_screen') && (
|
||||
<SettingItem
|
||||
title="Home Screen"
|
||||
description="Layout and content"
|
||||
icon="home"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('HomeScreenSettings')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('continue_watching') && (
|
||||
<SettingItem
|
||||
title="Continue Watching"
|
||||
description="Cache and playback behavior"
|
||||
icon="play-circle"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('ContinueWatchingSettings')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['show_discover']) && (
|
||||
<SettingsCard title="DISCOVERY" isTablet={isTablet}>
|
||||
{isItemVisible('show_discover') && (
|
||||
<SettingItem
|
||||
title="Show Discover Section"
|
||||
description="Display discover content in Search"
|
||||
icon="compass"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.showDiscover ?? true}
|
||||
onValueChange={(value) => updateSetting('showDiscover', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* ContentDiscoverySettingsScreen - Wrapper for mobile navigation
|
||||
*/
|
||||
const ContentDiscoverySettingsScreen: React.FC = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const screenIsTablet = width >= 768;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
|
@ -77,90 +192,7 @@ const ContentDiscoverySettingsScreen: React.FC = () => {
|
|||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
|
||||
>
|
||||
{hasVisibleItems(['addons', 'debrid', 'plugins']) && (
|
||||
<SettingsCard title="SOURCES">
|
||||
{isItemVisible('addons') && (
|
||||
<SettingItem
|
||||
title="Addons"
|
||||
description={`${addonCount} installed`}
|
||||
icon="layers"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('Addons')}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('debrid') && (
|
||||
<SettingItem
|
||||
title="Debrid Integration"
|
||||
description="Connect Torbox for premium streams"
|
||||
icon="link"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('DebridIntegration')}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('plugins') && (
|
||||
<SettingItem
|
||||
title="Plugins"
|
||||
description="Manage plugins and repositories"
|
||||
customIcon={<PluginIcon size={18} color={currentTheme.colors.primary} />}
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('ScraperSettings')}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['catalogs', 'home_screen', 'continue_watching']) && (
|
||||
<SettingsCard title="CATALOGS">
|
||||
{isItemVisible('catalogs') && (
|
||||
<SettingItem
|
||||
title="Catalogs"
|
||||
description={`${catalogCount} active`}
|
||||
icon="list"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('CatalogSettings')}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('home_screen') && (
|
||||
<SettingItem
|
||||
title="Home Screen"
|
||||
description="Layout and content"
|
||||
icon="home"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('HomeScreenSettings')}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('continue_watching') && (
|
||||
<SettingItem
|
||||
title="Continue Watching"
|
||||
description="Cache and playback behavior"
|
||||
icon="play-circle"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('ContinueWatchingSettings')}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['show_discover']) && (
|
||||
<SettingsCard title="DISCOVERY">
|
||||
{isItemVisible('show_discover') && (
|
||||
<SettingItem
|
||||
title="Show Discover Section"
|
||||
description="Display discover content in Search"
|
||||
icon="compass"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.showDiscover ?? true}
|
||||
onValueChange={(value) => updateSetting('showDiscover', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
<ContentDiscoverySettingsContent isTablet={screenIsTablet} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<IntegrationsSettingsContentProps> = ({ isTablet = false }) => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const config = useRealtimeConfig();
|
||||
|
||||
const [mdblistKeySet, setMdblistKeySet] = useState<boolean>(false);
|
||||
const [openRouterKeySet, setOpenRouterKeySet] = useState<boolean>(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']) && (
|
||||
<SettingsCard title="METADATA" isTablet={isTablet}>
|
||||
{isItemVisible('mdblist') && (
|
||||
<SettingItem
|
||||
title="MDBList"
|
||||
description={mdblistKeySet ? "Connected" : "Enable to add ratings & reviews"}
|
||||
customIcon={<MDBListIcon size={isTablet ? 22 : 18} colorPrimary={currentTheme.colors.primary} colorSecondary={currentTheme.colors.white} />}
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('MDBListSettings')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('tmdb') && (
|
||||
<SettingItem
|
||||
title="TMDB"
|
||||
description="Metadata & logo source provider"
|
||||
customIcon={<TMDBIcon size={isTablet ? 22 : 18} color={currentTheme.colors.primary} />}
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('TMDBSettings')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['openrouter']) && (
|
||||
<SettingsCard title="AI ASSISTANT" isTablet={isTablet}>
|
||||
{isItemVisible('openrouter') && (
|
||||
<SettingItem
|
||||
title="OpenRouter API"
|
||||
description={openRouterKeySet ? "Connected" : "Add your API key to enable AI chat"}
|
||||
icon="cpu"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('AISettings')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* IntegrationsSettingsScreen - Wrapper for mobile navigation
|
||||
*/
|
||||
const IntegrationsSettingsScreen: React.FC = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const screenIsTablet = width >= 768;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
|
@ -60,44 +125,7 @@ const IntegrationsSettingsScreen: React.FC = () => {
|
|||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
|
||||
>
|
||||
{hasVisibleItems(['mdblist', 'tmdb']) && (
|
||||
<SettingsCard title="METADATA">
|
||||
{isItemVisible('mdblist') && (
|
||||
<SettingItem
|
||||
title="MDBList"
|
||||
description={mdblistKeySet ? "Connected" : "Enable to add ratings & reviews"}
|
||||
customIcon={<MDBListIcon size={18} colorPrimary={currentTheme.colors.primary} colorSecondary={currentTheme.colors.white} />}
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('MDBListSettings')}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('tmdb') && (
|
||||
<SettingItem
|
||||
title="TMDB"
|
||||
description="Metadata & logo source provider"
|
||||
customIcon={<TMDBIcon size={18} color={currentTheme.colors.primary} />}
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('TMDBSettings')}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['openrouter']) && (
|
||||
<SettingsCard title="AI ASSISTANT">
|
||||
{isItemVisible('openrouter') && (
|
||||
<SettingItem
|
||||
title="OpenRouter API"
|
||||
description={openRouterKeySet ? "Connected" : "Add your API key to enable AI chat"}
|
||||
icon="cpu"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('AISettings')}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
<IntegrationsSettingsContent isTablet={screenIsTablet} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<PlaybackSettingsContentProps> = ({ isTablet = false }) => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
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 (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
<ScreenHeader title="Playback" showBackButton onBackPress={() => navigation.goBack()} />
|
||||
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
|
||||
>
|
||||
{hasVisibleItems(['video_player']) && (
|
||||
<SettingsCard title="VIDEO PLAYER">
|
||||
{isItemVisible('video_player') && (
|
||||
<SettingItem
|
||||
title="Video Player"
|
||||
description={Platform.OS === 'ios'
|
||||
? (settings?.preferredPlayer === 'internal' ? 'Built-in' : settings?.preferredPlayer?.toUpperCase() || 'Built-in')
|
||||
: (settings?.useExternalPlayer ? 'External' : 'Built-in')
|
||||
}
|
||||
icon="play-circle"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('PlayerSettings')}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{/* Audio & Subtitle Preferences */}
|
||||
<SettingsCard title="AUDIO & SUBTITLES">
|
||||
<SettingItem
|
||||
title="Preferred Audio Language"
|
||||
description={getLanguageName(settings?.preferredAudioLanguage || 'en')}
|
||||
icon="volume-2"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={openAudioLanguageSheet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Preferred Subtitle Language"
|
||||
description={getLanguageName(settings?.preferredSubtitleLanguage || 'en')}
|
||||
icon="type"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={openSubtitleLanguageSheet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Subtitle Source Priority"
|
||||
description={getSourceLabel(settings?.subtitleSourcePreference || 'internal')}
|
||||
icon="layers"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={openSubtitleSourceSheet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Auto-Select Subtitles"
|
||||
description="Automatically select subtitles matching your preferences"
|
||||
icon="zap"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.enableSubtitleAutoSelect ?? true}
|
||||
onValueChange={(value) => updateSetting('enableSubtitleAutoSelect', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
/>
|
||||
<>
|
||||
{hasVisibleItems(['video_player']) && (
|
||||
<SettingsCard title="VIDEO PLAYER" isTablet={isTablet}>
|
||||
{isItemVisible('video_player') && (
|
||||
<SettingItem
|
||||
title="Video Player"
|
||||
description={Platform.OS === 'ios'
|
||||
? (settings?.preferredPlayer === 'internal' ? 'Built-in' : settings?.preferredPlayer?.toUpperCase() || 'Built-in')
|
||||
: (settings?.useExternalPlayer ? 'External' : 'Built-in')
|
||||
}
|
||||
icon="play-circle"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('PlayerSettings')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['show_trailers', 'enable_downloads']) && (
|
||||
<SettingsCard title="MEDIA">
|
||||
{isItemVisible('show_trailers') && (
|
||||
<SettingItem
|
||||
title="Show Trailers"
|
||||
description="Display trailers in hero section"
|
||||
icon="film"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.showTrailers ?? true}
|
||||
onValueChange={(value) => updateSetting('showTrailers', value)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('enable_downloads') && (
|
||||
<SettingItem
|
||||
title="Enable Downloads (Beta)"
|
||||
description="Show Downloads tab and enable saving streams"
|
||||
icon="download"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.enableDownloads ?? false}
|
||||
onValueChange={(value) => updateSetting('enableDownloads', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
{/* Audio & Subtitle Preferences */}
|
||||
<SettingsCard title="AUDIO & SUBTITLES" isTablet={isTablet}>
|
||||
<SettingItem
|
||||
title="Preferred Audio Language"
|
||||
description={getLanguageName(settings?.preferredAudioLanguage || 'en')}
|
||||
icon="volume-2"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={openAudioLanguageSheet}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Preferred Subtitle Language"
|
||||
description={getLanguageName(settings?.preferredSubtitleLanguage || 'en')}
|
||||
icon="type"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={openSubtitleLanguageSheet}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Subtitle Source Priority"
|
||||
description={getSourceLabel(settings?.subtitleSourcePreference || 'internal')}
|
||||
icon="layers"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={openSubtitleSourceSheet}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
title="Auto-Select Subtitles"
|
||||
description="Automatically select subtitles matching your preferences"
|
||||
icon="zap"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.enableSubtitleAutoSelect ?? true}
|
||||
onValueChange={(value) => updateSetting('enableSubtitleAutoSelect', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
{hasVisibleItems(['notifications']) && (
|
||||
<SettingsCard title="NOTIFICATIONS">
|
||||
{isItemVisible('notifications') && (
|
||||
<SettingItem
|
||||
title="Notifications"
|
||||
description="Episode reminders"
|
||||
icon="bell"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('NotificationSettings')}
|
||||
isLast
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
</ScrollView>
|
||||
{hasVisibleItems(['show_trailers', 'enable_downloads']) && (
|
||||
<SettingsCard title="MEDIA" isTablet={isTablet}>
|
||||
{isItemVisible('show_trailers') && (
|
||||
<SettingItem
|
||||
title="Show Trailers"
|
||||
description="Display trailers in hero section"
|
||||
icon="film"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.showTrailers ?? true}
|
||||
onValueChange={(value) => updateSetting('showTrailers', value)}
|
||||
/>
|
||||
)}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
{isItemVisible('enable_downloads') && (
|
||||
<SettingItem
|
||||
title="Enable Downloads (Beta)"
|
||||
description="Show Downloads tab and enable saving streams"
|
||||
icon="download"
|
||||
renderControl={() => (
|
||||
<CustomSwitch
|
||||
value={settings?.enableDownloads ?? false}
|
||||
onValueChange={(value) => updateSetting('enableDownloads', value)}
|
||||
/>
|
||||
)}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{hasVisibleItems(['notifications']) && (
|
||||
<SettingsCard title="NOTIFICATIONS" isTablet={isTablet}>
|
||||
{isItemVisible('notifications') && (
|
||||
<SettingItem
|
||||
title="Notifications"
|
||||
description="Episode reminders"
|
||||
icon="bell"
|
||||
renderControl={() => <ChevronRight />}
|
||||
onPress={() => navigation.navigate('NotificationSettings')}
|
||||
isLast
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{/* Audio Language Bottom Sheet */}
|
||||
<BottomSheetModal
|
||||
|
|
@ -375,6 +384,32 @@ const PlaybackSettingsScreen: React.FC = () => {
|
|||
})}
|
||||
</BottomSheetScrollView>
|
||||
</BottomSheetModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* PlaybackSettingsScreen - Wrapper for mobile navigation
|
||||
* Uses PlaybackSettingsContent internally
|
||||
*/
|
||||
const PlaybackSettingsScreen: React.FC = () => {
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const screenIsTablet = width >= 768;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
<ScreenHeader title="Playback" showBackButton onBackPress={() => navigation.goBack()} />
|
||||
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 24 }]}
|
||||
>
|
||||
<PlaybackSettingsContent isTablet={screenIsTablet} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Reference in a new issue