mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-10 12:01:55 +00:00
improved layout for tablets
This commit is contained in:
parent
69e5141c58
commit
63c673bfae
2 changed files with 543 additions and 247 deletions
|
|
@ -66,6 +66,20 @@ interface TraktFolder {
|
||||||
|
|
||||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||||
|
|
||||||
|
// Compute responsive grid layout (more columns on tablets)
|
||||||
|
function getGridLayout(screenWidth: number): { numColumns: number; itemWidth: number } {
|
||||||
|
const horizontalPadding = 24; // matches listContainer padding (approx)
|
||||||
|
const gutter = 16; // space between items (via space-between + marginBottom)
|
||||||
|
let numColumns = 2;
|
||||||
|
if (screenWidth >= 1200) numColumns = 5;
|
||||||
|
else if (screenWidth >= 1000) numColumns = 4;
|
||||||
|
else if (screenWidth >= 700) numColumns = 3;
|
||||||
|
else numColumns = 2;
|
||||||
|
const available = screenWidth - horizontalPadding - (numColumns - 1) * gutter;
|
||||||
|
const itemWidth = Math.floor(available / numColumns);
|
||||||
|
return { numColumns, itemWidth };
|
||||||
|
}
|
||||||
|
|
||||||
const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item: TraktDisplayItem; width: number; navigation: any; currentTheme: any }) => {
|
const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item: TraktDisplayItem; width: number; navigation: any; currentTheme: any }) => {
|
||||||
const [posterUrl, setPosterUrl] = useState<string | null>(null);
|
const [posterUrl, setPosterUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
|
|
@ -146,7 +160,7 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item:
|
||||||
const SkeletonLoader = () => {
|
const SkeletonLoader = () => {
|
||||||
const pulseAnim = React.useRef(new RNAnimated.Value(0)).current;
|
const pulseAnim = React.useRef(new RNAnimated.Value(0)).current;
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const itemWidth = (width - 48) / 2;
|
const { numColumns, itemWidth } = getGridLayout(width);
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -190,10 +204,12 @@ const SkeletonLoader = () => {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Render enough skeletons for at least two rows
|
||||||
|
const skeletonCount = numColumns * 2;
|
||||||
return (
|
return (
|
||||||
<View style={styles.skeletonContainer}>
|
<View style={styles.skeletonContainer}>
|
||||||
{[...Array(6)].map((_, index) => (
|
{Array.from({ length: skeletonCount }).map((_, index) => (
|
||||||
<View key={index} style={{ width: itemWidth, margin: 8 }}>
|
<View key={index} style={{ width: itemWidth, marginBottom: 16 }}>
|
||||||
{renderSkeletonItem()}
|
{renderSkeletonItem()}
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|
@ -205,6 +221,7 @@ const LibraryScreen = () => {
|
||||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||||
const isDarkMode = useColorScheme() === 'dark';
|
const isDarkMode = useColorScheme() === 'dark';
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
|
const { numColumns, itemWidth } = useMemo(() => getGridLayout(width), [width]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [libraryItems, setLibraryItems] = useState<LibraryItem[]>([]);
|
const [libraryItems, setLibraryItems] = useState<LibraryItem[]>([]);
|
||||||
const [filter, setFilter] = useState<'all' | 'movies' | 'series'>('all');
|
const [filter, setFilter] = useState<'all' | 'movies' | 'series'>('all');
|
||||||
|
|
@ -329,8 +346,6 @@ const LibraryScreen = () => {
|
||||||
return folders.filter(folder => folder.itemCount > 0);
|
return folders.filter(folder => folder.itemCount > 0);
|
||||||
}, [traktAuthenticated, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]);
|
}, [traktAuthenticated, watchedMovies, watchedShows, watchlistMovies, watchlistShows, collectionMovies, collectionShows, continueWatching, ratedContent]);
|
||||||
|
|
||||||
const itemWidth = (width - 48) / 2; // 2 items per row with padding
|
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: LibraryItem }) => (
|
const renderItem = ({ item }: { item: LibraryItem }) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.itemContainer, { width: itemWidth }]}
|
style={[styles.itemContainer, { width: itemWidth }]}
|
||||||
|
|
@ -717,7 +732,7 @@ const LibraryScreen = () => {
|
||||||
data={traktFolders}
|
data={traktFolders}
|
||||||
renderItem={({ item }) => renderTraktCollectionFolder({ folder: item })}
|
renderItem={({ item }) => renderTraktCollectionFolder({ folder: item })}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
numColumns={2}
|
numColumns={numColumns}
|
||||||
contentContainerStyle={styles.listContainer}
|
contentContainerStyle={styles.listContainer}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
columnWrapperStyle={styles.columnWrapper}
|
columnWrapperStyle={styles.columnWrapper}
|
||||||
|
|
@ -760,7 +775,7 @@ const LibraryScreen = () => {
|
||||||
data={folderItems}
|
data={folderItems}
|
||||||
renderItem={({ item }) => renderTraktItem({ item })}
|
renderItem={({ item }) => renderTraktItem({ item })}
|
||||||
keyExtractor={(item) => `${item.type}-${item.id}`}
|
keyExtractor={(item) => `${item.type}-${item.id}`}
|
||||||
numColumns={2}
|
numColumns={numColumns}
|
||||||
columnWrapperStyle={styles.row}
|
columnWrapperStyle={styles.row}
|
||||||
style={styles.traktContainer}
|
style={styles.traktContainer}
|
||||||
contentContainerStyle={{ paddingBottom: insets.bottom + 80 }}
|
contentContainerStyle={{ paddingBottom: insets.bottom + 80 }}
|
||||||
|
|
@ -856,7 +871,7 @@ const LibraryScreen = () => {
|
||||||
return renderItem({ item: item as LibraryItem });
|
return renderItem({ item: item as LibraryItem });
|
||||||
}}
|
}}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
numColumns={2}
|
numColumns={numColumns}
|
||||||
contentContainerStyle={styles.listContainer}
|
contentContainerStyle={styles.listContainer}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
columnWrapperStyle={styles.columnWrapper}
|
columnWrapperStyle={styles.columnWrapper}
|
||||||
|
|
|
||||||
|
|
@ -31,34 +31,53 @@ import { catalogService } from '../services/catalogService';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import * as Sentry from '@sentry/react-native';
|
import * as Sentry from '@sentry/react-native';
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
const isTablet = width >= 768;
|
||||||
|
|
||||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||||
|
|
||||||
|
// Settings categories for tablet sidebar
|
||||||
|
const SETTINGS_CATEGORIES = [
|
||||||
|
{ id: 'account', title: 'Account', icon: 'account-circle' },
|
||||||
|
{ id: 'content', title: 'Content & Discovery', icon: 'explore' },
|
||||||
|
{ id: 'appearance', title: 'Appearance', icon: 'palette' },
|
||||||
|
{ id: 'integrations', title: 'Integrations', icon: 'extension' },
|
||||||
|
{ id: 'playback', title: 'Playback', icon: 'play-circle-outline' },
|
||||||
|
{ id: 'about', title: 'About', icon: 'info-outline' },
|
||||||
|
{ id: 'developer', title: 'Developer', icon: 'code' },
|
||||||
|
{ id: 'cache', title: 'Cache', icon: 'cached' },
|
||||||
|
];
|
||||||
|
|
||||||
// Card component with minimalistic style
|
// Card component with minimalistic style
|
||||||
interface SettingsCardProps {
|
interface SettingsCardProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
isTablet?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title }) => {
|
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title, isTablet = false }) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[styles.cardContainer]}
|
style={[
|
||||||
|
styles.cardContainer,
|
||||||
|
isTablet && styles.tabletCardContainer
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
{title && (
|
{title && (
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.cardTitle,
|
styles.cardTitle,
|
||||||
{ color: currentTheme.colors.mediumEmphasis }
|
{ color: currentTheme.colors.mediumEmphasis },
|
||||||
|
isTablet && styles.tabletCardTitle
|
||||||
]}>
|
]}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.card,
|
styles.card,
|
||||||
{ backgroundColor: currentTheme.colors.elevation1 }
|
{ backgroundColor: currentTheme.colors.elevation1 },
|
||||||
|
isTablet && styles.tabletCard
|
||||||
]}>
|
]}>
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -74,6 +93,7 @@ interface SettingItemProps {
|
||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
badge?: string | number;
|
badge?: string | number;
|
||||||
|
isTablet?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingItem: React.FC<SettingItemProps> = ({
|
const SettingItem: React.FC<SettingItemProps> = ({
|
||||||
|
|
@ -83,7 +103,8 @@ const SettingItem: React.FC<SettingItemProps> = ({
|
||||||
renderControl,
|
renderControl,
|
||||||
isLast = false,
|
isLast = false,
|
||||||
onPress,
|
onPress,
|
||||||
badge
|
badge,
|
||||||
|
isTablet = false
|
||||||
}) => {
|
}) => {
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
|
||||||
|
|
@ -94,22 +115,36 @@ const SettingItem: React.FC<SettingItemProps> = ({
|
||||||
style={[
|
style={[
|
||||||
styles.settingItem,
|
styles.settingItem,
|
||||||
!isLast && styles.settingItemBorder,
|
!isLast && styles.settingItemBorder,
|
||||||
{ borderBottomColor: currentTheme.colors.elevation2 }
|
{ borderBottomColor: currentTheme.colors.elevation2 },
|
||||||
|
isTablet && styles.tabletSettingItem
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.settingIconContainer,
|
styles.settingIconContainer,
|
||||||
{ backgroundColor: currentTheme.colors.elevation2 }
|
{ backgroundColor: currentTheme.colors.elevation2 },
|
||||||
|
isTablet && styles.tabletSettingIconContainer
|
||||||
]}>
|
]}>
|
||||||
<MaterialIcons name={icon} size={20} color={currentTheme.colors.primary} />
|
<MaterialIcons
|
||||||
|
name={icon}
|
||||||
|
size={isTablet ? 24 : 20}
|
||||||
|
color={currentTheme.colors.primary}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.settingContent}>
|
<View style={styles.settingContent}>
|
||||||
<View style={styles.settingTextContainer}>
|
<View style={styles.settingTextContainer}>
|
||||||
<Text style={[styles.settingTitle, { color: currentTheme.colors.highEmphasis }]}>
|
<Text style={[
|
||||||
|
styles.settingTitle,
|
||||||
|
{ color: currentTheme.colors.highEmphasis },
|
||||||
|
isTablet && styles.tabletSettingTitle
|
||||||
|
]}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
{description && (
|
{description && (
|
||||||
<Text style={[styles.settingDescription, { color: currentTheme.colors.mediumEmphasis }]} numberOfLines={1}>
|
<Text style={[
|
||||||
|
styles.settingDescription,
|
||||||
|
{ color: currentTheme.colors.mediumEmphasis },
|
||||||
|
isTablet && styles.tabletSettingDescription
|
||||||
|
]} numberOfLines={1}>
|
||||||
{description}
|
{description}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
@ -129,6 +164,62 @@ const SettingItem: React.FC<SettingItemProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tablet Sidebar Component
|
||||||
|
interface SidebarProps {
|
||||||
|
selectedCategory: string;
|
||||||
|
onCategorySelect: (category: string) => void;
|
||||||
|
currentTheme: any;
|
||||||
|
categories: typeof SETTINGS_CATEGORIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sidebar: React.FC<SidebarProps> = ({ selectedCategory, onCategorySelect, currentTheme, categories }) => {
|
||||||
|
return (
|
||||||
|
<View style={[styles.sidebar, { backgroundColor: currentTheme.colors.elevation1 }]}>
|
||||||
|
<View style={styles.sidebarHeader}>
|
||||||
|
<Text style={[styles.sidebarTitle, { color: currentTheme.colors.highEmphasis }]}>
|
||||||
|
Settings
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.sidebarContent} showsVerticalScrollIndicator={false}>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={category.id}
|
||||||
|
style={[
|
||||||
|
styles.sidebarItem,
|
||||||
|
selectedCategory === category.id && [
|
||||||
|
styles.sidebarItemActive,
|
||||||
|
{ backgroundColor: `${currentTheme.colors.primary}15` }
|
||||||
|
]
|
||||||
|
]}
|
||||||
|
onPress={() => onCategorySelect(category.id)}
|
||||||
|
>
|
||||||
|
<MaterialIcons
|
||||||
|
name={category.icon}
|
||||||
|
size={22}
|
||||||
|
color={
|
||||||
|
selectedCategory === category.id
|
||||||
|
? currentTheme.colors.primary
|
||||||
|
: currentTheme.colors.mediumEmphasis
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text style={[
|
||||||
|
styles.sidebarItemText,
|
||||||
|
{
|
||||||
|
color: selectedCategory === category.id
|
||||||
|
? currentTheme.colors.primary
|
||||||
|
: currentTheme.colors.mediumEmphasis
|
||||||
|
}
|
||||||
|
]}>
|
||||||
|
{category.title}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const SettingsScreen: React.FC = () => {
|
const SettingsScreen: React.FC = () => {
|
||||||
const { settings, updateSetting } = useSettings();
|
const { settings, updateSetting } = useSettings();
|
||||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||||
|
|
@ -138,6 +229,9 @@ const SettingsScreen: React.FC = () => {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { user, signOut } = useAccount();
|
const { user, signOut } = useAccount();
|
||||||
|
|
||||||
|
// Tablet-specific state
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState('account');
|
||||||
|
|
||||||
// Add a useEffect to check authentication status on focus
|
// Add a useEffect to check authentication status on focus
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This will reload the Trakt auth status whenever the settings screen is focused
|
// This will reload the Trakt auth status whenever the settings screen is focused
|
||||||
|
|
@ -267,15 +361,328 @@ const SettingsScreen: React.FC = () => {
|
||||||
const ChevronRight = () => (
|
const ChevronRight = () => (
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name="chevron-right"
|
name="chevron-right"
|
||||||
size={20}
|
size={isTablet ? 24 : 20}
|
||||||
color={currentTheme.colors.mediumEmphasis}
|
color={currentTheme.colors.mediumEmphasis}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Filter categories based on conditions
|
||||||
|
const visibleCategories = SETTINGS_CATEGORIES.filter(category => {
|
||||||
|
if (category.id === 'developer' && !__DEV__) return false;
|
||||||
|
if (category.id === 'cache' && !mdblistKeySet) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderCategoryContent = (categoryId: string) => {
|
||||||
|
switch (categoryId) {
|
||||||
|
case 'account':
|
||||||
|
return (
|
||||||
|
<SettingsCard title="ACCOUNT" isTablet={isTablet}>
|
||||||
|
{user ? (
|
||||||
|
<>
|
||||||
|
<SettingItem
|
||||||
|
title={user.displayName || user.email || user.id}
|
||||||
|
description="Manage account"
|
||||||
|
icon="account-circle"
|
||||||
|
onPress={() => navigation.navigate('AccountManage')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<SettingItem
|
||||||
|
title="Sign in / Create account"
|
||||||
|
description="Sync across devices"
|
||||||
|
icon="login"
|
||||||
|
onPress={() => navigation.navigate('Account')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<SettingItem
|
||||||
|
title="Trakt"
|
||||||
|
description={isAuthenticated ? `@${userProfile?.username || 'User'}` : "Sign in to sync"}
|
||||||
|
icon="person"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('TraktSettings')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
{isAuthenticated && (
|
||||||
|
<SettingItem
|
||||||
|
title="Profiles"
|
||||||
|
description="Manage multiple users"
|
||||||
|
icon="people"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('ProfilesSettings')}
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SettingsCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'content':
|
||||||
|
return (
|
||||||
|
<SettingsCard title="CONTENT & DISCOVERY" isTablet={isTablet}>
|
||||||
|
<SettingItem
|
||||||
|
title="Addons"
|
||||||
|
description={`${addonCount} installed`}
|
||||||
|
icon="extension"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('Addons')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Plugins"
|
||||||
|
description="Manage plugins and repositories"
|
||||||
|
icon="code"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('ScraperSettings')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Catalogs"
|
||||||
|
description={`${catalogCount} active`}
|
||||||
|
icon="view-list"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('CatalogSettings')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Home Screen"
|
||||||
|
description="Layout and content"
|
||||||
|
icon="home"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('HomeScreenSettings')}
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</SettingsCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'appearance':
|
||||||
|
return (
|
||||||
|
<SettingsCard title="APPEARANCE" isTablet={isTablet}>
|
||||||
|
<SettingItem
|
||||||
|
title="Theme"
|
||||||
|
description={currentTheme.name}
|
||||||
|
icon="palette"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('ThemeSettings')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Episode Layout"
|
||||||
|
description={settings?.episodeLayoutStyle === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||||
|
icon="view-module"
|
||||||
|
renderControl={() => (
|
||||||
|
<CustomSwitch
|
||||||
|
value={settings?.episodeLayoutStyle === 'horizontal'}
|
||||||
|
onValueChange={(value) => updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</SettingsCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'integrations':
|
||||||
|
return (
|
||||||
|
<SettingsCard title="INTEGRATIONS" isTablet={isTablet}>
|
||||||
|
<SettingItem
|
||||||
|
title="MDBList"
|
||||||
|
description={mdblistKeySet ? "Connected" : "Enable to add ratings & reviews"}
|
||||||
|
icon="star"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('MDBListSettings')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="TMDB"
|
||||||
|
description="Metadata provider"
|
||||||
|
icon="movie"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('TMDBSettings')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Media Sources"
|
||||||
|
description="Logo & image preferences"
|
||||||
|
icon="image"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('LogoSourceSettings')}
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</SettingsCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'playback':
|
||||||
|
return (
|
||||||
|
<SettingsCard title="PLAYBACK" isTablet={isTablet}>
|
||||||
|
<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-outline"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('PlayerSettings')}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Notifications"
|
||||||
|
description="Episode reminders"
|
||||||
|
icon="notifications-none"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('NotificationSettings')}
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</SettingsCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'about':
|
||||||
|
return (
|
||||||
|
<SettingsCard title="ABOUT" isTablet={isTablet}>
|
||||||
|
<SettingItem
|
||||||
|
title="Privacy Policy"
|
||||||
|
icon="lock"
|
||||||
|
onPress={() => Linking.openURL('https://github.com/Stremio/stremio-expo/blob/main/PRIVACY_POLICY.md')}
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Report Issue"
|
||||||
|
icon="bug-report"
|
||||||
|
onPress={() => Sentry.showFeedbackWidget()}
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Version"
|
||||||
|
description="1.0.0"
|
||||||
|
icon="info-outline"
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</SettingsCard>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'developer':
|
||||||
|
return __DEV__ ? (
|
||||||
|
<SettingsCard title="DEVELOPER" isTablet={isTablet}>
|
||||||
|
<SettingItem
|
||||||
|
title="Test Onboarding"
|
||||||
|
icon="play-circle-outline"
|
||||||
|
onPress={() => navigation.navigate('Onboarding')}
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Reset Onboarding"
|
||||||
|
icon="refresh"
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.removeItem('hasCompletedOnboarding');
|
||||||
|
Alert.alert('Success', 'Onboarding has been reset. Restart the app to see the onboarding flow.');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('Error', 'Failed to reset onboarding.');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Clear All Data"
|
||||||
|
icon="delete-forever"
|
||||||
|
onPress={() => {
|
||||||
|
Alert.alert(
|
||||||
|
'Clear All Data',
|
||||||
|
'This will reset all settings and clear all cached data. Are you sure?',
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Clear',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.clear();
|
||||||
|
Alert.alert('Success', 'All data cleared. Please restart the app.');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('Error', 'Failed to clear data.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</SettingsCard>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
case 'cache':
|
||||||
|
return mdblistKeySet ? (
|
||||||
|
<SettingsCard title="CACHE MANAGEMENT" isTablet={isTablet}>
|
||||||
|
<SettingItem
|
||||||
|
title="Clear MDBList Cache"
|
||||||
|
icon="cached"
|
||||||
|
onPress={handleClearMDBListCache}
|
||||||
|
isLast={true}
|
||||||
|
isTablet={isTablet}
|
||||||
|
/>
|
||||||
|
</SettingsCard>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const headerBaseHeight = Platform.OS === 'android' ? 80 : 60;
|
const headerBaseHeight = Platform.OS === 'android' ? 80 : 60;
|
||||||
const topSpacing = Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top;
|
const topSpacing = Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top;
|
||||||
const headerHeight = headerBaseHeight + topSpacing;
|
const headerHeight = headerBaseHeight + topSpacing;
|
||||||
|
|
||||||
|
if (isTablet) {
|
||||||
|
return (
|
||||||
|
<View style={[
|
||||||
|
styles.container,
|
||||||
|
{ backgroundColor: currentTheme.colors.darkBackground }
|
||||||
|
]}>
|
||||||
|
<StatusBar barStyle={'light-content'} />
|
||||||
|
<View style={styles.tabletContainer}>
|
||||||
|
<Sidebar
|
||||||
|
selectedCategory={selectedCategory}
|
||||||
|
onCategorySelect={setSelectedCategory}
|
||||||
|
currentTheme={currentTheme}
|
||||||
|
categories={visibleCategories}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.tabletContent}>
|
||||||
|
<ScrollView
|
||||||
|
style={styles.tabletScrollView}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.tabletScrollContent}
|
||||||
|
>
|
||||||
|
{renderCategoryContent(selectedCategory)}
|
||||||
|
|
||||||
|
{selectedCategory === 'about' && (
|
||||||
|
<View style={styles.footer}>
|
||||||
|
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
|
Made with ❤️ by the Nuvio team
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile Layout (original)
|
||||||
return (
|
return (
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
|
|
@ -295,232 +702,14 @@ const SettingsScreen: React.FC = () => {
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
>
|
>
|
||||||
{/* Account Section */}
|
{renderCategoryContent('account')}
|
||||||
<SettingsCard title="ACCOUNT">
|
{renderCategoryContent('content')}
|
||||||
{user ? (
|
{renderCategoryContent('appearance')}
|
||||||
<>
|
{renderCategoryContent('integrations')}
|
||||||
<SettingItem
|
{renderCategoryContent('playback')}
|
||||||
title={user.displayName || user.email || user.id}
|
{renderCategoryContent('about')}
|
||||||
description="Manage account"
|
{renderCategoryContent('developer')}
|
||||||
icon="account-circle"
|
{renderCategoryContent('cache')}
|
||||||
onPress={() => navigation.navigate('AccountManage')}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<SettingItem
|
|
||||||
title="Sign in / Create account"
|
|
||||||
description="Sync across devices"
|
|
||||||
icon="login"
|
|
||||||
onPress={() => navigation.navigate('Account')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SettingItem
|
|
||||||
title="Trakt"
|
|
||||||
description={isAuthenticated ? `@${userProfile?.username || 'User'}` : "Sign in to sync"}
|
|
||||||
icon="person"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('TraktSettings')}
|
|
||||||
/>
|
|
||||||
{isAuthenticated && (
|
|
||||||
<SettingItem
|
|
||||||
title="Profiles"
|
|
||||||
description="Manage multiple users"
|
|
||||||
icon="people"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('ProfilesSettings')}
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Content & Discovery */}
|
|
||||||
<SettingsCard title="CONTENT & DISCOVERY">
|
|
||||||
<SettingItem
|
|
||||||
title="Addons"
|
|
||||||
description={`${addonCount} installed`}
|
|
||||||
icon="extension"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('Addons')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Plugins"
|
|
||||||
description="Manage plugins and repositories"
|
|
||||||
icon="code"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('ScraperSettings')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Catalogs"
|
|
||||||
description={`${catalogCount} active`}
|
|
||||||
icon="view-list"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('CatalogSettings')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Home Screen"
|
|
||||||
description="Layout and content"
|
|
||||||
icon="home"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('HomeScreenSettings')}
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Appearance & Interface */}
|
|
||||||
<SettingsCard title="APPEARANCE">
|
|
||||||
<SettingItem
|
|
||||||
title="Theme"
|
|
||||||
description={currentTheme.name}
|
|
||||||
icon="palette"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('ThemeSettings')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Episode Layout"
|
|
||||||
description={settings?.episodeLayoutStyle === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
|
||||||
icon="view-module"
|
|
||||||
renderControl={() => (
|
|
||||||
<CustomSwitch
|
|
||||||
value={settings?.episodeLayoutStyle === 'horizontal'}
|
|
||||||
onValueChange={(value) => updateSetting('episodeLayoutStyle', value ? 'horizontal' : 'vertical')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Integrations */}
|
|
||||||
<SettingsCard title="INTEGRATIONS">
|
|
||||||
<SettingItem
|
|
||||||
title="MDBList"
|
|
||||||
description={mdblistKeySet ? "Connected" : "Enable to add ratings & reviews"}
|
|
||||||
icon="star"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('MDBListSettings')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="TMDB"
|
|
||||||
description="Metadata provider"
|
|
||||||
icon="movie"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('TMDBSettings')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Media Sources"
|
|
||||||
description="Logo & image preferences"
|
|
||||||
icon="image"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('LogoSourceSettings')}
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Playback & Experience */}
|
|
||||||
<SettingsCard title="PLAYBACK">
|
|
||||||
<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-outline"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('PlayerSettings')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Notifications"
|
|
||||||
description="Episode reminders"
|
|
||||||
icon="notifications-none"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('NotificationSettings')}
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* About & Support */}
|
|
||||||
<SettingsCard title="ABOUT">
|
|
||||||
<SettingItem
|
|
||||||
title="Privacy Policy"
|
|
||||||
icon="lock"
|
|
||||||
onPress={() => Linking.openURL('https://github.com/Stremio/stremio-expo/blob/main/PRIVACY_POLICY.md')}
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Report Issue"
|
|
||||||
icon="bug-report"
|
|
||||||
onPress={() => Sentry.showFeedbackWidget()}
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Version"
|
|
||||||
description="1.0.0"
|
|
||||||
icon="info-outline"
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
|
|
||||||
{/* Developer Options - Only show in development */}
|
|
||||||
{__DEV__ && (
|
|
||||||
<SettingsCard title="DEVELOPER">
|
|
||||||
<SettingItem
|
|
||||||
title="Test Onboarding"
|
|
||||||
icon="play-circle-outline"
|
|
||||||
onPress={() => navigation.navigate('Onboarding')}
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Reset Onboarding"
|
|
||||||
icon="refresh"
|
|
||||||
onPress={async () => {
|
|
||||||
try {
|
|
||||||
await AsyncStorage.removeItem('hasCompletedOnboarding');
|
|
||||||
Alert.alert('Success', 'Onboarding has been reset. Restart the app to see the onboarding flow.');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('Error', 'Failed to reset onboarding.');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
|
||||||
title="Clear All Data"
|
|
||||||
icon="delete-forever"
|
|
||||||
onPress={() => {
|
|
||||||
Alert.alert(
|
|
||||||
'Clear All Data',
|
|
||||||
'This will reset all settings and clear all cached data. Are you sure?',
|
|
||||||
[
|
|
||||||
{ text: 'Cancel', style: 'cancel' },
|
|
||||||
{
|
|
||||||
text: 'Clear',
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: async () => {
|
|
||||||
try {
|
|
||||||
await AsyncStorage.clear();
|
|
||||||
Alert.alert('Success', 'All data cleared. Please restart the app.');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('Error', 'Failed to clear data.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Cache Management - Only show if MDBList is connected */}
|
|
||||||
{mdblistKeySet && (
|
|
||||||
<SettingsCard title="CACHE MANAGEMENT">
|
|
||||||
<SettingItem
|
|
||||||
title="Clear MDBList Cache"
|
|
||||||
icon="cached"
|
|
||||||
onPress={handleClearMDBListCache}
|
|
||||||
isLast={true}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
|
|
@ -538,6 +727,7 @@ const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
// Mobile styles
|
||||||
header: {
|
header: {
|
||||||
paddingHorizontal: Math.max(1, width * 0.05),
|
paddingHorizontal: Math.max(1, width * 0.05),
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
@ -566,10 +756,69 @@ const styles = StyleSheet.create({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
paddingBottom: 90,
|
paddingBottom: 90,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Tablet-specific styles
|
||||||
|
tabletContainer: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
width: 280,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: 'rgba(255,255,255,0.1)',
|
||||||
|
},
|
||||||
|
sidebarHeader: {
|
||||||
|
padding: 24,
|
||||||
|
paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) + 24 : 48,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: 'rgba(255,255,255,0.1)',
|
||||||
|
},
|
||||||
|
sidebarTitle: {
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0.3,
|
||||||
|
},
|
||||||
|
sidebarContent: {
|
||||||
|
flex: 1,
|
||||||
|
paddingTop: 16,
|
||||||
|
},
|
||||||
|
sidebarItem: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
paddingVertical: 16,
|
||||||
|
marginHorizontal: 12,
|
||||||
|
marginVertical: 2,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
sidebarItemActive: {
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
sidebarItemText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '500',
|
||||||
|
marginLeft: 16,
|
||||||
|
},
|
||||||
|
tabletContent: {
|
||||||
|
flex: 1,
|
||||||
|
paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) + 24 : 48,
|
||||||
|
},
|
||||||
|
tabletScrollView: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: 32,
|
||||||
|
},
|
||||||
|
tabletScrollContent: {
|
||||||
|
paddingBottom: 32,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Common card styles
|
||||||
cardContainer: {
|
cardContainer: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
|
tabletCardContainer: {
|
||||||
|
marginBottom: 32,
|
||||||
|
},
|
||||||
cardTitle: {
|
cardTitle: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
|
|
@ -577,6 +826,11 @@ const styles = StyleSheet.create({
|
||||||
marginLeft: Math.max(12, width * 0.04),
|
marginLeft: Math.max(12, width * 0.04),
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
|
tabletCardTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginLeft: 0,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
card: {
|
card: {
|
||||||
marginHorizontal: Math.max(12, width * 0.04),
|
marginHorizontal: Math.max(12, width * 0.04),
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
|
|
@ -586,7 +840,14 @@ const styles = StyleSheet.create({
|
||||||
shadowOpacity: 0.1,
|
shadowOpacity: 0.1,
|
||||||
shadowRadius: 4,
|
shadowRadius: 4,
|
||||||
elevation: 3,
|
elevation: 3,
|
||||||
width: undefined, // Let it fill the container width
|
width: undefined,
|
||||||
|
},
|
||||||
|
tabletCard: {
|
||||||
|
marginHorizontal: 0,
|
||||||
|
borderRadius: 20,
|
||||||
|
shadowOpacity: 0.15,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 5,
|
||||||
},
|
},
|
||||||
settingItem: {
|
settingItem: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
@ -597,6 +858,11 @@ const styles = StyleSheet.create({
|
||||||
minHeight: Math.max(54, width * 0.14),
|
minHeight: Math.max(54, width * 0.14),
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
|
tabletSettingItem: {
|
||||||
|
paddingVertical: 16,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
minHeight: 70,
|
||||||
|
},
|
||||||
settingItemBorder: {
|
settingItemBorder: {
|
||||||
// Border styling handled directly in the component with borderBottomWidth
|
// Border styling handled directly in the component with borderBottomWidth
|
||||||
},
|
},
|
||||||
|
|
@ -608,6 +874,12 @@ const styles = StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
tabletSettingIconContainer: {
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 12,
|
||||||
|
marginRight: 20,
|
||||||
|
},
|
||||||
settingContent: {
|
settingContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
@ -621,10 +893,19 @@ const styles = StyleSheet.create({
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
marginBottom: 3,
|
marginBottom: 3,
|
||||||
},
|
},
|
||||||
|
tabletSettingTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
settingDescription: {
|
settingDescription: {
|
||||||
fontSize: Math.min(14, width * 0.037),
|
fontSize: Math.min(14, width * 0.037),
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
},
|
},
|
||||||
|
tabletSettingDescription: {
|
||||||
|
fontSize: 16,
|
||||||
|
opacity: 0.7,
|
||||||
|
},
|
||||||
settingControl: {
|
settingControl: {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue