mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-13 19:24:44 +08:00
129 lines
3.4 KiB
TypeScript
129 lines
3.4 KiB
TypeScript
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,
|
|
};
|
|
}; |