user can now customzie rows in of catalogscreen.

This commit is contained in:
tapframe 2025-08-13 02:16:47 +05:30
parent 408b1cb366
commit 375ea61b37
2 changed files with 136 additions and 35 deletions

View file

@ -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<CatalogScreenProps> = ({ 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<CatalogScreenProps> = ({ 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<CatalogScreenProps> = ({ 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<CatalogScreenProps> = ({ route, navigation }) => {
transition={0}
allowDownscaling
/>
<View style={styles.itemContent}>
<Text
style={styles.title}
numberOfLines={2}
>
{item.name}
</Text>
{item.releaseInfo && (
<Text style={styles.releaseInfo}>
{item.releaseInfo}
</Text>
)}
</View>
</TouchableOpacity>
);
}, [navigation, styles, screenData.numColumns, screenData.itemWidth]);
}, [navigation, styles, effectiveNumColumns, effectiveItemWidth]);
const renderEmptyState = () => (
<View style={styles.centered}>
@ -681,8 +687,8 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
data={items}
renderItem={renderItem}
keyExtractor={(item) => `${item.id}-${item.type}`}
numColumns={screenData.numColumns}
key={screenData.numColumns}
numColumns={effectiveNumColumns}
key={effectiveNumColumns}
refreshControl={
<RefreshControl
refreshing={refreshing}

View file

@ -51,6 +51,7 @@ interface GroupedCatalogs {
const CATALOG_SETTINGS_KEY = 'catalog_settings';
const CATALOG_CUSTOM_NAMES_KEY = 'catalog_custom_names';
const CATALOG_MOBILE_COLUMNS_KEY = 'catalog_mobile_columns';
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
// Create a styles creator function that accepts the theme colors
@ -133,6 +134,33 @@ const createStyles = (colors: any) => 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<CatalogSetting[]>([]);
const [groupedSettings, setGroupedSettings] = useState<GroupedCatalogs>({});
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 = () => {
<Text style={styles.headerTitle}>Catalogs</Text>
<ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContent}>
{/* Layout (Mobile only) */}
{Platform.OS && (
<View style={styles.addonSection}>
<Text style={styles.addonTitle}>LAYOUT CATALOGSCREEN (PHONE)</Text>
<View style={styles.card}>
<View style={styles.groupHeader}>
<Text style={styles.groupTitle}>Posters per row</Text>
<View style={styles.groupHeaderRight} />
</View>
{/* Only show on phones (approx width < 600) */}
<View style={styles.optionRow}>
<TouchableOpacity
style={[styles.optionChip, mobileColumns === 'auto' && styles.optionChipSelected]}
onPress={async () => {
try {
await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, 'auto');
setMobileColumns('auto');
} catch {}
}}
activeOpacity={0.7}
>
<Text style={[styles.optionChipText, mobileColumns === 'auto' && styles.optionChipTextSelected]}>Auto</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.optionChip, mobileColumns === 2 && styles.optionChipSelected]}
onPress={async () => {
try {
await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '2');
setMobileColumns(2);
} catch {}
}}
activeOpacity={0.7}
>
<Text style={[styles.optionChipText, mobileColumns === 2 && styles.optionChipTextSelected]}>2</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.optionChip, mobileColumns === 3 && styles.optionChipSelected]}
onPress={async () => {
try {
await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '3');
setMobileColumns(3);
} catch {}
}}
activeOpacity={0.7}
>
<Text style={[styles.optionChipText, mobileColumns === 3 && styles.optionChipTextSelected]}>3</Text>
</TouchableOpacity>
</View>
<View style={styles.hintRow}>
<MaterialIcons name="info-outline" size={14} color={colors.mediumGray} />
<Text style={styles.hintText}>Applies to phones only. Tablets keep adaptive layout.</Text>
</View>
</View>
</View>
)}
{Object.entries(groupedSettings).map(([addonId, group]) => (
<View key={addonId} style={styles.addonSection}>
<Text style={styles.addonTitle}>