앱: 애니메이션 및 네비게이션 개선, 문서 업데이트
This commit is contained in:
parent
e52951f43c
commit
a89139f056
7 changed files with 324 additions and 61 deletions
|
|
@ -121,7 +121,16 @@ export default function AppNavigator() {
|
|||
>
|
||||
<Tab.Screen name="HomeTab" component={HomeScreen} />
|
||||
<Tab.Screen name="MembersTab" component={MembersScreen} />
|
||||
<Tab.Screen name="AlbumTab" component={AlbumStackNavigator} />
|
||||
<Tab.Screen
|
||||
name="AlbumTab"
|
||||
component={AlbumStackNavigator}
|
||||
listeners={({ navigation }) => ({
|
||||
tabPress: (e) => {
|
||||
// 앨범 탭 클릭 시 스택을 루트(목록)으로 리셋
|
||||
navigation.navigate('AlbumTab', { screen: 'AlbumList' });
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<Tab.Screen name="ScheduleTab" component={ScheduleScreen} />
|
||||
</Tab.Navigator>
|
||||
</NavigationContainer>
|
||||
|
|
|
|||
|
|
@ -74,28 +74,32 @@ export default function AlbumScreen() {
|
|||
{/* 2열 그리드 */}
|
||||
<View style={styles.grid}>
|
||||
{albums.map((album) => (
|
||||
<TouchableOpacity
|
||||
<View
|
||||
key={album.id}
|
||||
onPress={() => navigation.navigate('AlbumDetail', { name: album.folder_name })}
|
||||
style={styles.albumCard}
|
||||
activeOpacity={0.7}
|
||||
style={styles.albumCardWrapper}
|
||||
>
|
||||
<View style={styles.albumImageContainer}>
|
||||
<Image
|
||||
source={{ uri: album.cover_thumb_url || album.cover_medium_url }}
|
||||
style={styles.albumImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.albumInfo}>
|
||||
<Text style={styles.albumTitle} numberOfLines={1}>
|
||||
{album.title}
|
||||
</Text>
|
||||
<Text style={styles.albumType}>
|
||||
{album.album_type_short} · {album.release_date?.slice(0, 4)}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate('AlbumDetail', { name: album.folder_name })}
|
||||
style={styles.albumCard}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.albumImageContainer}>
|
||||
<Image
|
||||
source={{ uri: album.cover_thumb_url || album.cover_medium_url }}
|
||||
style={styles.albumImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.albumInfo}>
|
||||
<Text style={styles.albumTitle} numberOfLines={1}>
|
||||
{album.title}
|
||||
</Text>
|
||||
<Text style={styles.albumType}>
|
||||
{album.album_type_short} · {album.release_date?.slice(0, 4)}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
|
@ -124,17 +128,19 @@ const styles = StyleSheet.create({
|
|||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
albumCard: {
|
||||
albumCardWrapper: {
|
||||
width: '48%',
|
||||
marginBottom: 16,
|
||||
},
|
||||
albumCard: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 16,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 3,
|
||||
elevation: 2,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
albumImageContainer: {
|
||||
aspectRatio: 1,
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ export default function HomeScreen() {
|
|||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// 애니메이션
|
||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||
const slideAnim = useRef(new Animated.Value(20)).current;
|
||||
const fadeAnim = useRef(new Animated.Value(1)).current;
|
||||
const slideAnim = useRef(new Animated.Value(0)).current;
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
|
|
@ -49,20 +49,6 @@ export default function HomeScreen() {
|
|||
setMembers(membersData.filter(m => !m.is_former));
|
||||
setAlbums(albumsData.slice(0, 2));
|
||||
setSchedules(schedulesData || []);
|
||||
|
||||
// 애니메이션 시작
|
||||
Animated.parallel([
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 500,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(slideAnim, {
|
||||
toValue: 0,
|
||||
duration: 500,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start();
|
||||
} catch (error) {
|
||||
console.error('데이터 로드 오류:', error);
|
||||
} finally {
|
||||
|
|
@ -77,8 +63,6 @@ export default function HomeScreen() {
|
|||
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
fadeAnim.setValue(0);
|
||||
slideAnim.setValue(20);
|
||||
fetchData();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -74,17 +74,21 @@ export default function MembersScreen() {
|
|||
})
|
||||
).current;
|
||||
|
||||
const startAnimation = () => {
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 300,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
};
|
||||
|
||||
const fetchMembers = async () => {
|
||||
try {
|
||||
const data = await getMembers();
|
||||
setMembers(data.filter(m => !m.is_former));
|
||||
setFormerMembers(data.filter(m => m.is_former));
|
||||
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: 1,
|
||||
duration: 300,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
startAnimation();
|
||||
} catch (error) {
|
||||
console.error('멤버 로드 오류:', error);
|
||||
} finally {
|
||||
|
|
@ -99,7 +103,6 @@ export default function MembersScreen() {
|
|||
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
fadeAnim.setValue(0);
|
||||
fetchMembers();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -669,18 +669,129 @@ node scripts/extract-keywords.js
|
|||
|
||||
---
|
||||
|
||||
## 15. 오늘 작업 요약 (2026-01-11)
|
||||
## 15. 모바일 앱 (`/app`)
|
||||
|
||||
### 기술 스택
|
||||
|
||||
| 계층 | 기술 |
|
||||
| -------------- | ------------------------------------------------------------------------ |
|
||||
| **프레임워크** | Expo (React Native) |
|
||||
| **언어** | TypeScript |
|
||||
| **네비게이션** | React Navigation (Tab + Stack) |
|
||||
| **UI 효과** | expo-blur, expo-linear-gradient, react-native-color-matrix-image-filters |
|
||||
| **미디어** | expo-file-system, expo-media-library, react-native-pager-view |
|
||||
|
||||
### 디렉토리 구조
|
||||
|
||||
```
|
||||
app/src/
|
||||
├── api/ # API 호출 함수
|
||||
│ ├── albums.ts # 앨범 API (AlbumPhoto에 width/height 포함)
|
||||
│ ├── members.ts # 멤버 API
|
||||
│ └── schedules.ts # 일정 API
|
||||
├── components/ # 공통 컴포넌트
|
||||
│ └── common/
|
||||
│ └── Header.tsx # 공통 헤더 (title, showBack, rightElement)
|
||||
├── constants/
|
||||
│ └── colors.ts # 테마 색상 (primary: #FF4D8D)
|
||||
├── navigation/
|
||||
│ └── AppNavigator.tsx # 탭 + 스택 네비게이션
|
||||
└── screens/
|
||||
├── HomeScreen.tsx # 홈 (멤버, 앨범, 일정 요약)
|
||||
├── MembersScreen.tsx # 멤버 목록 + 바텀시트 상세
|
||||
├── AlbumScreen.tsx # 앨범 목록 (2열 그리드)
|
||||
├── AlbumDetailScreen.tsx # 앨범 상세 (트랙, 티저, 포토)
|
||||
├── AlbumGalleryScreen.tsx # 컨셉포토 갤러리 (라이트박스)
|
||||
└── ScheduleScreen.tsx # 일정 목록
|
||||
```
|
||||
|
||||
### 네비게이션 구조
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
TabNav[TabNavigator 하단 탭]
|
||||
TabNav --> HomeTab[홈]
|
||||
TabNav --> MembersTab[멤버]
|
||||
TabNav --> AlbumTab[앨범]
|
||||
TabNav --> ScheduleTab[일정]
|
||||
|
||||
AlbumTab --> AlbumStack[AlbumStackNavigator]
|
||||
AlbumStack --> AlbumList[AlbumScreen]
|
||||
AlbumStack --> AlbumDetail[AlbumDetailScreen]
|
||||
AlbumStack --> AlbumGallery[AlbumGalleryScreen]
|
||||
```
|
||||
|
||||
### 주요 기능
|
||||
|
||||
#### 탭 전환 시 앨범 스택 리셋
|
||||
|
||||
```tsx
|
||||
// AppNavigator.tsx
|
||||
<Tab.Screen
|
||||
name="AlbumTab"
|
||||
component={AlbumStackNavigator}
|
||||
listeners={({ navigation }) => ({
|
||||
tabPress: (e) => {
|
||||
// 앨범 탭 클릭 시 스택을 루트(목록)으로 리셋
|
||||
navigation.navigate("AlbumTab", { screen: "AlbumList" });
|
||||
},
|
||||
})}
|
||||
/>
|
||||
```
|
||||
|
||||
#### AlbumGalleryScreen (컨셉포토 라이트박스)
|
||||
|
||||
- **PagerView**: 스와이프로 이미지 탐색
|
||||
- **페이지 인디케이터**: `n / total` 형식
|
||||
- **다운로드 기능**: `expo-file-system` + `expo-media-library`
|
||||
- 웹 버전과 1:1 동일한 UI
|
||||
|
||||
#### MembersScreen (멤버 상세)
|
||||
|
||||
- **바텀시트 모달**: PanResponder 드래그로 닫기
|
||||
- **전 멤버 흑백 처리**: `Grayscale` 필터
|
||||
- **글래스모피즘**: `BlurView` (intensity 30, dimezisBlurView)
|
||||
|
||||
### 개발 명령어
|
||||
|
||||
```bash
|
||||
# 개발 서버 실행
|
||||
cd /docker/fromis_9/app
|
||||
npx expo start --lan
|
||||
|
||||
# Android APK 빌드
|
||||
npx expo run:android --variant release
|
||||
|
||||
# 로컬 네이티브 빌드 (android/ 폴더에서)
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
### 웹-앱 동기화 체크리스트
|
||||
|
||||
| 화면 | 웹 경로 | 앱 파일 |
|
||||
| ----------- | -------------------------------- | ------------------------ |
|
||||
| 홈 | `mobile/public/Home.jsx` | `HomeScreen.tsx` |
|
||||
| 멤버 | `mobile/public/Members.jsx` | `MembersScreen.tsx` |
|
||||
| 앨범 목록 | `mobile/public/Album.jsx` | `AlbumScreen.tsx` |
|
||||
| 앨범 상세 | `mobile/public/AlbumDetail.jsx` | `AlbumDetailScreen.tsx` |
|
||||
| 앨범 갤러리 | `mobile/public/AlbumGallery.jsx` | `AlbumGalleryScreen.tsx` |
|
||||
| 일정 | `mobile/public/Schedule.jsx` | `ScheduleScreen.tsx` |
|
||||
|
||||
---
|
||||
|
||||
## 16. 오늘 작업 요약 (2026-01-11 ~ 2026-01-12)
|
||||
|
||||
### 커밋 히스토리
|
||||
|
||||
| 커밋 | 설명 |
|
||||
| --------- | ------------------------------------------------------- |
|
||||
| `727b05f` | Redis 기반 bi-gram 추천 검색어 시스템 구현 |
|
||||
| `9c2ff74` | Admin Schedule 추천 검색어 연동 + 빈 상태 드롭다운 숨김 |
|
||||
| `2ad5341` | Mobile Schedule 추천 검색어 API 연동 |
|
||||
| `8e3cab9` | 기본 카테고리 보호 및 ID 재정렬 |
|
||||
| `de2e02f` | 모바일 앨범 상세 UI 개선 |
|
||||
| `d6bc8d7` | 모바일 앨범 갤러리 UI 대폭 개선 (Swiper, 뒤로가기 등) |
|
||||
| 커밋 | 설명 |
|
||||
| --------- | ----------------------------------------------------------------- |
|
||||
| `727b05f` | Redis 기반 bi-gram 추천 검색어 시스템 구현 |
|
||||
| `9c2ff74` | Admin Schedule 추천 검색어 연동 + 빈 상태 드롭다운 숨김 |
|
||||
| `2ad5341` | Mobile Schedule 추천 검색어 API 연동 |
|
||||
| `8e3cab9` | 기본 카테고리 보호 및 ID 재정렬 |
|
||||
| `de2e02f` | 모바일 앨범 상세 UI 개선 |
|
||||
| `d6bc8d7` | 모바일 앨범 갤러리 UI 대폭 개선 (Swiper, 뒤로가기 등) |
|
||||
| `bf6b7f7` | 앱: 앨범 화면 2열 그리드 레이아웃 개선, 탭 전환 시 앨범 스택 리셋 |
|
||||
|
||||
### 주요 변경 사항
|
||||
|
||||
|
|
@ -688,3 +799,4 @@ node scripts/extract-keywords.js
|
|||
2. **모바일 앨범 갤러리**: Swiper ViewPager + LightboxIndicator
|
||||
3. **뒤로가기 처리**: History API로 모달/라이트박스 닫기
|
||||
4. **카테고리 보호**: 시스템 기본 카테고리 삭제 방지
|
||||
5. **앱 앨범 스택 리셋**: 탭 전환 시 목록으로 자동 복귀
|
||||
|
|
|
|||
149
docs/handover.md
Normal file
149
docs/handover.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# fromis_9 프로젝트 인수인계서
|
||||
|
||||
## 프로젝트 개요
|
||||
fromis_9 K-pop 아이돌 팬사이트 - 웹 프론트엔드, 백엔드 API, 모바일 앱으로 구성
|
||||
|
||||
---
|
||||
|
||||
## 1. 디렉토리 구조
|
||||
|
||||
```
|
||||
/docker/fromis_9/
|
||||
├── frontend/ # React 웹 프론트엔드 (Vite)
|
||||
├── backend/ # Express.js 백엔드 API
|
||||
├── app/ # React Native 모바일 앱 (Expo)
|
||||
└── .env # 환경변수 (DB 접속정보 등)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 웹 프론트엔드 (`/frontend`)
|
||||
|
||||
### 기술 스택
|
||||
- React + Vite
|
||||
- TailwindCSS
|
||||
- framer-motion (애니메이션)
|
||||
|
||||
### 주요 경로
|
||||
- `src/pages/` - 페이지 컴포넌트
|
||||
- `public/` - 공개 페이지 (Home, Members, Album 등)
|
||||
- `mobile/public/` - 모바일 전용 페이지
|
||||
- `admin/` - 관리자 페이지
|
||||
- `src/api/` - API 호출 함수
|
||||
- `src/components/` - 재사용 컴포넌트
|
||||
|
||||
---
|
||||
|
||||
## 3. 백엔드 (`/backend`)
|
||||
|
||||
### 기술 스택
|
||||
- Express.js
|
||||
- MariaDB (mysql2)
|
||||
- RustFS (파일 스토리지)
|
||||
|
||||
### 주요 경로
|
||||
- `routes/` - API 라우트
|
||||
- `public/` - 공개 API
|
||||
- `admin/` - 관리자 API
|
||||
- `lib/` - 유틸리티 (DB, 파일 업로드 등)
|
||||
|
||||
---
|
||||
|
||||
## 4. 모바일 앱 (`/app`)
|
||||
|
||||
### 기술 스택
|
||||
- **Expo** (React Native)
|
||||
- **TypeScript**
|
||||
- React Navigation (탭 + 스택 네비게이션)
|
||||
- expo-blur, expo-linear-gradient (UI 효과)
|
||||
|
||||
### 주요 경로
|
||||
```
|
||||
app/src/
|
||||
├── api/ # API 호출 함수
|
||||
│ ├── albums.ts # 앨범 API
|
||||
│ ├── members.ts # 멤버 API
|
||||
│ └── schedules.ts # 일정 API
|
||||
├── components/ # 공통 컴포넌트
|
||||
│ └── common/
|
||||
│ └── Header.tsx # 공통 헤더 (뒤로가기, 타이틀, rightElement)
|
||||
├── navigation/ # 네비게이션 설정
|
||||
│ └── AppNavigator.tsx
|
||||
├── screens/ # 화면 컴포넌트
|
||||
│ ├── HomeScreen.tsx
|
||||
│ ├── MembersScreen.tsx
|
||||
│ ├── AlbumScreen.tsx
|
||||
│ ├── AlbumDetailScreen.tsx
|
||||
│ ├── AlbumGalleryScreen.tsx # 컨셉포토 갤러리 (라이트박스)
|
||||
│ └── ScheduleScreen.tsx
|
||||
└── constants/ # 상수 (colors 등)
|
||||
```
|
||||
|
||||
### 네비게이션 구조
|
||||
```
|
||||
TabNavigator (하단 탭)
|
||||
├── HomeTab → HomeScreen
|
||||
├── MembersTab → MembersScreen
|
||||
├── AlbumTab → AlbumStackNavigator
|
||||
│ ├── AlbumList → AlbumScreen
|
||||
│ ├── AlbumDetail → AlbumDetailScreen
|
||||
│ └── AlbumGallery → AlbumGalleryScreen
|
||||
└── ScheduleTab → ScheduleScreen
|
||||
```
|
||||
|
||||
### 주요 기능
|
||||
- **탭 전환 시 앨범 스택 리셋**: 다른 탭 갔다가 앨범 탭 클릭 시 목록으로 돌아감
|
||||
- **AlbumGalleryScreen**: 웹과 1:1 동일한 컨셉포토 갤러리 (PagerView 라이트박스, 다운로드)
|
||||
- **MembersScreen**: 바텀시트 모달, 전 멤버 흑백 처리
|
||||
|
||||
### 개발 서버 실행
|
||||
```bash
|
||||
cd /docker/fromis_9/app
|
||||
npx expo start --lan
|
||||
```
|
||||
|
||||
### APK 빌드
|
||||
```bash
|
||||
npx expo run:android --variant release
|
||||
# 또는 로컬 빌드
|
||||
./gradlew assembleDebug # android/ 폴더에서
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 분석 절차
|
||||
|
||||
### 5.1 코드 전수 조사
|
||||
```bash
|
||||
# 프로젝트 구조 확인
|
||||
find /docker/fromis_9 -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | head -50
|
||||
```
|
||||
|
||||
### 5.2 DB 구조 파악
|
||||
```bash
|
||||
# .env에서 DB 정보 확인
|
||||
cat /docker/fromis_9/.env
|
||||
|
||||
# MariaDB 접속 (컨테이너명: mariadb)
|
||||
docker exec -it mariadb mysql -u [USER] -p[PASSWORD] fromis9
|
||||
|
||||
# 테이블 목록
|
||||
SHOW TABLES;
|
||||
|
||||
# 테이블 스키마
|
||||
DESCRIBE [table_name];
|
||||
```
|
||||
|
||||
### 5.3 Caddy 설정 확인
|
||||
```bash
|
||||
cat /docker/caddy/Caddyfile | grep -A 20 "fromis9"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 주의사항
|
||||
|
||||
- **앱 HMR**: Vite처럼 자동 반영, 빌드 불필요
|
||||
- **앱 테스트**: 흔들어서 → Reload로 확인
|
||||
- **DB 접속**: `.env` 파일의 실제 자격증명 사용
|
||||
- **웹/앱 1:1 동기화**: 기능 추가 시 웹과 앱 모두 구현 필요
|
||||
|
|
@ -36,7 +36,7 @@ function MobileAlbum() {
|
|||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
onClick={() => navigate(`/album/${album.folder_name}`)}
|
||||
className="bg-white rounded-2xl overflow-hidden shadow-sm"
|
||||
className="bg-white rounded-2xl overflow-hidden shadow-md"
|
||||
>
|
||||
<div className="aspect-square bg-gray-200">
|
||||
{album.cover_thumb_url && (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue