improved layout for tablets

This commit is contained in:
tapframe 2025-08-11 14:15:05 +05:30
parent 69e5141c58
commit 63c673bfae
2 changed files with 543 additions and 247 deletions

View file

@ -66,6 +66,20 @@ interface TraktFolder {
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 [posterUrl, setPosterUrl] = useState<string | null>(null);
@ -146,7 +160,7 @@ const TraktItem = React.memo(({ item, width, navigation, currentTheme }: { item:
const SkeletonLoader = () => {
const pulseAnim = React.useRef(new RNAnimated.Value(0)).current;
const { width } = useWindowDimensions();
const itemWidth = (width - 48) / 2;
const { numColumns, itemWidth } = getGridLayout(width);
const { currentTheme } = useTheme();
React.useEffect(() => {
@ -190,10 +204,12 @@ const SkeletonLoader = () => {
</View>
);
// Render enough skeletons for at least two rows
const skeletonCount = numColumns * 2;
return (
<View style={styles.skeletonContainer}>
{[...Array(6)].map((_, index) => (
<View key={index} style={{ width: itemWidth, margin: 8 }}>
{Array.from({ length: skeletonCount }).map((_, index) => (
<View key={index} style={{ width: itemWidth, marginBottom: 16 }}>
{renderSkeletonItem()}
</View>
))}
@ -205,6 +221,7 @@ const LibraryScreen = () => {
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const isDarkMode = useColorScheme() === 'dark';
const { width } = useWindowDimensions();
const { numColumns, itemWidth } = useMemo(() => getGridLayout(width), [width]);
const [loading, setLoading] = useState(true);
const [libraryItems, setLibraryItems] = useState<LibraryItem[]>([]);
const [filter, setFilter] = useState<'all' | 'movies' | 'series'>('all');
@ -329,8 +346,6 @@ const LibraryScreen = () => {
return folders.filter(folder => folder.itemCount > 0);
}, [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 }) => (
<TouchableOpacity
style={[styles.itemContainer, { width: itemWidth }]}
@ -717,7 +732,7 @@ const LibraryScreen = () => {
data={traktFolders}
renderItem={({ item }) => renderTraktCollectionFolder({ folder: item })}
keyExtractor={item => item.id}
numColumns={2}
numColumns={numColumns}
contentContainerStyle={styles.listContainer}
showsVerticalScrollIndicator={false}
columnWrapperStyle={styles.columnWrapper}
@ -760,7 +775,7 @@ const LibraryScreen = () => {
data={folderItems}
renderItem={({ item }) => renderTraktItem({ item })}
keyExtractor={(item) => `${item.type}-${item.id}`}
numColumns={2}
numColumns={numColumns}
columnWrapperStyle={styles.row}
style={styles.traktContainer}
contentContainerStyle={{ paddingBottom: insets.bottom + 80 }}
@ -856,7 +871,7 @@ const LibraryScreen = () => {
return renderItem({ item: item as LibraryItem });
}}
keyExtractor={item => item.id}
numColumns={2}
numColumns={numColumns}
contentContainerStyle={styles.listContainer}
showsVerticalScrollIndicator={false}
columnWrapperStyle={styles.columnWrapper}

View file

@ -31,34 +31,53 @@ import { catalogService } from '../services/catalogService';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
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;
// 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
interface SettingsCardProps {
children: React.ReactNode;
title?: string;
isTablet?: boolean;
}
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title }) => {
const SettingsCard: React.FC<SettingsCardProps> = ({ children, title, isTablet = false }) => {
const { currentTheme } = useTheme();
return (
<View
style={[styles.cardContainer]}
style={[
styles.cardContainer,
isTablet && styles.tabletCardContainer
]}
>
{title && (
<Text style={[
styles.cardTitle,
{ color: currentTheme.colors.mediumEmphasis }
{ color: currentTheme.colors.mediumEmphasis },
isTablet && styles.tabletCardTitle
]}>
{title}
</Text>
)}
<View style={[
styles.card,
{ backgroundColor: currentTheme.colors.elevation1 }
{ backgroundColor: currentTheme.colors.elevation1 },
isTablet && styles.tabletCard
]}>
{children}
</View>
@ -74,6 +93,7 @@ interface SettingItemProps {
isLast?: boolean;
onPress?: () => void;
badge?: string | number;
isTablet?: boolean;
}
const SettingItem: React.FC<SettingItemProps> = ({
@ -83,7 +103,8 @@ const SettingItem: React.FC<SettingItemProps> = ({
renderControl,
isLast = false,
onPress,
badge
badge,
isTablet = false
}) => {
const { currentTheme } = useTheme();
@ -94,22 +115,36 @@ const SettingItem: React.FC<SettingItemProps> = ({
style={[
styles.settingItem,
!isLast && styles.settingItemBorder,
{ borderBottomColor: currentTheme.colors.elevation2 }
{ borderBottomColor: currentTheme.colors.elevation2 },
isTablet && styles.tabletSettingItem
]}
>
<View style={[
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 style={styles.settingContent}>
<View style={styles.settingTextContainer}>
<Text style={[styles.settingTitle, { color: currentTheme.colors.highEmphasis }]}>
<Text style={[
styles.settingTitle,
{ color: currentTheme.colors.highEmphasis },
isTablet && styles.tabletSettingTitle
]}>
{title}
</Text>
{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}
</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 { settings, updateSetting } = useSettings();
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
@ -138,6 +229,9 @@ const SettingsScreen: React.FC = () => {
const insets = useSafeAreaInsets();
const { user, signOut } = useAccount();
// Tablet-specific state
const [selectedCategory, setSelectedCategory] = useState('account');
// Add a useEffect to check authentication status on focus
useEffect(() => {
// This will reload the Trakt auth status whenever the settings screen is focused
@ -267,15 +361,328 @@ const SettingsScreen: React.FC = () => {
const ChevronRight = () => (
<MaterialIcons
name="chevron-right"
size={20}
size={isTablet ? 24 : 20}
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 topSpacing = Platform.OS === 'android' ? (StatusBar.currentHeight || 0) : insets.top;
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 (
<View style={[
styles.container,
@ -295,232 +702,14 @@ const SettingsScreen: React.FC = () => {
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
{/* Account Section */}
<SettingsCard title="ACCOUNT">
{user ? (
<>
<SettingItem
title={user.displayName || user.email || user.id}
description="Manage account"
icon="account-circle"
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>
)}
{renderCategoryContent('account')}
{renderCategoryContent('content')}
{renderCategoryContent('appearance')}
{renderCategoryContent('integrations')}
{renderCategoryContent('playback')}
{renderCategoryContent('about')}
{renderCategoryContent('developer')}
{renderCategoryContent('cache')}
<View style={styles.footer}>
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
@ -538,6 +727,7 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
// Mobile styles
header: {
paddingHorizontal: Math.max(1, width * 0.05),
flexDirection: 'row',
@ -566,10 +756,69 @@ const styles = StyleSheet.create({
width: '100%',
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: {
width: '100%',
marginBottom: 20,
},
tabletCardContainer: {
marginBottom: 32,
},
cardTitle: {
fontSize: 13,
fontWeight: '600',
@ -577,6 +826,11 @@ const styles = StyleSheet.create({
marginLeft: Math.max(12, width * 0.04),
marginBottom: 8,
},
tabletCardTitle: {
fontSize: 14,
marginLeft: 0,
marginBottom: 12,
},
card: {
marginHorizontal: Math.max(12, width * 0.04),
borderRadius: 16,
@ -586,7 +840,14 @@ const styles = StyleSheet.create({
shadowOpacity: 0.1,
shadowRadius: 4,
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: {
flexDirection: 'row',
@ -597,6 +858,11 @@ const styles = StyleSheet.create({
minHeight: Math.max(54, width * 0.14),
width: '100%',
},
tabletSettingItem: {
paddingVertical: 16,
paddingHorizontal: 24,
minHeight: 70,
},
settingItemBorder: {
// Border styling handled directly in the component with borderBottomWidth
},
@ -608,6 +874,12 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
tabletSettingIconContainer: {
width: 44,
height: 44,
borderRadius: 12,
marginRight: 20,
},
settingContent: {
flex: 1,
flexDirection: 'row',
@ -621,10 +893,19 @@ const styles = StyleSheet.create({
fontWeight: '500',
marginBottom: 3,
},
tabletSettingTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 4,
},
settingDescription: {
fontSize: Math.min(14, width * 0.037),
opacity: 0.8,
},
tabletSettingDescription: {
fontSize: 16,
opacity: 0.7,
},
settingControl: {
justifyContent: 'center',
alignItems: 'center',