feat: Enhance mobile and tablet support with responsive layout adjustments and new navigation components

This commit is contained in:
zimplexing
2025-08-01 16:36:28 +08:00
parent 942703509e
commit 9e9e4597cc
35 changed files with 4082 additions and 634 deletions

View File

@@ -0,0 +1,129 @@
import { useState, useEffect } from 'react';
import { Dimensions, Platform } from 'react-native';
export type DeviceType = 'mobile' | 'tablet' | 'tv';
export interface ResponsiveConfig {
deviceType: DeviceType;
columns: number;
cardWidth: number;
cardHeight: number;
spacing: number;
isPortrait: boolean;
screenWidth: number;
screenHeight: number;
}
const BREAKPOINTS = {
mobile: { min: 0, max: 767 },
tablet: { min: 768, max: 1023 },
tv: { min: 1024, max: Infinity }
};
const getDeviceType = (width: number): DeviceType => {
const isTV = process.env.EXPO_TV === '1' || Platform.isTV;
if (isTV) return 'tv';
if (width >= BREAKPOINTS.tv.min) return 'tv';
if (width >= BREAKPOINTS.tablet.min) return 'tablet';
return 'mobile';
};
const getLayoutConfig = (deviceType: DeviceType, width: number, height: number, isPortrait: boolean): ResponsiveConfig => {
const spacing = deviceType === 'mobile' ? 8 : deviceType === 'tablet' ? 12 : 16;
let columns: number;
let cardWidth: number;
let cardHeight: number;
switch (deviceType) {
case 'mobile':
columns = isPortrait ? 2 : 3;
cardWidth = (width - spacing * (columns + 1)) / columns;
cardHeight = cardWidth * 1.5; // 2:3 aspect ratio
break;
case 'tablet':
columns = isPortrait ? 3 : 4;
cardWidth = (width - spacing * (columns + 1)) / columns;
cardHeight = cardWidth * 1.4; // slightly less tall ratio
break;
case 'tv':
default:
columns = 5;
cardWidth = 160; // Fixed width for TV
cardHeight = 240; // Fixed height for TV
break;
}
return {
deviceType,
columns,
cardWidth,
cardHeight,
spacing,
isPortrait,
screenWidth: width,
screenHeight: height,
};
};
export const useResponsiveLayout = (): ResponsiveConfig => {
const [dimensions, setDimensions] = useState(() => {
const { width, height } = Dimensions.get('window');
return { width, height };
});
useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => {
setDimensions({ width: window.width, height: window.height });
});
return () => subscription?.remove();
}, []);
const { width, height } = dimensions;
const isPortrait = height > width;
const deviceType = getDeviceType(width);
return getLayoutConfig(deviceType, width, height, isPortrait);
};
// Utility hook for responsive values
export const useResponsiveValue = <T>(values: { mobile: T; tablet: T; tv: T }): T => {
const { deviceType } = useResponsiveLayout();
return values[deviceType];
};
// Utility hook for responsive styles
export const useResponsiveStyles = () => {
const config = useResponsiveLayout();
return {
// Common responsive styles
container: {
paddingHorizontal: config.spacing,
},
// Card styles
cardContainer: {
width: config.cardWidth,
height: config.cardHeight,
marginBottom: config.spacing,
},
// Grid styles
gridContainer: {
paddingHorizontal: config.spacing / 2,
},
// Typography
titleFontSize: config.deviceType === 'mobile' ? 18 : config.deviceType === 'tablet' ? 22 : 28,
bodyFontSize: config.deviceType === 'mobile' ? 14 : config.deviceType === 'tablet' ? 16 : 18,
// Spacing
sectionSpacing: config.deviceType === 'mobile' ? 16 : config.deviceType === 'tablet' ? 20 : 24,
itemSpacing: config.spacing,
};
};