diff --git a/src/screens/CatalogScreen.tsx b/src/screens/CatalogScreen.tsx index f17d8361..a19cae87 100644 --- a/src/screens/CatalogScreen.tsx +++ b/src/screens/CatalogScreen.tsx @@ -21,6 +21,7 @@ import { Image } from 'expo-image'; import { MaterialIcons } from '@expo/vector-icons'; import { logger } from '../utils/logger'; import { useCustomCatalogNames } from '../hooks/useCustomCatalogNames'; +import AsyncStorage from '@react-native-async-storage/async-storage'; import { catalogService, DataSource, StreamingContent } from '../services/catalogService'; type CatalogScreenProps = { @@ -143,20 +144,7 @@ const createStyles = (colors: any) => StyleSheet.create({ borderTopRightRadius: 12, backgroundColor: colors.elevation3, }, - itemContent: { - padding: SPACING.sm, - }, - title: { - fontSize: 14, - fontWeight: '600', - color: colors.white, - lineHeight: 18, - }, - releaseInfo: { - fontSize: 12, - marginTop: SPACING.xs, - color: colors.mediumGray, - }, + // removed bottom text container; keep spacing via item margin only footer: { padding: SPACING.lg, alignItems: 'center', @@ -223,19 +211,33 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { ...calculateCatalogLayout(width) }; }); + const [mobileColumnsPref, setMobileColumnsPref] = useState<'auto' | 2 | 3>('auto'); const { currentTheme } = useTheme(); const colors = currentTheme.colors; const styles = createStyles(colors); const isDarkMode = true; const isInitialRender = React.useRef(true); + // Load mobile columns preference (phones only) + useEffect(() => { + (async () => { + try { + const pref = await AsyncStorage.getItem('catalog_mobile_columns'); + if (pref === '2') setMobileColumnsPref(2); + else if (pref === '3') setMobileColumnsPref(3); + else setMobileColumnsPref('auto'); + } catch {} + })(); + }, []); + // Handle screen dimension changes useEffect(() => { const subscription = Dimensions.addEventListener('change', ({ window }) => { - setScreenData({ + const base = calculateCatalogLayout(window.width); + setScreenData(prev => ({ width: window.width, - ...calculateCatalogLayout(window.width) - }); + ...base + })); }); return () => subscription?.remove(); @@ -542,9 +544,26 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { } }, [loading, paginating, hasMore, page, loadItems, refreshing]); + const effectiveNumColumns = React.useMemo(() => { + const isPhone = screenData.width < 600; // basic breakpoint; tablets generally above this + if (!isPhone || mobileColumnsPref === 'auto') return screenData.numColumns; + // clamp to 2 or 3 on phones + return mobileColumnsPref === 2 ? 2 : 3; + }, [screenData.width, screenData.numColumns, mobileColumnsPref]); + + const effectiveItemWidth = React.useMemo(() => { + if (effectiveNumColumns === screenData.numColumns) return screenData.itemWidth; + // recompute width for custom columns on mobile to maintain spacing roughly similar + const HORIZONTAL_PADDING = 16 * 2; // SPACING.lg * 2 + const ITEM_SPACING = 8; // SPACING.sm + const availableWidth = screenData.width - HORIZONTAL_PADDING; + const totalSpacing = ITEM_SPACING * (effectiveNumColumns - 1); + return (availableWidth - totalSpacing) / effectiveNumColumns; + }, [effectiveNumColumns, screenData.width, screenData.itemWidth]); + const renderItem = useCallback(({ item, index }: { item: Meta; index: number }) => { // Calculate if this is the last item in a row - const isLastInRow = (index + 1) % screenData.numColumns === 0; + const isLastInRow = (index + 1) % effectiveNumColumns === 0; // For proper spacing const rightMargin = isLastInRow ? 0 : SPACING.sm; @@ -554,7 +573,7 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { styles.item, { marginRight: rightMargin, - width: screenData.itemWidth + width: effectiveItemWidth } ]} onPress={() => navigation.navigate('Metadata', { id: item.id, type: item.type, addonId })} @@ -568,22 +587,9 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { transition={0} allowDownscaling /> - - - {item.name} - - {item.releaseInfo && ( - - {item.releaseInfo} - - )} - ); - }, [navigation, styles, screenData.numColumns, screenData.itemWidth]); + }, [navigation, styles, effectiveNumColumns, effectiveItemWidth]); const renderEmptyState = () => ( @@ -681,8 +687,8 @@ const CatalogScreen: React.FC = ({ route, navigation }) => { data={items} renderItem={renderItem} keyExtractor={(item) => `${item.id}-${item.type}`} - numColumns={screenData.numColumns} - key={screenData.numColumns} + numColumns={effectiveNumColumns} + key={effectiveNumColumns} refreshControl={ StyleSheet.create({ groupHeaderRight: { flexDirection: 'row', alignItems: 'center', + }, + optionRow: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 12, + gap: 8, + }, + optionChip: { + paddingHorizontal: 12, + paddingVertical: 8, + borderRadius: 18, + borderWidth: 1, + borderColor: 'rgba(255,255,255,0.15)', + backgroundColor: 'transparent', + }, + optionChipSelected: { + backgroundColor: colors.primary, + borderColor: colors.primary, + }, + optionChipText: { + color: colors.mediumGray, + fontSize: 13, + fontWeight: '600', + }, + optionChipTextSelected: { + color: colors.white, }, hintRow: { flexDirection: 'row', @@ -224,6 +252,7 @@ const CatalogSettingsScreen = () => { const [loading, setLoading] = useState(true); const [settings, setSettings] = useState([]); const [groupedSettings, setGroupedSettings] = useState({}); + const [mobileColumns, setMobileColumns] = useState<'auto' | 2 | 3>('auto'); const navigation = useNavigation(); const { refreshCatalogs } = useCatalogContext(); const { currentTheme } = useTheme(); @@ -320,6 +349,16 @@ const CatalogSettingsScreen = () => { setSettings(availableCatalogs); setGroupedSettings(grouped); + + // Load mobile columns preference (phones only) + try { + const pref = await AsyncStorage.getItem(CATALOG_MOBILE_COLUMNS_KEY); + if (pref === '2') setMobileColumns(2); + else if (pref === '3') setMobileColumns(3); + else setMobileColumns('auto'); + } catch (e) { + // ignore + } } catch (error) { logger.error('Failed to load catalog settings:', error); } finally { @@ -470,6 +509,62 @@ const CatalogSettingsScreen = () => { Catalogs + {/* Layout (Mobile only) */} + {Platform.OS && ( + + LAYOUT CATALOGSCREEN (PHONE) + + + Posters per row + + + {/* Only show on phones (approx width < 600) */} + + { + try { + await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, 'auto'); + setMobileColumns('auto'); + } catch {} + }} + activeOpacity={0.7} + > + Auto + + { + try { + await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '2'); + setMobileColumns(2); + } catch {} + }} + activeOpacity={0.7} + > + 2 + + { + try { + await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '3'); + setMobileColumns(3); + } catch {} + }} + activeOpacity={0.7} + > + 3 + + + + + Applies to phones only. Tablets keep adaptive layout. + + + + )} + {Object.entries(groupedSettings).map(([addonId, group]) => (