feat(tabs): 添加个人页面标签并重构标签栏布局
- 新增个人页面标签和对应的页面文件 - 将标签栏布局从垂直改为水平排列 - 隐藏顶部标题栏以提供更简洁的界面 - 调整选中状态下的内边距和间距 - 将界面文本本地化为中文 - 在图标映射中添加person图标支持
This commit is contained in:
@@ -17,6 +17,7 @@ export default function TabLayout() {
|
|||||||
pathname.includes(routeName);
|
pathname.includes(routeName);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
headerShown: false,
|
||||||
tabBarActiveTintColor: '#192126',
|
tabBarActiveTintColor: '#192126',
|
||||||
tabBarButton: (props) => {
|
tabBarButton: (props) => {
|
||||||
const { children, onPress } = props;
|
const { children, onPress } = props;
|
||||||
@@ -35,14 +36,13 @@ export default function TabLayout() {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
flexDirection: 'column',
|
flexDirection: 'row',
|
||||||
marginHorizontal: 6,
|
marginHorizontal: 6,
|
||||||
marginVertical: 10,
|
marginVertical: 10,
|
||||||
borderRadius: 25,
|
borderRadius: 25,
|
||||||
backgroundColor: isSelected ? '#BBF246' : 'transparent',
|
backgroundColor: isSelected ? '#BBF246' : 'transparent',
|
||||||
paddingHorizontal: isSelected ? 24 : 12,
|
paddingHorizontal: isSelected ? 16 : 8,
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
minWidth: isSelected ? 140 : 48,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -81,11 +81,11 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
title: 'Home',
|
title: '首页',
|
||||||
tabBarIcon: ({ color }) => {
|
tabBarIcon: ({ color }) => {
|
||||||
const isHomeSelected = pathname === '/' || pathname === '/index';
|
const isHomeSelected = pathname === '/' || pathname === '/index';
|
||||||
return (
|
return (
|
||||||
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<IconSymbol size={22} name="house.fill" color={color} />
|
<IconSymbol size={22} name="house.fill" color={color} />
|
||||||
{isHomeSelected && (
|
{isHomeSelected && (
|
||||||
<Text
|
<Text
|
||||||
@@ -94,12 +94,11 @@ export default function TabLayout() {
|
|||||||
color: color,
|
color: color,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginTop: 4,
|
marginLeft: 6,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
minWidth: 60,
|
|
||||||
}}>
|
}}>
|
||||||
Home
|
首页
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -110,11 +109,11 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="explore"
|
name="explore"
|
||||||
options={{
|
options={{
|
||||||
title: 'Explore',
|
title: '探索',
|
||||||
tabBarIcon: ({ color }) => {
|
tabBarIcon: ({ color }) => {
|
||||||
const isExploreSelected = pathname === '/explore';
|
const isExploreSelected = pathname === '/explore';
|
||||||
return (
|
return (
|
||||||
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<IconSymbol size={22} name="paperplane.fill" color={color} />
|
<IconSymbol size={22} name="paperplane.fill" color={color} />
|
||||||
{isExploreSelected && (
|
{isExploreSelected && (
|
||||||
<Text
|
<Text
|
||||||
@@ -123,12 +122,40 @@ export default function TabLayout() {
|
|||||||
color: color,
|
color: color,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginTop: 4,
|
marginLeft: 6,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
minWidth: 60,
|
|
||||||
}}>
|
}}>
|
||||||
Explore
|
探索
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tabs.Screen
|
||||||
|
name="personal"
|
||||||
|
options={{
|
||||||
|
title: '个人',
|
||||||
|
tabBarIcon: ({ color }) => {
|
||||||
|
const isPersonalSelected = pathname === '/personal';
|
||||||
|
return (
|
||||||
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
|
<IconSymbol size={22} name="person.fill" color={color} />
|
||||||
|
{isPersonalSelected && (
|
||||||
|
<Text
|
||||||
|
numberOfLines={1}
|
||||||
|
style={{
|
||||||
|
color: color,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginLeft: 6,
|
||||||
|
textAlign: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
个人
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,75 +1,121 @@
|
|||||||
import { Image } from 'expo-image';
|
import { SearchBox } from '@/components/SearchBox';
|
||||||
import { Platform, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { HelloWave } from '@/components/HelloWave';
|
|
||||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
|
||||||
import { ThemedText } from '@/components/ThemedText';
|
import { ThemedText } from '@/components/ThemedText';
|
||||||
import { ThemedView } from '@/components/ThemedView';
|
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() {
|
export default function HomeScreen() {
|
||||||
return (
|
return (
|
||||||
<ParallaxScrollView
|
<SafeAreaView style={styles.safeArea}>
|
||||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
<ThemedView style={styles.container}>
|
||||||
headerImage={
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
<Image
|
{/* Header Section */}
|
||||||
source={require('@/assets/images/partial-react-logo.png')}
|
<View style={styles.header}>
|
||||||
style={styles.reactLogo}
|
<ThemedText style={styles.greeting}>Good Morning 🔥</ThemedText>
|
||||||
|
<ThemedText style={styles.userName}>Pramuditya Uzumaki</ThemedText>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Search Box */}
|
||||||
|
<SearchBox placeholder="Search" />
|
||||||
|
|
||||||
|
{/* Popular Workouts Section */}
|
||||||
|
<View style={styles.sectionContainer}>
|
||||||
|
<ThemedText style={styles.sectionTitle}>Popular Workouts</ThemedText>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.workoutScrollContainer}
|
||||||
|
style={styles.workoutScroll}
|
||||||
|
>
|
||||||
|
{workoutData.map((workout) => (
|
||||||
|
<WorkoutCard
|
||||||
|
key={workout.id}
|
||||||
|
title={workout.title}
|
||||||
|
calories={workout.calories}
|
||||||
|
duration={workout.duration}
|
||||||
|
imageSource={workout.imageSource}
|
||||||
|
onPress={() => console.log(`Pressed ${workout.title}`)}
|
||||||
/>
|
/>
|
||||||
}>
|
))}
|
||||||
<ThemedView style={styles.titleContainer}>
|
</ScrollView>
|
||||||
<ThemedText type="title">Welcome!</ThemedText>
|
</View>
|
||||||
<HelloWave />
|
|
||||||
|
{/* Add some spacing at the bottom */}
|
||||||
|
<View style={styles.bottomSpacing} />
|
||||||
|
</ScrollView>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
<ThemedView style={styles.stepContainer}>
|
</SafeAreaView>
|
||||||
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
|
|
||||||
<ThemedText>
|
|
||||||
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
|
|
||||||
Press{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">
|
|
||||||
{Platform.select({
|
|
||||||
ios: 'cmd + d',
|
|
||||||
android: 'cmd + m',
|
|
||||||
web: 'F12',
|
|
||||||
})}
|
|
||||||
</ThemedText>{' '}
|
|
||||||
to open developer tools.
|
|
||||||
</ThemedText>
|
|
||||||
</ThemedView>
|
|
||||||
<ThemedView style={styles.stepContainer}>
|
|
||||||
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
|
|
||||||
<ThemedText>
|
|
||||||
{`Tap the Explore tab to learn more about what's included in this starter app.`}
|
|
||||||
</ThemedText>
|
|
||||||
</ThemedView>
|
|
||||||
<ThemedView style={styles.stepContainer}>
|
|
||||||
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
|
|
||||||
<ThemedText>
|
|
||||||
{`When you're ready, run `}
|
|
||||||
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
|
|
||||||
</ThemedText>
|
|
||||||
</ThemedView>
|
|
||||||
</ParallaxScrollView>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
titleContainer: {
|
safeArea: {
|
||||||
flexDirection: 'row',
|
flex: 1,
|
||||||
alignItems: 'center',
|
backgroundColor: '#F7F8FA',
|
||||||
gap: 8,
|
|
||||||
},
|
},
|
||||||
stepContainer: {
|
container: {
|
||||||
gap: 8,
|
flex: 1,
|
||||||
marginBottom: 8,
|
backgroundColor: '#F7F8FA',
|
||||||
},
|
},
|
||||||
reactLogo: {
|
header: {
|
||||||
height: 178,
|
paddingHorizontal: 24,
|
||||||
width: 290,
|
paddingTop: 16,
|
||||||
bottom: 0,
|
paddingBottom: 8,
|
||||||
left: 0,
|
},
|
||||||
position: 'absolute',
|
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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
110
app/(tabs)/personal.tsx
Normal file
110
app/(tabs)/personal.tsx
Normal file
@@ -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 (
|
||||||
|
<ParallaxScrollView
|
||||||
|
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
||||||
|
headerImage={
|
||||||
|
<IconSymbol
|
||||||
|
size={310}
|
||||||
|
color="#808080"
|
||||||
|
name="chevron.left.forwardslash.chevron.right"
|
||||||
|
style={styles.headerImage}
|
||||||
|
/>
|
||||||
|
}>
|
||||||
|
<ThemedView style={styles.titleContainer}>
|
||||||
|
<ThemedText type="title">Explore</ThemedText>
|
||||||
|
</ThemedView>
|
||||||
|
<ThemedText>This app includes example code to help you get started.</ThemedText>
|
||||||
|
<Collapsible title="File-based routing">
|
||||||
|
<ThemedText>
|
||||||
|
This app has two screens:{' '}
|
||||||
|
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
||||||
|
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
||||||
|
</ThemedText>
|
||||||
|
<ThemedText>
|
||||||
|
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
||||||
|
sets up the tab navigator.
|
||||||
|
</ThemedText>
|
||||||
|
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
||||||
|
<ThemedText type="link">Learn more</ThemedText>
|
||||||
|
</ExternalLink>
|
||||||
|
</Collapsible>
|
||||||
|
<Collapsible title="Android, iOS, and web support">
|
||||||
|
<ThemedText>
|
||||||
|
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
||||||
|
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
||||||
|
</ThemedText>
|
||||||
|
</Collapsible>
|
||||||
|
<Collapsible title="Images">
|
||||||
|
<ThemedText>
|
||||||
|
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
||||||
|
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
||||||
|
different screen densities
|
||||||
|
</ThemedText>
|
||||||
|
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
|
||||||
|
<ExternalLink href="https://reactnative.dev/docs/images">
|
||||||
|
<ThemedText type="link">Learn more</ThemedText>
|
||||||
|
</ExternalLink>
|
||||||
|
</Collapsible>
|
||||||
|
<Collapsible title="Custom fonts">
|
||||||
|
<ThemedText>
|
||||||
|
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
|
||||||
|
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
|
||||||
|
custom fonts such as this one.
|
||||||
|
</ThemedText>
|
||||||
|
</ThemedText>
|
||||||
|
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
|
||||||
|
<ThemedText type="link">Learn more</ThemedText>
|
||||||
|
</ExternalLink>
|
||||||
|
</Collapsible>
|
||||||
|
<Collapsible title="Light and dark mode components">
|
||||||
|
<ThemedText>
|
||||||
|
This template has light and dark mode support. The{' '}
|
||||||
|
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
||||||
|
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
||||||
|
</ThemedText>
|
||||||
|
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
||||||
|
<ThemedText type="link">Learn more</ThemedText>
|
||||||
|
</ExternalLink>
|
||||||
|
</Collapsible>
|
||||||
|
<Collapsible title="Animations">
|
||||||
|
<ThemedText>
|
||||||
|
This template includes an example of an animated component. The{' '}
|
||||||
|
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
||||||
|
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText>{' '}
|
||||||
|
library to create a waving hand animation.
|
||||||
|
</ThemedText>
|
||||||
|
{Platform.select({
|
||||||
|
ios: (
|
||||||
|
<ThemedText>
|
||||||
|
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
||||||
|
component provides a parallax effect for the header image.
|
||||||
|
</ThemedText>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</Collapsible>
|
||||||
|
</ParallaxScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
headerImage: {
|
||||||
|
color: '#808080',
|
||||||
|
bottom: -90,
|
||||||
|
left: -35,
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
61
components/SearchBox.tsx
Normal file
61
components/SearchBox.tsx
Normal file
@@ -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 (
|
||||||
|
<View style={[styles.container, { backgroundColor: backgroundColor }]}>
|
||||||
|
<Ionicons name="search" size={20} color={iconColor} style={styles.icon} />
|
||||||
|
<TextInput
|
||||||
|
style={[styles.input, { color: textColor }]}
|
||||||
|
placeholder={placeholder}
|
||||||
|
placeholderTextColor={iconColor}
|
||||||
|
value={value}
|
||||||
|
onChangeText={onChangeText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
124
components/WorkoutCard.tsx
Normal file
124
components/WorkoutCard.tsx
Normal file
@@ -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 (
|
||||||
|
<TouchableOpacity style={styles.container} onPress={onPress}>
|
||||||
|
<ImageBackground
|
||||||
|
source={imageSource}
|
||||||
|
style={styles.backgroundImage}
|
||||||
|
imageStyle={styles.imageStyle}
|
||||||
|
>
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<Text style={styles.title}>{title}</Text>
|
||||||
|
|
||||||
|
<View style={styles.statsContainer}>
|
||||||
|
{calories !== undefined && (
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Ionicons name="flame-outline" size={16} color="#fff" />
|
||||||
|
<Text style={styles.statText}>{calories} Kcal</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={styles.statItem}>
|
||||||
|
<Ionicons name="time-outline" size={16} color="#fff" />
|
||||||
|
<Text style={styles.statText}>{duration} Min</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.playButton}>
|
||||||
|
<Ionicons name="play" size={24} color="#000" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ImageBackground>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Fallback for using MaterialIcons on Android and web.
|
// Fallback for using MaterialIcons on Android and web.
|
||||||
|
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { SymbolWeight, SymbolViewProps } from 'expo-symbols';
|
import { SymbolViewProps, SymbolWeight } from 'expo-symbols';
|
||||||
import { ComponentProps } from 'react';
|
import { ComponentProps } from 'react';
|
||||||
import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native';
|
import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native';
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ const MAPPING = {
|
|||||||
'paperplane.fill': 'send',
|
'paperplane.fill': 'send',
|
||||||
'chevron.left.forwardslash.chevron.right': 'code',
|
'chevron.left.forwardslash.chevron.right': 'code',
|
||||||
'chevron.right': 'chevron-right',
|
'chevron.right': 'chevron-right',
|
||||||
|
'person.fill': 'person',
|
||||||
} as IconMapping;
|
} as IconMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user