feat: 重构项目并添加新功能
- 重命名项目为 Digital Pilates - 更新 bundleIdentifier 为 digital-pilates - 重新设计底部导航栏,增加选中状态和标签 - 添加 CLAUDE.md 文件,提供代码助手指导 - 删除 README.md 文件中的冗余信息
This commit is contained in:
19
CLAUDE.md
Normal file
19
CLAUDE.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- **Start development server**: `npm start`
|
||||||
|
- **Run on Android**: `npm run android`
|
||||||
|
- **Run on iOS**: `npm run ios`
|
||||||
|
- **Run on Web**: `npm run web`
|
||||||
|
- **Lint**: `npm run lint`
|
||||||
|
- **Reset project**: `npm run reset-project`
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
- **Framework**: React Native (Expo) with TypeScript.
|
||||||
|
- **Navigation**: Expo Router for file-based routing (`app/` directory).
|
||||||
|
- **UI**: Themed components (`ThemedText`, `ThemedView`) and reusable UI elements (`Collapsible`, `ParallaxScrollView`).
|
||||||
|
- **Platform-Specific**: Android (`android/`) and iOS (`ios/`) configurations with native modules.
|
||||||
|
- **Hooks**: Custom hooks for color scheme (`useColorScheme`) and theme management (`useThemeColor`).
|
||||||
|
- **Dependencies**: React Navigation for tab-based navigation, Expo modules for native features (haptics, blur, etc.).
|
||||||
50
README.md
50
README.md
@@ -1,50 +0,0 @@
|
|||||||
# Welcome to your Expo app 👋
|
|
||||||
|
|
||||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
|
||||||
|
|
||||||
## Get started
|
|
||||||
|
|
||||||
1. Install dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start the app
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
In the output, you'll find options to open the app in a
|
|
||||||
|
|
||||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
|
||||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
|
||||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
|
||||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
|
||||||
|
|
||||||
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
|
||||||
|
|
||||||
## Get a fresh project
|
|
||||||
|
|
||||||
When you're ready, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run reset-project
|
|
||||||
```
|
|
||||||
|
|
||||||
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
|
||||||
|
|
||||||
## Learn more
|
|
||||||
|
|
||||||
To learn more about developing your project with Expo, look at the following resources:
|
|
||||||
|
|
||||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
|
||||||
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
|
||||||
|
|
||||||
## Join the community
|
|
||||||
|
|
||||||
Join our community of developers creating universal apps.
|
|
||||||
|
|
||||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
|
||||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
|
||||||
4
app.json
4
app.json
@@ -10,7 +10,7 @@
|
|||||||
"newArchEnabled": true,
|
"newArchEnabled": true,
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true,
|
"supportsTablet": true,
|
||||||
"bundleIdentifier": "com.anonymous.digitalpilates"
|
"bundleIdentifier": "digital-pilates"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
@@ -41,4 +41,4 @@
|
|||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,139 @@
|
|||||||
import { Tabs } from 'expo-router';
|
import * as Haptics from 'expo-haptics';
|
||||||
|
import { Tabs, usePathname } from 'expo-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Platform } from 'react-native';
|
import { Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
import { HapticTab } from '@/components/HapticTab';
|
|
||||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||||
import TabBarBackground from '@/components/ui/TabBarBackground';
|
|
||||||
import { Colors } from '@/constants/Colors';
|
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
const colorScheme = useColorScheme();
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
screenOptions={{
|
screenOptions={({ route }) => {
|
||||||
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
|
const routeName = route.name;
|
||||||
headerShown: false,
|
const isSelected = (routeName === 'index' && pathname === '/') ||
|
||||||
tabBarButton: HapticTab,
|
(routeName === 'explore' && pathname === '/explore') ||
|
||||||
tabBarBackground: TabBarBackground,
|
pathname.includes(routeName);
|
||||||
tabBarStyle: Platform.select({
|
|
||||||
ios: {
|
return {
|
||||||
// Use a transparent background on iOS to show the blur effect
|
tabBarActiveTintColor: '#192126',
|
||||||
position: 'absolute',
|
tabBarButton: (props) => {
|
||||||
|
const { children, onPress } = props;
|
||||||
|
|
||||||
|
const handlePress = (event: any) => {
|
||||||
|
if (process.env.EXPO_OS === 'ios') {
|
||||||
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||||
|
}
|
||||||
|
onPress && onPress(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={handlePress}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
marginHorizontal: 6,
|
||||||
|
marginVertical: 10,
|
||||||
|
borderRadius: 25,
|
||||||
|
backgroundColor: isSelected ? '#BBF246' : 'transparent',
|
||||||
|
paddingHorizontal: isSelected ? 24 : 12,
|
||||||
|
paddingVertical: 8,
|
||||||
|
minWidth: isSelected ? 140 : 48,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
default: {},
|
tabBarStyle: {
|
||||||
}),
|
position: 'absolute',
|
||||||
|
bottom: 20,
|
||||||
|
height: 68,
|
||||||
|
borderRadius: 34,
|
||||||
|
backgroundColor: '#192126',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 10,
|
||||||
|
elevation: 5,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingBottom: 0,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
width: '90%',
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
|
tabBarItemStyle: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
height: 68,
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 0,
|
||||||
|
paddingTop: 0,
|
||||||
|
paddingBottom: 0,
|
||||||
|
},
|
||||||
|
tabBarShowLabel: false,
|
||||||
|
};
|
||||||
}}>
|
}}>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
title: 'Home',
|
title: 'Home',
|
||||||
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
|
tabBarIcon: ({ color }) => {
|
||||||
|
const isHomeSelected = pathname === '/' || pathname === '/index';
|
||||||
|
return (
|
||||||
|
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
|
||||||
|
<IconSymbol size={22} name="house.fill" color={color} />
|
||||||
|
{isHomeSelected && (
|
||||||
|
<Text
|
||||||
|
numberOfLines={1}
|
||||||
|
style={{
|
||||||
|
color: color,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginTop: 4,
|
||||||
|
textAlign: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
minWidth: 60,
|
||||||
|
}}>
|
||||||
|
Home
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="explore"
|
name="explore"
|
||||||
options={{
|
options={{
|
||||||
title: 'Explore',
|
title: 'Explore',
|
||||||
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
|
tabBarIcon: ({ color }) => {
|
||||||
|
const isExploreSelected = pathname === '/explore';
|
||||||
|
return (
|
||||||
|
<View style={{ flexDirection: 'column', alignItems: 'center' }}>
|
||||||
|
<IconSymbol size={22} name="paperplane.fill" color={color} />
|
||||||
|
{isExploreSelected && (
|
||||||
|
<Text
|
||||||
|
numberOfLines={1}
|
||||||
|
style={{
|
||||||
|
color: color,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginTop: 4,
|
||||||
|
textAlign: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
minWidth: 60,
|
||||||
|
}}>
|
||||||
|
Explore
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
Reference in New Issue
Block a user