From 4963c9dcb56d419299982c8a2c7dd9315f434c69 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 11 Aug 2025 19:24:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(tabs):=20=E6=B7=BB=E5=8A=A0=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E9=A1=B5=E9=9D=A2=E6=A0=87=E7=AD=BE=E5=B9=B6=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=A0=87=E7=AD=BE=E6=A0=8F=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增个人页面标签和对应的页面文件 - 将标签栏布局从垂直改为水平排列 - 隐藏顶部标题栏以提供更简洁的界面 - 调整选中状态下的内边距和间距 - 将界面文本本地化为中文 - 在图标映射中添加person图标支持 --- app/(tabs)/_layout.tsx | 53 ++++++++--- app/(tabs)/index.tsx | 168 ++++++++++++++++++++++------------- app/(tabs)/personal.tsx | 110 +++++++++++++++++++++++ components/SearchBox.tsx | 61 +++++++++++++ components/WorkoutCard.tsx | 124 ++++++++++++++++++++++++++ components/ui/IconSymbol.tsx | 3 +- 6 files changed, 444 insertions(+), 75 deletions(-) create mode 100644 app/(tabs)/personal.tsx create mode 100644 components/SearchBox.tsx create mode 100644 components/WorkoutCard.tsx diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 1e1ebf8..189f693 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -17,6 +17,7 @@ export default function TabLayout() { pathname.includes(routeName); return { + headerShown: false, tabBarActiveTintColor: '#192126', tabBarButton: (props) => { const { children, onPress } = props; @@ -35,14 +36,13 @@ export default function TabLayout() { flex: 1, alignItems: 'center', justifyContent: 'center', - flexDirection: 'column', + flexDirection: 'row', marginHorizontal: 6, marginVertical: 10, borderRadius: 25, backgroundColor: isSelected ? '#BBF246' : 'transparent', - paddingHorizontal: isSelected ? 24 : 12, + paddingHorizontal: isSelected ? 16 : 8, paddingVertical: 8, - minWidth: isSelected ? 140 : 48, }} > {children} @@ -81,11 +81,11 @@ export default function TabLayout() { { const isHomeSelected = pathname === '/' || pathname === '/index'; return ( - + {isHomeSelected && ( - Home + 首页 )} @@ -110,11 +109,11 @@ export default function TabLayout() { { const isExploreSelected = pathname === '/explore'; return ( - + {isExploreSelected && ( - Explore + 探索 + + )} + + ); + }, + }} + /> + + { + const isPersonalSelected = pathname === '/personal'; + return ( + + + {isPersonalSelected && ( + + 个人 )} diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 462e8cd..9ad8d4f 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,75 +1,121 @@ -import { Image } from 'expo-image'; -import { Platform, StyleSheet } from 'react-native'; - -import { HelloWave } from '@/components/HelloWave'; -import ParallaxScrollView from '@/components/ParallaxScrollView'; +import { SearchBox } from '@/components/SearchBox'; import { ThemedText } from '@/components/ThemedText'; import { ThemedView } from '@/components/ThemedView'; +import { WorkoutCard } from '@/components/WorkoutCard'; +import React from 'react'; +import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native'; + +const workoutData = [ + { + id: 1, + title: '体态评估', + duration: 50, + imageSource: require('@/assets/images/react-logo.png'), + }, + { + id: 2, + title: 'Hand\nTraining', + calories: 600, + duration: 40, + imageSource: require('@/assets/images/react-logo.png'), + }, + { + id: 3, + title: 'Core\nWorkout', + calories: 450, + duration: 35, + imageSource: require('@/assets/images/react-logo.png'), + }, +]; export default function HomeScreen() { return ( - - }> - - Welcome! - + + + + {/* Header Section */} + + Good Morning 🔥 + Pramuditya Uzumaki + + + {/* Search Box */} + + + {/* Popular Workouts Section */} + + Popular Workouts + + + {workoutData.map((workout) => ( + console.log(`Pressed ${workout.title}`)} + /> + ))} + + + + {/* Add some spacing at the bottom */} + + - - Step 1: Try it - - Edit app/(tabs)/index.tsx to see changes. - Press{' '} - - {Platform.select({ - ios: 'cmd + d', - android: 'cmd + m', - web: 'F12', - })} - {' '} - to open developer tools. - - - - Step 2: Explore - - {`Tap the Explore tab to learn more about what's included in this starter app.`} - - - - Step 3: Get a fresh start - - {`When you're ready, run `} - npm run reset-project to get a fresh{' '} - app directory. This will move the current{' '} - app to{' '} - app-example. - - - + ); } const styles = StyleSheet.create({ - titleContainer: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, + safeArea: { + flex: 1, + backgroundColor: '#F7F8FA', }, - stepContainer: { - gap: 8, - marginBottom: 8, + container: { + flex: 1, + backgroundColor: '#F7F8FA', }, - reactLogo: { - height: 178, - width: 290, - bottom: 0, - left: 0, - position: 'absolute', + header: { + paddingHorizontal: 24, + paddingTop: 16, + paddingBottom: 8, + }, + greeting: { + fontSize: 16, + color: '#8A8A8E', + fontWeight: '400', + marginBottom: 6, + }, + userName: { + fontSize: 30, + fontWeight: 'bold', + color: '#1A1A1A', + lineHeight: 36, + }, + sectionContainer: { + marginTop: 24, + }, + sectionTitle: { + fontSize: 24, + fontWeight: 'bold', + color: '#1A1A1A', + paddingHorizontal: 24, + marginBottom: 18, + }, + workoutScroll: { + paddingLeft: 24, + }, + workoutScrollContainer: { + paddingRight: 24, + }, + bottomSpacing: { + height: 120, }, }); diff --git a/app/(tabs)/personal.tsx b/app/(tabs)/personal.tsx new file mode 100644 index 0000000..d4fbcaa --- /dev/null +++ b/app/(tabs)/personal.tsx @@ -0,0 +1,110 @@ +import { Image } from 'expo-image'; +import { Platform, StyleSheet } from 'react-native'; + +import { Collapsible } from '@/components/Collapsible'; +import { ExternalLink } from '@/components/ExternalLink'; +import ParallaxScrollView from '@/components/ParallaxScrollView'; +import { ThemedText } from '@/components/ThemedText'; +import { ThemedView } from '@/components/ThemedView'; +import { IconSymbol } from '@/components/ui/IconSymbol'; + +export default function TabTwoScreen() { + return ( + + }> + + Explore + + This app includes example code to help you get started. + + + This app has two screens:{' '} + app/(tabs)/index.tsx and{' '} + app/(tabs)/explore.tsx + + + The layout file in app/(tabs)/_layout.tsx{' '} + sets up the tab navigator. + + + Learn more + + + + + You can open this project on Android, iOS, and the web. To open the web version, press{' '} + w in the terminal running this project. + + + + + For static images, you can use the @2x and{' '} + @3x suffixes to provide files for + different screen densities + + + + Learn more + + + + + Open app/_layout.tsx to see how to load{' '} + + custom fonts such as this one. + + + + Learn more + + + + + This template has light and dark mode support. The{' '} + useColorScheme() hook lets you inspect + what the user's current color scheme is, and so you can adjust UI colors accordingly. + + + Learn more + + + + + This template includes an example of an animated component. The{' '} + components/HelloWave.tsx component uses + the powerful react-native-reanimated{' '} + library to create a waving hand animation. + + {Platform.select({ + ios: ( + + The components/ParallaxScrollView.tsx{' '} + component provides a parallax effect for the header image. + + ), + })} + + + ); +} + +const styles = StyleSheet.create({ + headerImage: { + color: '#808080', + bottom: -90, + left: -35, + position: 'absolute', + }, + titleContainer: { + flexDirection: 'row', + gap: 8, + }, +}); diff --git a/components/SearchBox.tsx b/components/SearchBox.tsx new file mode 100644 index 0000000..8813083 --- /dev/null +++ b/components/SearchBox.tsx @@ -0,0 +1,61 @@ +import { useThemeColor } from '@/hooks/useThemeColor'; +import { Ionicons } from '@expo/vector-icons'; +import React from 'react'; +import { StyleSheet, TextInput, View } from 'react-native'; + +interface SearchBoxProps { + placeholder?: string; + value?: string; + onChangeText?: (text: string) => void; +} + +export function SearchBox({ placeholder = "Search", value, onChangeText }: SearchBoxProps) { + const backgroundColor = useThemeColor({}, 'background'); + const textColor = useThemeColor({}, 'text'); + const iconColor = useThemeColor({ light: '#9BA1A6', dark: '#687076' }, 'icon'); + + return ( + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#FFFFFF', + borderRadius: 25, + paddingHorizontal: 18, + paddingVertical: 14, + marginHorizontal: 20, + marginVertical: 10, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.08, + shadowRadius: 8, + elevation: 3, + borderWidth: 1, + borderColor: '#F0F0F0', + }, + icon: { + marginRight: 12, + }, + input: { + flex: 1, + fontSize: 16, + color: '#333', + height: 20, + }, +}); \ No newline at end of file diff --git a/components/WorkoutCard.tsx b/components/WorkoutCard.tsx new file mode 100644 index 0000000..36db43d --- /dev/null +++ b/components/WorkoutCard.tsx @@ -0,0 +1,124 @@ +import { Ionicons } from '@expo/vector-icons'; +import React from 'react'; +import { ImageBackground, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +interface WorkoutCardProps { + title: string; + calories: number; + duration: number; + imageSource: any; + onPress?: () => void; +} + +export function WorkoutCard({ title, calories, duration, imageSource, onPress }: WorkoutCardProps) { + return ( + + + + + + {title} + + + {calories !== undefined && ( + + + {calories} Kcal + + )} + + + + {duration} Min + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + width: 280, + height: 170, + marginRight: 16, + borderRadius: 20, + overflow: 'hidden', + }, + backgroundImage: { + flex: 1, + width: '100%', + height: '100%', + }, + imageStyle: { + borderRadius: 20, + }, + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.4)', + borderRadius: 20, + }, + content: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-end', + padding: 18, + }, + textContainer: { + flex: 1, + }, + title: { + fontSize: 22, + fontWeight: 'bold', + color: '#fff', + marginBottom: 14, + lineHeight: 26, + }, + statsContainer: { + flexDirection: 'column', + gap: 6, + }, + statItem: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.25)', + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 15, + alignSelf: 'flex-start', + }, + statText: { + color: '#fff', + fontSize: 13, + fontWeight: '600', + marginLeft: 4, + }, + playButton: { + width: 48, + height: 48, + borderRadius: 24, + backgroundColor: '#A8E866', + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 3, + }, + shadowOpacity: 0.3, + shadowRadius: 5, + elevation: 6, + }, +}); \ No newline at end of file diff --git a/components/ui/IconSymbol.tsx b/components/ui/IconSymbol.tsx index b7ece6b..87c1c52 100644 --- a/components/ui/IconSymbol.tsx +++ b/components/ui/IconSymbol.tsx @@ -1,7 +1,7 @@ // Fallback for using MaterialIcons on Android and web. import MaterialIcons from '@expo/vector-icons/MaterialIcons'; -import { SymbolWeight, SymbolViewProps } from 'expo-symbols'; +import { SymbolViewProps, SymbolWeight } from 'expo-symbols'; import { ComponentProps } from 'react'; import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'; @@ -18,6 +18,7 @@ const MAPPING = { 'paperplane.fill': 'send', 'chevron.left.forwardslash.chevron.right': 'code', 'chevron.right': 'chevron-right', + 'person.fill': 'person', } as IconMapping; /**