diff --git a/Dockerfile b/Dockerfile index 19513c5681b4797efcb959e180bf07198a7934e3..285f2f46d6a3c496dd399b8752cdbbcdd91de8b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM reactnativecommunity/react-native-android - -WORKDIR /app -ENV PATH /app/node_modules/.bin:$PATH - -# 기본 도구 설치 +FROM reactnativecommunity/react-native-android + +WORKDIR /app +ENV PATH /app/node_modules/.bin:$PATH + +# 기본 도구 설치 RUN apt-get update && apt-get install -y git \ No newline at end of file diff --git a/README.md b/README.md index a8c6a5f26efdcccfeabc82bf2a4f2715a667b099..a0345522a43293c1d60540ac3af24306cb1e3ac3 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,152 @@ -# Bin Buddy App 개발 환경 - -## 개요 -이 프로젝트는 Docker를 사용하여 React Native 안드로이드 앱을 개발하기 위한 환경을 제공합니다. - -## 사전 요구사항 -- Docker가 설치되어 있어야 합니다 -- Git이 설치되어 있어야 합니다 - -## 처음 시작하는 방법 - -1. 환경 설정 저장소 클론 - -```bash -git clone https://git.ajou.ac.kr/myunsyeya/bin_buddy_app_env.git -cd bin_buddy_app_env -``` - -2. 실행 권한 부여 - -```bash -chmod +x *.sh -``` - -3. 개발 환경 구축 - -```bash -./setup.sh -``` - -## 개발 시작하기 - -1. 소스코드 수정 후 동기화 -src/ 디렉토리에서 코드를 수정한 후, 다음 명령어로 동기화합니다: -```bash -./sync.sh -``` - -2. APK 빌드 -다음 명령어로 APK를 빌드합니다: -```bash -./dev.sh -``` -빌드된 APK는 상위 디렉토리에 자동으로 복사됩니다. - -3. (선택사항) 도커 개발 환경 진입 -도커 환경에서 직접 작업이 필요한 경우: -```bash -./dev.sh shell -``` - -## 프로젝트 구조 -```text -bin_buddy_app_env/ # 메인 개발 및 git 저장소 -├── Dockerfile # 도커 이미지 설정 -├── setup.sh # 초기 환경 구축 스크립트 -├── dev.sh # 개발 환경 실행 스크립트 -├── README.md # 문서 -└── src/ # 앱 소스코드 - ├── App.js # 메인 앱 컴포넌트 (딥링크 처리) - ├── components/ # 재사용 가능한 컴포넌트 - ├── screens/ # 화면 컴포넌트 - └── android/ # 안드로이드 설정 - └── AndroidManifest.xml # 딥링크 및 앱 설정 - -bin_buddy_app/ # 빌드용 임시 디렉토리 -``` - -- `src/`: 실제 개발이 이루어지는 소스코드 디렉토리 - - React Native 컴포넌트 및 화면 - - 안드로이드 네이티브 설정 파일 -- `bin_buddy_app/`: 빌드 전용 임시 디렉토리 - - setup.sh 실행 시 생성되는 실행 가능한 RN 프로젝트 - - src/ 디렉토리의 코드가 이곳으로 복사되어 빌드됨 - -## 주의사항 -- 모든 소스코드는 src 디렉토리에서 개발하고 관리됩니다 -- bin_buddy_app은 빌드 검증용 임시 디렉토리이므로 이곳에서 직접 수정하지 마세요 -- 새로운 설정이나 코드는 src 디렉토리 내 적절한 위치에 추가해 주세요 - -## 커밋 컨벤션 - -커밋 메시지는 다음 형식을 따릅니다: -``` -<type>: <description> - -[optional body] -``` - -커밋 타입: -- feat: 새로운 기능 추가 -- fix: 버그 수정 -- docs: 문서 수정 -- style: 코드 포맷팅, 세미콜론 누락 등 (코드 변경 없음) -- refactor: 코드 리팩토링 -- test: 테스트 코드 추가/수정 -- chore: 빌드 프로세스, 환경 설정 변경 등 - -예시: -``` -feat: 사용자 위치 추적 기능 추가 - -- 실시간 위치 업데이트 -- 위치 정보 권한 처리 -- 에러 핸들링 추가 -``` - -## 개발 워크플로우 - -1. 새로운 기능 개발 시작: -```bash -# feature 브랜치 생성 -git checkout -b feature/기능이름 - -# 개발 및 커밋 -git add . -git commit -m "feat: 새로운 기능 추가" -``` - -2. 기능 개발 완료 후: -```bash -# 원격 저장소에 push -git push origin feature/기능이름 -``` - -3. GitLab에서: -- Merge Request 생성 -- Source: feature/기능이름 -- Target: main -- "Squash commits" 옵션 체크 -- 변경사항 리뷰 후 머지 - -4. 머지 완료 후 로컬 정리: -```bash -# 원격 저장소 변경사항 가져오기 -git fetch - -# main 브랜치를 원격과 동일하게 맞추기 -git checkout main -git reset --hard origin/main - -# 작업이 완료된 feature 브랜치 삭제 -git branch -D feature/기능이름 -``` - -이렇게 하면: -- 기능별로 독립적인 개발 가능 -- 커밋 히스토리 깔끔하게 관리 -- 코드 리뷰 용이 -- 로컬 브랜치 관리 용이 - +# Bin Buddy App 개발 환경 + +## 개요 +이 프로젝트는 Docker를 사용하여 React Native 안드로이드 앱을 개발하기 위한 환경을 제공합니다. + +## 사전 요구사항 +- Docker가 설치되어 있어야 합니다 +- Git이 설치되어 있어야 합니다 + +## 처음 시작하는 방법 + +1. 환경 설정 저장소 클론 + +```bash +git clone https://git.ajou.ac.kr/myunsyeya/bin_buddy_app_env.git +cd bin_buddy_app_env +``` + +2. 실행 권한 부여 + +```bash +chmod +x *.sh +``` + +3. 개발 환경 구축 + +```bash +./setup.sh +``` + +## 개발 시작하기 + +1. 소스코드 수정 후 동기화 +src/ 디렉토리에서 코드를 수정한 후, 다음 명령어로 동기화합니다: +```bash +./sync.sh +``` + +2. APK 빌드 +다음 명령어로 APK를 빌드합니다: +```bash +./dev.sh +``` +빌드된 APK는 상위 디렉토리에 자동으로 복사됩니다. + +3. (선택사항) 도커 개발 환경 진입 +도커 환경에서 직접 작업이 필요한 경우: +```bash +./dev.sh shell +``` + +## 프로젝트 구조 +```text +bin_buddy_app_env/ # 메인 개발 및 git 저장소 +├── Dockerfile # 도커 이미지 설정 +├── setup.sh # 초기 환경 구축 스크립트 +├── dev.sh # 개발 환경 실행 스크립트 +├── README.md # 문서 +└── src/ # 앱 소스코드 + ├── App.js # 메인 앱 컴포넌트 (딥링크 처리) + ├── components/ # 재사용 가능한 컴포넌트 + ├── screens/ # 화면 컴포넌트 + └── android/ # 안드로이드 설정 + └── AndroidManifest.xml # 딥링크 및 앱 설정 + +bin_buddy_app/ # 빌드용 임시 디렉토리 +``` + +- `src/`: 실제 개발이 이루어지는 소스코드 디렉토리 + - React Native 컴포넌트 및 화면 + - 안드로이드 네이티브 설정 파일 +- `bin_buddy_app/`: 빌드 전용 임시 디렉토리 + - setup.sh 실행 시 생성되는 실행 가능한 RN 프로젝트 + - src/ 디렉토리의 코드가 이곳으로 복사되어 빌드됨 + +## 주의사항 +- 모든 소스코드는 src 디렉토리에서 개발하고 관리됩니다 +- bin_buddy_app은 빌드 검증용 임시 디렉토리이므로 이곳에서 직접 수정하지 마세요 +- 새로운 설정이나 코드는 src 디렉토리 내 적절한 위치에 추가해 주세요 + +## 커밋 컨벤션 + +커밋 메시지는 다음 형식을 따릅니다: +``` +<type>: <description> + +[optional body] +``` + +커밋 타입: +- feat: 새로운 기능 추가 +- fix: 버그 수정 +- docs: 문서 수정 +- style: 코드 포맷팅, 세미콜론 누락 등 (코드 변경 없음) +- refactor: 코드 리팩토링 +- test: 테스트 코드 추가/수정 +- chore: 빌드 프로세스, 환경 설정 변경 등 + +예시: +``` +feat: 사용자 위치 추적 기능 추가 + +- 실시간 위치 업데이트 +- 위치 정보 권한 처리 +- 에러 핸들링 추가 +``` + +## 개발 워크플로우 + +1. 새로운 기능 개발 시작: +```bash +# feature 브랜치 생성 +git checkout -b feature/기능이름 + +# 개발 및 커밋 +git add . +git commit -m "feat: 새로운 기능 추가" +``` + +2. 기능 개발 완료 후: +```bash +# 원격 저장소에 push +git push origin feature/기능이름 +``` + +3. GitLab에서: +- Merge Request 생성 +- Source: feature/기능이름 +- Target: main +- "Squash commits" 옵션 체크 +- 변경사항 리뷰 후 머지 + +4. 머지 완료 후 로컬 정리: +```bash +# 원격 저장소 변경사항 가져오기 +git fetch + +# main 브랜치를 원격과 동일하게 맞추기 +git checkout main +git reset --hard origin/main + +# 작업이 완료된 feature 브랜치 삭제 +git branch -D feature/기능이름 +``` + +이렇게 하면: +- 기능별로 독립적인 개발 가능 +- 커밋 히스토리 깔끔하게 관리 +- 코드 리뷰 용이 +- 로컬 브랜치 관리 용이 + 해당 절차에 따라 진행해주시면 감사하겠습니다. \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..7e812ba022a5de5f30025c6a54b2efd32461cd4c --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,72 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + buildToolsVersion = "34.0.0" + minSdkVersion = 21 + compileSdkVersion = 34 + targetSdkVersion = 34 + ndkVersion = "25.1.8937393" + kotlinVersion = "1.8.0" // Kotlin 버전 추가 + + // React Native 관련 설정 + reactNativeArchitectures = ["x86_64", "arm64-v8a"] + hermesEnabled = true + enableSeparateBuildPerCPUArchitecture = true + enableProguardInReleaseBuilds = true + FLIPPER_VERSION = '0.125.0' + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.0") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") // Kotlin 플러그인 추가 + } +} + +apply plugin: "com.android.application" +apply plugin: "com.facebook.react" + +allprojects { + repositories { + maven { + // Android JSC가 여기에 있습니다 + url("$rootDir/../node_modules/jsc-android/dist") + } + mavenCentral() + google() + maven { url 'https://www.jitpack.io' } + maven { + // React Native 프리빌드 의존성을 위해 필요 + url("$rootDir/../node_modules/react-native/android") + } + maven { + // React Native 의존성을 위해 필요 + url("$rootDir/../node_modules/jsc-android/dist") + } + } + + // Kotlin 버전 강제 적용 + configurations.all { + resolutionStrategy { + force "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + force "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + force "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + } + } +} + +// 새로운 프로젝트를 생성할 때 적용할 설정 +def REACT_NATIVE_VERSION = new File(['node', '--print', "require('react-native/package.json').version"].execute(null, rootDir).text.trim()) + +allprojects { + configurations.all { + resolutionStrategy { + force "com.facebook.react:react-native:" + REACT_NATIVE_VERSION + } + } +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..bc51141d1a136a647a1b68053144c863edb080f8 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,37 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + buildToolsVersion = "34.0.0" + minSdkVersion = 21 + compileSdkVersion = 34 + targetSdkVersion = 34 + ndkVersion = "25.1.8937393" + kotlinVersion = "1.8.0" + + // React Native 관련 설정 + reactNativeArchitectures = ["x86_64", "arm64-v8a"] + hermesEnabled = true + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.0") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + } +} + +allprojects { + repositories { + maven { + // Android JSC가 여기에 있습니다 + url("$rootDir/../node_modules/jsc-android/dist") + } + mavenCentral() + google() + maven { url 'https://www.jitpack.io' } + } +} \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ae999eaf7ced802cd48d7ee2e4590887c160ed2e --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,18 @@ +rootProject.name = 'bin_buddy_app' + +apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) +include ':app' +includeBuild('../node_modules/@react-native/gradle-plugin') + +// React Native 의존성 추가 +include ':react-native-screens' +project(':react-native-screens').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-screens/android') + +include ':react-native-safe-area-context' +project(':react-native-safe-area-context').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-safe-area-context/android') + +include ':react-native-vector-icons' +project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') + +include ':@react-native-community_geolocation' +project(':@react-native-community_geolocation').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/geolocation/android') \ No newline at end of file diff --git a/setup.sh b/setup.sh index af8580dfea2b1eca66b057b9b769abc5d4913fde..a69c256ebf851011db53f52e2e56453d386dfa07 100755 --- a/setup.sh +++ b/setup.sh @@ -8,15 +8,15 @@ cd .. npx react-native init bin_buddy_app # 타입스크립트 관련 파일 제거 -rm bin_buddy_app/App.tsx -rm bin_buddy_app/tsconfig.json -rm bin_buddy_app/__tests__/App.test.tsx +rm -f bin_buddy_app/App.tsx +rm -f bin_buddy_app/tsconfig.json +rm -f bin_buddy_app/__tests__/App.test.tsx # src의 모든 소스코드를 bin_buddy_app/src로 복사 mkdir -p bin_buddy_app/src cp -rv "$ENV_DIR/src/"* bin_buddy_app/src/ -# AndroidManifest.xml 복사 (이미 src 복사에 포함되어 있지만 명시적으로 다시 한번) +# AndroidManifest.xml 복사 cp -v "$ENV_DIR/src/android/AndroidManifest.xml" bin_buddy_app/android/app/src/main/AndroidManifest.xml # index.js 새로 생성 @@ -28,13 +28,30 @@ import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => App); EOF -# 필요한 의존성 설치 +# 필요한 의존성 설치 (버전 명시) cd bin_buddy_app -npm install -npm install @react-native-community/geolocation -# package.json 의존성 추가 -npm install @react-native-community/geolocation +# node_modules 디렉토리 제거 (sudo 사용) +if [ -d "node_modules" ]; then + sudo rm -rf node_modules +fi + +# package-lock.json 제거 +if [ -f "package-lock.json" ]; then + sudo rm package-lock.json +fi + +# 각 패키지를 개별적으로 설치 +npm install --save @react-navigation/native@6.1.9 +npm install --save @react-navigation/bottom-tabs@6.5.11 +npm install --save @react-navigation/native-stack@6.9.17 +npm install --save react-native-screens@3.22.1 +npm install --save react-native-safe-area-context@4.6.3 +npm install --save react-native-vector-icons@10.0.3 +npm install --save @react-native-community/geolocation@3.1.0 + +# 권한 수정 +sudo chown -R $USER:$USER . # 다시 env 디렉토리로 이동 cd "$ENV_DIR" diff --git a/src/App.js b/src/App.js index 4fa6a2ae0e1996d8955f71b34862b8d121c3a9d6..52850247d5dfcbdd04687fa46f3f182ab16d592b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,22 +1,67 @@ -import React from 'react'; -import { View, StyleSheet } from 'react-native'; -import DeepLinkHandler from './components/DeepLinkHandler'; -import LocationTracker from './components/LocationTracker'; - -const App = () => { - return ( - <View style={styles.container}> - <DeepLinkHandler /> - <LocationTracker /> - </View> - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - }, -}); - -export default App; +import React from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import Icon from 'react-native-vector-icons/MaterialIcons'; + +import HomeScreen from './screens/HomeScreen'; +import ActivityScreen from './screens/ActivityScreen'; +import MyPageScreen from './screens/MyPageScreen'; +import DeepLinkHandler from './components/DeepLinkHandler'; + +const Tab = createBottomTabNavigator(); + +const App = () => { + const linking = { + prefixes: ['binbuddy://'], + config: { + screens: { + Home: 'home', + Activity: 'activity', + MyPage: 'mypage', + FindTrashBin: 'find/trashbin', + FindFoodTruck: 'find/foodtruck', + }, + }, + }; + + return ( + <NavigationContainer linking={linking}> + <DeepLinkHandler /> + <Tab.Navigator + screenOptions={({ route }) => ({ + tabBarIcon: ({ focused, color, size }) => { + let iconName; + if (route.name === 'Home') { + iconName = 'home'; + } else if (route.name === 'Activity') { + iconName = 'local-activity'; + } else if (route.name === 'MyPage') { + iconName = 'person'; + } + return <Icon name={iconName} size={size} color={color} />; + }, + tabBarActiveTintColor: '#1E88E5', + tabBarInactiveTintColor: 'gray', + })} + > + <Tab.Screen + name="Home" + component={HomeScreen} + options={{ title: '홈' }} + /> + <Tab.Screen + name="Activity" + component={ActivityScreen} + options={{ title: '활동' }} + /> + <Tab.Screen + name="MyPage" + component={MyPageScreen} + options={{ title: '마이페이지' }} + /> + </Tab.Navigator> + </NavigationContainer> + ); +}; + +export default App; diff --git a/src/android/AndroidManifest.xml b/src/android/AndroidManifest.xml index 246999d88ca29cbded5061cbd36c943f566000b1..67c0414db0e065c53b188d0b2dd2304df9c73917 100644 --- a/src/android/AndroidManifest.xml +++ b/src/android/AndroidManifest.xml @@ -1,42 +1,49 @@ -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> - - <application - android:name=".MainApplication" - android:label="@string/app_name" - android:icon="@mipmap/ic_launcher" - android:roundIcon="@mipmap/ic_launcher_round" - android:allowBackup="false" - android:theme="@style/AppTheme" - android:supportsRtl="true"> - - <activity - android:name=".MainActivity" - android:label="@string/app_name" - android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" - android:launchMode="singleTask" - android:windowSoftInputMode="adjustResize" - android:exported="true"> - - <!-- 기본 런처 설정 --> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - - <!-- 딥링크 설정 --> - <intent-filter android:autoVerify="true"> - <action android:name="android.intent.action.VIEW" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - <data - android:scheme="binbuddy" - android:host="route" - android:pathPattern="/.*" /> - </intent-filter> - </activity> - </application> -</manifest> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> + <uses-permission android:name="android.permission.VIBRATE"/> + + <application + android:name=".MainApplication" + android:label="@string/app_name" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:allowBackup="false" + android:theme="@style/AppTheme" + android:supportsRtl="true" + android:usesCleartextTraffic="true"> + + <activity + android:name=".MainActivity" + android:label="@string/app_name" + android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" + android:launchMode="singleTask" + android:windowSoftInputMode="adjustResize" + android:exported="true" + android:screenOrientation="portrait"> + + <!-- 기본 런처 설정 --> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + + <!-- 딥링크 설정 --> + <intent-filter android:autoVerify="true"> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="binbuddy" android:host="route" android:pathPattern="/.*" /> + <data android:scheme="binbuddy" android:host="home" /> + <data android:scheme="binbuddy" android:host="activity" /> + <data android:scheme="binbuddy" android:host="mypage" /> + <data android:scheme="binbuddy" android:host="find" /> + <data android:scheme="binbuddy" android:host="find" android:pathPrefix="/trashbin" /> + <data android:scheme="binbuddy" android:host="find" android:pathPrefix="/foodtruck" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/src/assets/default-profile.png b/src/assets/default-profile.png new file mode 100644 index 0000000000000000000000000000000000000000..cff2a3cd395006de392508ac8ca955cb0df26ad9 Binary files /dev/null and b/src/assets/default-profile.png differ diff --git a/src/components/DeepLinkHandler.js b/src/components/DeepLinkHandler.js index aabc0499bdc674c0cc590401e9a8ae244b3c59ab..494b53a983ccc12498207ced32615e5b632aa80a 100644 --- a/src/components/DeepLinkHandler.js +++ b/src/components/DeepLinkHandler.js @@ -1,42 +1,52 @@ -import React, { useEffect } from 'react'; -import { Alert } from 'react-native'; -import { Linking } from 'react-native'; - -const DeepLinkHandler = () => { - useEffect(() => { - const handleDeepLink = (event) => { - const { url } = event; - - // URL 파싱 - try { - const regex = /^binbuddy:\/\/route\/(.+)$/; - const match = url.match(regex); - - if (match) { - const description = decodeURIComponent(match[1]); - Alert.alert('위치 정보 수신', `수신된 정보: ${description}`); - } - } catch (error) { - Alert.alert('오류', '잘못된 URL 형식입니다'); - } - }; - - // 앱이 실행 중일 때 딥링크 처리 - Linking.addEventListener('url', handleDeepLink); - - // 앱이 실행 중이지 않을 때 딥링크로 앱이 열린 경우 처리 - Linking.getInitialURL().then((url) => { - if (url) { - handleDeepLink({ url }); - } - }); - - return () => { - Linking.removeEventListener('url', handleDeepLink); - }; - }, []); - - return null; -}; - +import { useEffect } from 'react'; +import { Linking } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; + +const DeepLinkHandler = () => { + const navigation = useNavigation(); + + useEffect(() => { + const handleDeepLink = ({ url }) => { + if (!url) return; + + // URL 파싱 + const route = url.replace('binbuddy://', ''); + + switch (route) { + case 'home': + navigation.navigate('Home'); + break; + case 'activity': + navigation.navigate('Activity'); + break; + case 'mypage': + navigation.navigate('MyPage'); + break; + case 'find/trashbin': + navigation.navigate('FindTrashBin'); + break; + case 'find/foodtruck': + navigation.navigate('FindFoodTruck'); + break; + } + }; + + // 앱이 실행 중일 때 딥링크 처리 + Linking.addEventListener('url', handleDeepLink); + + // 앱이 실행되지 않은 상태에서 딥링크로 열린 경우 + Linking.getInitialURL().then(url => { + if (url) { + handleDeepLink({ url }); + } + }); + + return () => { + Linking.removeEventListener('url', handleDeepLink); + }; + }, [navigation]); + + return null; +}; + export default DeepLinkHandler; \ No newline at end of file diff --git a/src/components/LocationTracker.js b/src/components/LocationTracker.js index 3ed45a00fd26791bf3cfe9f2eee44dbfb707277e..56e832d1bcf710cd6261ff58b5e5981d3fbbd4ab 100644 --- a/src/components/LocationTracker.js +++ b/src/components/LocationTracker.js @@ -1,103 +1,103 @@ -import React, { useState, useEffect } from 'react'; -import { View, Text, StyleSheet, PermissionsAndroid } from 'react-native'; -import Geolocation from '@react-native-community/geolocation'; - -const LocationTracker = () => { - const [location, setLocation] = useState(null); - const [watchId, setWatchId] = useState(null); - - const requestLocationPermission = async () => { - try { - const granted = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, - { - title: "위치 정보 권한", - message: "쓰레기통 위치 확인을 위해 위치 정보 권한이 필요합니다.", - buttonNeutral: "나중에 묻기", - buttonNegative: "거부", - buttonPositive: "허용" - } - ); - return granted === PermissionsAndroid.RESULTS.GRANTED; - } catch (err) { - console.warn(err); - return false; - } - }; - - useEffect(() => { - const startLocationTracking = async () => { - const hasPermission = await requestLocationPermission(); - - if (hasPermission) { - // 초기 위치 가져오기 - Geolocation.getCurrentPosition( - (position) => { - console.log('Initial position:', position); - setLocation({ - latitude: position.coords.latitude, - longitude: position.coords.longitude, - }); - }, - (error) => { - console.error('Error getting location:', error); - }, - { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 } - ); - - // 위치 변경 감지 시작 - const id = Geolocation.watchPosition( - (position) => { - console.log('Updated position:', position); - setLocation({ - latitude: position.coords.latitude, - longitude: position.coords.longitude, - }); - }, - (error) => { - console.error('Error watching location:', error); - }, - { enableHighAccuracy: true, distanceFilter: 10 } - ); - setWatchId(id); - } - }; - - startLocationTracking(); - - // cleanup - return () => { - if (watchId !== null) { - Geolocation.clearWatch(watchId); - } - }; - }, []); - - return ( - <View style={styles.container}> - {location ? ( - <> - <Text style={styles.text}>lat: {location.latitude.toFixed(6)}</Text> - <Text style={styles.text}>lon: {location.longitude.toFixed(6)}</Text> - </> - ) : ( - <Text style={styles.text}>위치 정보를 가져오는 중...</Text> - )} - </View> - ); -}; - -const styles = StyleSheet.create({ - container: { - padding: 20, - backgroundColor: '#f5f5f5', - borderRadius: 10, - margin: 10, - }, - text: { - fontSize: 16, - marginVertical: 5, - }, -}); - +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, PermissionsAndroid } from 'react-native'; +import Geolocation from '@react-native-community/geolocation'; + +const LocationTracker = () => { + const [location, setLocation] = useState(null); + const [watchId, setWatchId] = useState(null); + + const requestLocationPermission = async () => { + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, + { + title: "위치 정보 권한", + message: "쓰레기통 위치 확인을 위해 위치 정보 권한이 필요합니다.", + buttonNeutral: "나중에 묻기", + buttonNegative: "거부", + buttonPositive: "허용" + } + ); + return granted === PermissionsAndroid.RESULTS.GRANTED; + } catch (err) { + console.warn(err); + return false; + } + }; + + useEffect(() => { + const startLocationTracking = async () => { + const hasPermission = await requestLocationPermission(); + + if (hasPermission) { + // 초기 위치 가져오기 + Geolocation.getCurrentPosition( + (position) => { + console.log('Initial position:', position); + setLocation({ + latitude: position.coords.latitude, + longitude: position.coords.longitude, + }); + }, + (error) => { + console.error('Error getting location:', error); + }, + { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 } + ); + + // 위치 변경 감지 시작 + const id = Geolocation.watchPosition( + (position) => { + console.log('Updated position:', position); + setLocation({ + latitude: position.coords.latitude, + longitude: position.coords.longitude, + }); + }, + (error) => { + console.error('Error watching location:', error); + }, + { enableHighAccuracy: true, distanceFilter: 10 } + ); + setWatchId(id); + } + }; + + startLocationTracking(); + + // cleanup + return () => { + if (watchId !== null) { + Geolocation.clearWatch(watchId); + } + }; + }, []); + + return ( + <View style={styles.container}> + {location ? ( + <> + <Text style={styles.text}>lat: {location.latitude.toFixed(6)}</Text> + <Text style={styles.text}>lon: {location.longitude.toFixed(6)}</Text> + </> + ) : ( + <Text style={styles.text}>위치 정보를 가져오는 중...</Text> + )} + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + padding: 20, + backgroundColor: '#f5f5f5', + borderRadius: 10, + margin: 10, + }, + text: { + fontSize: 16, + marginVertical: 5, + }, +}); + export default LocationTracker; \ No newline at end of file diff --git a/src/package.json b/src/package.json index 57ad9cb5dbffc1f6f9633171ea698e50005b746a..8765a3d82e60db2c8330a0a81ec643f447c13262 100644 --- a/src/package.json +++ b/src/package.json @@ -1,40 +1,46 @@ -{ - "name": "bin_buddy_app", - "version": "0.0.1", - "private": true, - "scripts": { - "android": "react-native run-android", - "ios": "react-native run-ios", - "lint": "eslint .", - "start": "react-native start", - "test": "jest" - }, - "dependencies": { - "react": "18.3.1", - "react-native": "0.76.3", - "@react-native-community/geolocation": "^3.1.0" - }, - "devDependencies": { - "@babel/core": "^7.25.2", - "@babel/preset-env": "^7.25.3", - "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "15.0.1", - "@react-native-community/cli-platform-android": "15.0.1", - "@react-native-community/cli-platform-ios": "15.0.1", - "@react-native/babel-preset": "0.76.3", - "@react-native/eslint-config": "0.76.3", - "@react-native/metro-config": "0.76.3", - "@react-native/typescript-config": "0.76.3", - "@types/react": "^18.2.6", - "@types/react-test-renderer": "^18.0.0", - "babel-jest": "^29.6.3", - "eslint": "^8.19.0", - "jest": "^29.6.3", - "prettier": "2.8.8", - "react-test-renderer": "18.3.1", - "typescript": "5.0.4" - }, - "engines": { - "node": ">=18" - } +{ + "name": "bin_buddy_app", + "version": "0.0.1", + "private": true, + "scripts": { + "android": "react-native run-android", + "ios": "react-native run-ios", + "lint": "eslint .", + "start": "react-native start", + "test": "jest" + }, + "dependencies": { + "react": "18.3.1", + "react-native": "0.76.3", + "@react-native-community/geolocation": "^3.1.0", + "@react-navigation/native": "^6.1.9", + "@react-navigation/bottom-tabs": "^6.5.11", + "@react-navigation/native-stack": "^6.9.17", + "react-native-screens": "^3.29.0", + "react-native-safe-area-context": "^4.9.0", + "react-native-vector-icons": "^10.0.3" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@react-native-community/cli": "15.0.1", + "@react-native-community/cli-platform-android": "15.0.1", + "@react-native-community/cli-platform-ios": "15.0.1", + "@react-native/babel-preset": "0.76.3", + "@react-native/eslint-config": "0.76.3", + "@react-native/metro-config": "0.76.3", + "@react-native/typescript-config": "0.76.3", + "@types/react": "^18.2.6", + "@types/react-test-renderer": "^18.0.0", + "babel-jest": "^29.6.3", + "eslint": "^8.19.0", + "jest": "^29.6.3", + "prettier": "2.8.8", + "react-test-renderer": "18.3.1", + "typescript": "5.0.4" + }, + "engines": { + "node": ">=18" + } } \ No newline at end of file diff --git a/src/screens/ActivityScreen.js b/src/screens/ActivityScreen.js new file mode 100644 index 0000000000000000000000000000000000000000..b1b525766878d590a96a98654c14cecdd6eab860 --- /dev/null +++ b/src/screens/ActivityScreen.js @@ -0,0 +1,179 @@ +import React from 'react'; +import { View, TouchableOpacity, Text, StyleSheet, ScrollView } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import { useNavigation } from '@react-navigation/native'; + +const ActivityScreen = () => { + const navigation = useNavigation(); + + const activities = [ + { + id: 'walk', + icon: 'directions-walk', + title: '걸기', + action: '시작하기', + color: '#1976D2', + }, + { + id: 'bike', + icon: 'directions-bike', + title: '자전거', + action: '출발하기', + color: '#388E3C', + }, + ]; + + const features = [ + { + id: 'trashbin', + icon: 'delete', + title: '가까운 쓰레기통 찾기', + color: '#1E88E5', + onPress: () => navigation.navigate('FindTrashBin'), + }, + { + id: 'foodtruck', + icon: 'local-dining', + title: '푸드트럭 찾기', + color: '#E64A19', + onPress: () => navigation.navigate('FindFoodTruck'), + }, + { + id: 'tumbler', + icon: 'local-cafe', + title: '텀블러 할인 카페 찾기', + color: '#7B1FA2', + onPress: () => {}, + }, + ]; + + return ( + <ScrollView style={styles.container}> + <View style={styles.header}> + <Text style={styles.headerTitle}>전체</Text> + <Text style={styles.headerSubtitle}>내 활동</Text> + </View> + + <View style={styles.activityGrid}> + {activities.map((activity) => ( + <TouchableOpacity + key={activity.id} + style={styles.activityCard} + > + <View style={[styles.iconContainer, { backgroundColor: `${activity.color}15` }]}> + <Icon name={activity.icon} size={32} color={activity.color} /> + </View> + <Text style={styles.activityTitle}>{activity.title}</Text> + <TouchableOpacity style={[styles.actionButton, { backgroundColor: activity.color }]}> + <Text style={styles.actionText}>{activity.action}</Text> + </TouchableOpacity> + </TouchableOpacity> + ))} + </View> + + <View style={styles.featureSection}> + {features.map((feature) => ( + <TouchableOpacity + key={feature.id} + style={styles.featureCard} + onPress={feature.onPress} + > + <View style={[styles.featureIconContainer, { backgroundColor: `${feature.color}15` }]}> + <Icon name={feature.icon} size={24} color={feature.color} /> + </View> + <Text style={styles.featureTitle}>{feature.title}</Text> + <Icon name="chevron-right" size={24} color="#666" /> + </TouchableOpacity> + ))} + </View> + </ScrollView> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f8f9fa', + }, + header: { + padding: 20, + backgroundColor: '#fff', + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + headerTitle: { + fontSize: 24, + fontWeight: 'bold', + color: '#1a237e', + marginBottom: 4, + }, + headerSubtitle: { + fontSize: 14, + color: '#666', + }, + activityGrid: { + flexDirection: 'row', + padding: 12, + }, + activityCard: { + flex: 1, + margin: 4, + padding: 16, + backgroundColor: '#fff', + borderRadius: 12, + elevation: 2, + alignItems: 'center', + }, + iconContainer: { + width: 60, + height: 60, + borderRadius: 30, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 12, + }, + activityTitle: { + fontSize: 16, + fontWeight: 'bold', + color: '#333', + marginBottom: 12, + }, + actionButton: { + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 20, + }, + actionText: { + color: '#fff', + fontSize: 14, + fontWeight: 'bold', + }, + featureSection: { + padding: 12, + }, + featureCard: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + backgroundColor: '#fff', + borderRadius: 12, + marginBottom: 8, + elevation: 2, + }, + featureIconContainer: { + width: 48, + height: 48, + borderRadius: 24, + justifyContent: 'center', + alignItems: 'center', + marginRight: 16, + }, + featureTitle: { + flex: 1, + fontSize: 16, + color: '#333', + fontWeight: '500', + }, +}); + +export default ActivityScreen; \ No newline at end of file diff --git a/src/screens/HomeScreen.js b/src/screens/HomeScreen.js new file mode 100644 index 0000000000000000000000000000000000000000..d68084a1f0b99ebb42faffa6261ac71366fb1c50 --- /dev/null +++ b/src/screens/HomeScreen.js @@ -0,0 +1,223 @@ +import React from 'react'; +import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import LocationTracker from '../components/LocationTracker'; + +const HomeScreen = () => { + return ( + <ScrollView style={styles.container}> + <View style={styles.header}> + <Text style={styles.title}>기후행동기회득</Text> + <TouchableOpacity style={styles.bellIcon}> + <Icon name="notifications-none" size={24} color="#333" /> + </TouchableOpacity> + </View> + + <View style={styles.pointSection}> + <View style={styles.pointHeader}> + <Text style={styles.pointLabel}>내 적립금</Text> + <View style={styles.badge}> + <Text style={styles.badgeText}>2024 시범사업 종료</Text> + </View> + </View> + <Text style={styles.pointAmount}>0원</Text> + <View style={styles.pointStamp}> + <Icon name="verified" size={20} color="#1E88E5" /> + <Text style={styles.stampText}>시범사업 종료</Text> + </View> + </View> + + <View style={styles.carbonSection}> + <View style={styles.carbonHeader}> + <Icon name="eco" size={24} color="#2E7D32" /> + <Text style={styles.sectionTitle}>온실가스 저감효과</Text> + </View> + <Text style={styles.carbonAmount}>0.0kgCO₂eq</Text> + </View> + + <View style={styles.quickActions}> + <TouchableOpacity style={styles.actionButton}> + <Icon name="directions-walk" size={24} color="#1E88E5" /> + <Text style={styles.actionText}>걸음수</Text> + <Text style={styles.actionValue}>시작하기</Text> + </TouchableOpacity> + + <TouchableOpacity style={styles.actionButton}> + <Icon name="directions-bike" size={24} color="#1E88E5" /> + <Text style={styles.actionText}>자전거</Text> + <Text style={styles.actionValue}>출발하기</Text> + </TouchableOpacity> + </View> + + <View style={styles.additionalFeatures}> + <TouchableOpacity style={styles.featureButton}> + <Icon name="local-cafe" size={24} color="#1E88E5" /> + <Text style={styles.featureText}>텀블러 할인 카페 찾기</Text> + </TouchableOpacity> + </View> + + <View style={styles.cardSection}> + <TouchableOpacity style={styles.card}> + <View style={styles.cardContent}> + <Text style={styles.cardTitle}>경기지역화폐 카드 연결하기</Text> + <Icon name="arrow-forward" size={24} color="#666" /> + </View> + </TouchableOpacity> + </View> + + <LocationTracker /> + </ScrollView> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f8f9fa', + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 16, + backgroundColor: '#fff', + elevation: 2, + }, + title: { + fontSize: 24, + fontWeight: 'bold', + color: '#1a237e', + }, + bellIcon: { + padding: 8, + }, + pointSection: { + margin: 16, + padding: 20, + backgroundColor: '#fff', + borderRadius: 12, + elevation: 3, + }, + pointHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + pointLabel: { + fontSize: 16, + color: '#666', + }, + pointAmount: { + fontSize: 32, + fontWeight: 'bold', + marginTop: 12, + color: '#1a237e', + }, + badge: { + backgroundColor: '#ff5252', + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 12, + }, + badgeText: { + color: '#fff', + fontSize: 12, + fontWeight: 'bold', + }, + pointStamp: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 12, + }, + stampText: { + marginLeft: 8, + color: '#1E88E5', + fontSize: 14, + }, + carbonSection: { + margin: 16, + marginTop: 0, + padding: 20, + backgroundColor: '#e8f5e9', + borderRadius: 12, + elevation: 3, + }, + carbonHeader: { + flexDirection: 'row', + alignItems: 'center', + }, + sectionTitle: { + fontSize: 16, + color: '#2e7d32', + marginLeft: 8, + fontWeight: 'bold', + }, + carbonAmount: { + fontSize: 28, + fontWeight: 'bold', + color: '#2e7d32', + marginTop: 12, + }, + quickActions: { + flexDirection: 'row', + justifyContent: 'space-between', + padding: 16, + }, + actionButton: { + flex: 1, + margin: 8, + padding: 16, + backgroundColor: '#fff', + borderRadius: 12, + alignItems: 'center', + elevation: 2, + }, + actionText: { + marginTop: 8, + fontSize: 14, + color: '#333', + }, + actionValue: { + marginTop: 4, + fontSize: 16, + color: '#1E88E5', + fontWeight: 'bold', + }, + additionalFeatures: { + padding: 16, + }, + featureButton: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + backgroundColor: '#fff', + borderRadius: 12, + elevation: 2, + }, + featureText: { + marginLeft: 12, + fontSize: 16, + color: '#333', + }, + cardSection: { + padding: 16, + }, + card: { + backgroundColor: '#fff', + borderRadius: 12, + padding: 20, + elevation: 2, + }, + cardContent: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + cardTitle: { + fontSize: 16, + color: '#333', + fontWeight: 'bold', + }, +}); + +export default HomeScreen; \ No newline at end of file diff --git a/src/screens/MyPageScreen.js b/src/screens/MyPageScreen.js new file mode 100644 index 0000000000000000000000000000000000000000..2b65e002521b1ec880f720011de173a45fca0306 --- /dev/null +++ b/src/screens/MyPageScreen.js @@ -0,0 +1,239 @@ +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; + +const MenuItem = ({ icon, title, showBorder = true }) => ( + <TouchableOpacity style={[styles.menuItem, showBorder && styles.menuBorder]}> + <View style={styles.menuContent}> + <Icon name={icon} size={22} color="#555" /> + <Text style={styles.menuText}>{title}</Text> + </View> + <Icon name="chevron-right" size={22} color="#999" /> + </TouchableOpacity> +); + +const MyPageScreen = () => { + const menuItems = [ + { icon: 'person', title: '개인정보관리' }, + { icon: 'description', title: '가입확인서 발급' }, + { icon: 'people', title: '추천인 등록' }, + { icon: 'campaign', title: '공지사항' }, + { icon: 'notifications', title: '알림마당' }, + { icon: 'help', title: '자주 묻는 질문' }, + { icon: 'headset-mic', title: '고객센터', showBorder: false } + ]; + + return ( + <ScrollView style={styles.container}> + <View style={styles.profileSection}> + <View style={styles.profileHeader}> + <View style={styles.profileInfo}> + <View style={styles.profileImageContainer}> + <Image + source={require('../assets/default-profile.png')} + style={styles.profileImage} + /> + <TouchableOpacity style={styles.editButton}> + <Icon name="edit" size={16} color="#fff" /> + </TouchableOpacity> + </View> + <View style={styles.nameSection}> + <Text style={styles.username}>jack1358</Text> + <TouchableOpacity style={styles.editProfile}> + <Text style={styles.editProfileText}>프로필 관리</Text> + <Icon name="chevron-right" size={16} color="#666" /> + </TouchableOpacity> + </View> + </View> + </View> + + <View style={styles.statsContainer}> + <View style={styles.statItem}> + <Text style={styles.statLabel}>내 적립 리워드</Text> + <Text style={styles.statValue}>0원</Text> + </View> + <View style={styles.statDivider} /> + <View style={styles.statItem}> + <Text style={styles.statLabel}>온실가스 저감효과</Text> + <Text style={[styles.statValue, styles.greenText]}>0kgCO₂eq</Text> + </View> + </View> + </View> + + <View style={styles.cardSection}> + <View style={styles.cardContainer}> + <TouchableOpacity style={styles.card}> + <Icon name="credit-card" size={24} color="#1E88E5" /> + <Text style={styles.cardText}>교통카드 등록</Text> + </TouchableOpacity> + <TouchableOpacity style={styles.card}> + <Icon name="account-balance-wallet" size={24} color="#1E88E5" /> + <Text style={styles.cardText}>경기지역화폐 카드 연결</Text> + </TouchableOpacity> + </View> + </View> + + <View style={styles.menuSection}> + {menuItems.map((item, index) => ( + <MenuItem + key={index} + icon={item.icon} + title={item.title} + showBorder={item.showBorder !== false} + /> + ))} + </View> + + <View style={styles.footer}> + <Text style={styles.version}>앱 버전 1.0.0</Text> + </View> + </ScrollView> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f8f9fa', + }, + profileSection: { + backgroundColor: '#fff', + paddingTop: 20, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + profileHeader: { + paddingHorizontal: 20, + }, + profileInfo: { + flexDirection: 'row', + alignItems: 'center', + }, + profileImageContainer: { + position: 'relative', + }, + profileImage: { + width: 80, + height: 80, + borderRadius: 40, + backgroundColor: '#f0f0f0', + }, + editButton: { + position: 'absolute', + right: 0, + bottom: 0, + backgroundColor: '#1E88E5', + width: 28, + height: 28, + borderRadius: 14, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 2, + borderColor: '#fff', + }, + nameSection: { + marginLeft: 20, + flex: 1, + }, + username: { + fontSize: 24, + fontWeight: 'bold', + color: '#333', + }, + editProfile: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 8, + }, + editProfileText: { + color: '#666', + fontSize: 14, + }, + statsContainer: { + flexDirection: 'row', + paddingVertical: 20, + marginTop: 20, + backgroundColor: '#f8f9fa', + }, + statItem: { + flex: 1, + alignItems: 'center', + }, + statDivider: { + width: 1, + backgroundColor: '#ddd', + marginVertical: 10, + }, + statLabel: { + fontSize: 14, + color: '#666', + marginBottom: 8, + }, + statValue: { + fontSize: 20, + fontWeight: 'bold', + color: '#333', + }, + greenText: { + color: '#2E7D32', + }, + cardSection: { + padding: 20, + }, + cardContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + card: { + flex: 1, + backgroundColor: '#fff', + padding: 16, + borderRadius: 12, + alignItems: 'center', + marginHorizontal: 6, + elevation: 2, + }, + cardText: { + marginTop: 8, + fontSize: 12, + color: '#333', + textAlign: 'center', + }, + menuSection: { + backgroundColor: '#fff', + borderRadius: 12, + marginHorizontal: 20, + marginBottom: 20, + elevation: 2, + }, + menuItem: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: 16, + paddingHorizontal: 20, + }, + menuContent: { + flexDirection: 'row', + alignItems: 'center', + }, + menuBorder: { + borderBottomWidth: 1, + borderBottomColor: '#f0f0f0', + }, + menuText: { + fontSize: 16, + color: '#333', + marginLeft: 12, + }, + footer: { + padding: 20, + alignItems: 'center', + }, + version: { + color: '#999', + fontSize: 14, + }, +}); + +export default MyPageScreen; \ No newline at end of file diff --git a/sync.sh b/sync.sh index a2403ac9932b837607179afa5977f559185a4c8f..448b5e8c03df2002227b553eae3e8e5822d7a429 100755 --- a/sync.sh +++ b/sync.sh @@ -30,7 +30,7 @@ if [ -f "src/package.json" ]; then rm ../bin_buddy_app/package.json.bak cd ../bin_buddy_app - npm install + npm install --legacy-peer-deps cd ../bin_buddy_app_env fi