diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7f50648..388fa8c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,10 @@ "Bash(yarn prebuild-tv:*)", "Bash(mkdir:*)", "Bash(yarn lint:*)", - "Bash(yarn add:*)" + "Bash(yarn add:*)", + "Bash(git reset:*)", + "Bash(git add:*)", + "Bash(git commit:*)" ], "deny": [] } diff --git a/app/index.tsx b/app/index.tsx index 0ba5409..60d1220 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useCallback, useRef, useState } from "react"; -import { View, StyleSheet, ActivityIndicator, FlatList, Pressable, Animated } from "react-native"; +import { View, StyleSheet, ActivityIndicator, FlatList, Pressable, Animated, StatusBar } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; import { ThemedView } from "@/components/ThemedView"; import { ThemedText } from "@/components/ThemedText"; import { api } from "@/services/api"; @@ -21,6 +22,7 @@ export default function HomeScreen() { const colorScheme = "dark"; const [selectedTag, setSelectedTag] = useState(null); const fadeAnim = useRef(new Animated.Value(0)).current; + const insets = useSafeAreaInsets(); // 响应式布局配置 const responsiveConfig = useResponsiveLayout(); @@ -169,7 +171,7 @@ export default function HomeScreen() { const dynamicStyles = StyleSheet.create({ container: { flex: 1, - paddingTop: deviceType === 'mobile' ? 0 : 40, + paddingTop: deviceType === 'mobile' ? insets.top : 40, }, headerContainer: { flexDirection: "row", @@ -198,10 +200,10 @@ export default function HomeScreen() { paddingHorizontal: spacing, }, categoryButton: { - paddingHorizontal: spacing / 2, - paddingVertical: spacing / 2, + paddingHorizontal: deviceType === 'tv' ? spacing / 4 : spacing / 2, + paddingVertical: deviceType === 'tv' ? spacing / 4 : spacing / 2, borderRadius: deviceType === 'mobile' ? 6 : 8, - marginHorizontal: spacing / 2, + marginHorizontal: deviceType === 'tv' ? spacing / 4 : spacing / 2, }, categoryText: { fontSize: deviceType === 'mobile' ? 14 : 16, @@ -214,6 +216,9 @@ export default function HomeScreen() { const content = ( + {/* 状态栏 */} + {deviceType === 'mobile' && } + {/* 顶部导航 */} {renderHeader()} diff --git a/components/CustomScrollView.tsx b/components/CustomScrollView.tsx index 2f31545..23b5492 100644 --- a/components/CustomScrollView.tsx +++ b/components/CustomScrollView.tsx @@ -93,38 +93,35 @@ const CustomScrollView: React.FC = ({ const dynamicStyles = StyleSheet.create({ listContent: { paddingBottom: responsiveConfig.spacing * 2, + paddingHorizontal: responsiveConfig.spacing / 2, }, - rowContainer: { + gridContainer: { flexDirection: "row", - justifyContent: responsiveConfig.deviceType === 'mobile' ? "space-around" : "flex-start", flexWrap: "wrap", - marginBottom: responsiveConfig.spacing / 2, + justifyContent: "space-evenly", }, itemContainer: { - marginHorizontal: responsiveConfig.spacing / 2, - alignItems: "center", + width: responsiveConfig.cardWidth, + marginBottom: responsiveConfig.spacing, }, }); return ( {data.length > 0 ? ( <> - {/* Render content in a responsive grid layout */} - {Array.from({ length: Math.ceil(data.length / effectiveColumns) }).map((_, rowIndex) => ( - - {data.slice(rowIndex * effectiveColumns, (rowIndex + 1) * effectiveColumns).map((item, index) => ( - - {renderItem({ item, index: rowIndex * effectiveColumns + index })} - - ))} - - ))} + + {data.map((item, index) => ( + + {renderItem({ item, index })} + + ))} + {renderFooter()} ) : ( diff --git a/components/VideoCard.mobile.tsx b/components/VideoCard.mobile.tsx index 230fdd8..dfa2dca 100644 --- a/components/VideoCard.mobile.tsx +++ b/components/VideoCard.mobile.tsx @@ -179,7 +179,6 @@ const createMobileStyles = (cardWidth: number, cardHeight: number, spacing: numb return StyleSheet.create({ wrapper: { width: cardWidth, - marginHorizontal: spacing / 2, marginBottom: spacing, }, pressable: { diff --git a/components/navigation/MobileTabContainer.tsx b/components/navigation/MobileTabContainer.tsx new file mode 100644 index 0000000..a09ef8d --- /dev/null +++ b/components/navigation/MobileTabContainer.tsx @@ -0,0 +1,139 @@ +import React, { useState } from 'react'; +import { View, StyleSheet, TouchableOpacity, Text, Platform } from 'react-native'; +import { useRouter, usePathname } from 'expo-router'; +import { Home, Search, Heart, Settings, Tv } from 'lucide-react-native'; +import { Colors } from '@/constants/Colors'; +import { useResponsiveLayout } from '@/hooks/useResponsiveLayout'; +import { DeviceUtils } from '@/utils/DeviceUtils'; + +interface TabItem { + key: string; + label: string; + icon: React.ComponentType; + route: string; +} + +const tabs: TabItem[] = [ + { key: 'home', label: '首页', icon: Home, route: '/' }, + { key: 'search', label: '搜索', icon: Search, route: '/search' }, + { key: 'live', label: '直播', icon: Tv, route: '/live' }, + { key: 'favorites', label: '收藏', icon: Heart, route: '/favorites' }, + { key: 'settings', label: '设置', icon: Settings, route: '/settings' }, +]; + +interface MobileTabContainerProps { + children: React.ReactNode; +} + +const MobileTabContainer: React.FC = ({ children }) => { + const router = useRouter(); + const pathname = usePathname(); + const { spacing } = useResponsiveLayout(); + + const handleTabPress = (route: string) => { + if (route === '/') { + router.push('/'); + } else { + router.push(route as any); + } + }; + + const isTabActive = (route: string) => { + if (route === '/' && pathname === '/') return true; + if (route !== '/' && pathname === route) return true; + return false; + }; + + const dynamicStyles = createStyles(spacing); + + return ( + + {/* 内容区域 */} + + {children} + + + {/* 底部导航栏 */} + + {tabs.map((tab) => { + const isActive = isTabActive(tab.route); + const IconComponent = tab.icon; + + return ( + handleTabPress(tab.route)} + activeOpacity={0.7} + > + + + {tab.label} + + + ); + })} + + + ); +}; + +const createStyles = (spacing: number) => { + const minTouchTarget = DeviceUtils.getMinTouchTargetSize(); + + return StyleSheet.create({ + container: { + flex: 1, + }, + content: { + flex: 1, + }, + tabBar: { + flexDirection: 'row', + backgroundColor: '#1c1c1e', + borderTopWidth: 1, + borderTopColor: '#333', + paddingTop: spacing / 2, + paddingBottom: Platform.OS === 'ios' ? spacing * 2 : spacing, + paddingHorizontal: spacing, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: -2, + }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 10, + }, + tab: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + minHeight: minTouchTarget, + paddingVertical: spacing / 2, + borderRadius: 8, + }, + activeTab: { + backgroundColor: 'rgba(64, 156, 255, 0.1)', + }, + tabLabel: { + fontSize: 11, + color: '#888', + marginTop: 2, + fontWeight: '500', + }, + activeTabLabel: { + color: Colors.dark.primary, + fontWeight: '600', + }, + }); +}; + +export default MobileTabContainer; \ No newline at end of file diff --git a/components/navigation/ResponsiveNavigation.tsx b/components/navigation/ResponsiveNavigation.tsx index afb4078..3a4d2d5 100644 --- a/components/navigation/ResponsiveNavigation.tsx +++ b/components/navigation/ResponsiveNavigation.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; import { useResponsiveLayout } from '@/hooks/useResponsiveLayout'; -import MobileBottomTabNavigator from './MobileBottomTabNavigator'; +import MobileTabContainer from './MobileTabContainer'; import TabletSidebarNavigator from './TabletSidebarNavigator'; interface ResponsiveNavigationProps { @@ -13,14 +13,8 @@ const ResponsiveNavigation: React.FC = ({ children }) switch (deviceType) { case 'mobile': - return ( - - - {children} - - - - ); + // 移动端使用Tab容器包装children + return {children}; case 'tablet': return ( diff --git a/hooks/useResponsiveLayout.ts b/hooks/useResponsiveLayout.ts index 7123610..bd95d93 100644 --- a/hooks/useResponsiveLayout.ts +++ b/hooks/useResponsiveLayout.ts @@ -38,14 +38,15 @@ const getLayoutConfig = (deviceType: DeviceType, width: number, height: number, switch (deviceType) { case 'mobile': - columns = isPortrait ? 2 : 3; - cardWidth = (width - spacing * (columns + 1)) / columns; - cardHeight = cardWidth * 1.5; // 2:3 aspect ratio + columns = isPortrait ? 3 : 4; + // 使用flex布局,卡片可以更大一些来填充空间 + cardWidth = (width - spacing) / columns * 0.85; // 增大到85% + cardHeight = cardWidth * 1.2; // 5:6 aspect ratio (reduced from 2:3) break; case 'tablet': columns = isPortrait ? 3 : 4; - cardWidth = (width - spacing * (columns + 1)) / columns; + cardWidth = (width - spacing) / columns * 0.85; // 增大到85% cardHeight = cardWidth * 1.4; // slightly less tall ratio break;