2026-01-12 10:20:34 +09:00
|
|
|
import React, { useEffect, useState, useRef } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
View,
|
|
|
|
|
Text,
|
|
|
|
|
ScrollView,
|
|
|
|
|
Image,
|
|
|
|
|
TouchableOpacity,
|
|
|
|
|
ActivityIndicator,
|
|
|
|
|
RefreshControl,
|
|
|
|
|
StyleSheet,
|
|
|
|
|
Animated,
|
|
|
|
|
} from 'react-native';
|
|
|
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
|
|
import { useNavigation } from '@react-navigation/native';
|
|
|
|
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
|
|
|
import { ChevronRight, Clock, Tag } from 'lucide-react-native';
|
|
|
|
|
|
|
|
|
|
import { getAlbums, Album } from '../api/albums';
|
|
|
|
|
import { getMembers, Member } from '../api/members';
|
|
|
|
|
import { getUpcomingSchedules, Schedule } from '../api/schedules';
|
|
|
|
|
import { colors } from '../constants/colors';
|
2026-01-12 12:54:09 +09:00
|
|
|
import Header from '../components/common/Header';
|
2026-01-12 10:20:34 +09:00
|
|
|
import type { AlbumStackParamList } from '../navigation/AppNavigator';
|
|
|
|
|
|
|
|
|
|
type NavigationProp = NativeStackNavigationProp<AlbumStackParamList>;
|
|
|
|
|
|
|
|
|
|
// 전체 배경색 (tailwind bg-gray-50)
|
|
|
|
|
const BG_GRAY = '#F9FAFB';
|
|
|
|
|
|
|
|
|
|
export default function HomeScreen() {
|
|
|
|
|
const navigation = useNavigation<NavigationProp>();
|
|
|
|
|
const [members, setMembers] = useState<Member[]>([]);
|
|
|
|
|
const [albums, setAlbums] = useState<Album[]>([]);
|
|
|
|
|
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
|
|
|
|
|
|
// 애니메이션
|
|
|
|
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
|
|
|
|
const slideAnim = useRef(new Animated.Value(20)).current;
|
|
|
|
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const [membersData, albumsData, schedulesData] = await Promise.all([
|
|
|
|
|
getMembers(),
|
|
|
|
|
getAlbums(),
|
|
|
|
|
getUpcomingSchedules(3),
|
|
|
|
|
]);
|
|
|
|
|
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 {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchData();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const onRefresh = () => {
|
|
|
|
|
setRefreshing(true);
|
|
|
|
|
fadeAnim.setValue(0);
|
|
|
|
|
slideAnim.setValue(20);
|
|
|
|
|
fetchData();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 날짜 포맷
|
|
|
|
|
const formatScheduleDate = (dateStr: string) => {
|
|
|
|
|
const date = new Date(dateStr);
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const currentYear = today.getFullYear();
|
|
|
|
|
const currentMonth = today.getMonth();
|
|
|
|
|
|
|
|
|
|
const scheduleYear = date.getFullYear();
|
|
|
|
|
const scheduleMonth = date.getMonth();
|
|
|
|
|
const isCurrentYear = scheduleYear === currentYear;
|
|
|
|
|
const isCurrentMonth = isCurrentYear && scheduleMonth === currentMonth;
|
|
|
|
|
|
|
|
|
|
const weekdays = ['일', '월', '화', '수', '목', '금', '토'];
|
|
|
|
|
return {
|
|
|
|
|
day: date.getDate(),
|
|
|
|
|
weekday: weekdays[date.getDay()],
|
|
|
|
|
year: scheduleYear,
|
|
|
|
|
month: scheduleMonth + 1,
|
|
|
|
|
isCurrentYear,
|
|
|
|
|
isCurrentMonth,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
2026-01-12 12:54:09 +09:00
|
|
|
<SafeAreaView style={styles.container} edges={[]}>
|
|
|
|
|
<Header />
|
2026-01-12 10:20:34 +09:00
|
|
|
<View style={styles.loadingContainer}>
|
|
|
|
|
<ActivityIndicator size="large" color={colors.primary} />
|
|
|
|
|
</View>
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-12 12:54:09 +09:00
|
|
|
<SafeAreaView style={styles.container} edges={[]}>
|
|
|
|
|
{/* 공통 헤더 */}
|
|
|
|
|
<Header />
|
2026-01-12 10:20:34 +09:00
|
|
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
style={styles.scrollView}
|
|
|
|
|
refreshControl={
|
|
|
|
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
|
|
|
|
}
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
contentContainerStyle={styles.scrollContent}
|
|
|
|
|
>
|
|
|
|
|
{/* 히어로 섹션 - bg-gradient-to-br from-primary to-primary-dark py-12 px-4 */}
|
|
|
|
|
<Animated.View style={[styles.heroSection, { opacity: fadeAnim }]}>
|
|
|
|
|
<View style={styles.heroOverlay} />
|
|
|
|
|
<View style={styles.heroContent}>
|
|
|
|
|
<Text style={styles.heroTitle}>fromis_9</Text>
|
|
|
|
|
<Text style={styles.heroSubtitle}>프로미스나인</Text>
|
|
|
|
|
<Text style={styles.heroText}>
|
|
|
|
|
인사드리겠습니다. 둘, 셋!{'\n'}
|
|
|
|
|
이제는 약속해 소중히 간직해,{'\n'}
|
|
|
|
|
당신의 아이돌로 성장하겠습니다!
|
|
|
|
|
</Text>
|
|
|
|
|
</View>
|
|
|
|
|
{/* 장식 원 */}
|
|
|
|
|
<View style={[styles.decorCircle, styles.decorCircle1]} />
|
|
|
|
|
<View style={[styles.decorCircle, styles.decorCircle2]} />
|
|
|
|
|
</Animated.View>
|
|
|
|
|
|
|
|
|
|
{/* 멤버 섹션 */}
|
|
|
|
|
<Animated.View
|
|
|
|
|
style={[
|
|
|
|
|
styles.section,
|
|
|
|
|
{ opacity: fadeAnim, transform: [{ translateY: slideAnim }] }
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<View style={styles.sectionHeader}>
|
|
|
|
|
<Text style={styles.sectionTitle}>멤버</Text>
|
|
|
|
|
<TouchableOpacity style={styles.moreButton}>
|
|
|
|
|
<Text style={styles.moreButtonText}>전체보기</Text>
|
|
|
|
|
<ChevronRight size={16} color={colors.primary} />
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
</View>
|
|
|
|
|
<View style={styles.membersGrid}>
|
|
|
|
|
{members.slice(0, 5).map((member) => (
|
|
|
|
|
<View key={member.id} style={styles.memberItem}>
|
|
|
|
|
<View style={styles.memberImageContainer}>
|
|
|
|
|
{member.image_url && (
|
|
|
|
|
<Image
|
|
|
|
|
source={{ uri: member.image_url }}
|
|
|
|
|
style={styles.memberImage}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</View>
|
|
|
|
|
<Text style={styles.memberName} numberOfLines={1}>
|
|
|
|
|
{member.name}
|
|
|
|
|
</Text>
|
|
|
|
|
</View>
|
|
|
|
|
))}
|
|
|
|
|
</View>
|
|
|
|
|
</Animated.View>
|
|
|
|
|
|
|
|
|
|
{/* 앨범 섹션 */}
|
|
|
|
|
<Animated.View
|
|
|
|
|
style={[
|
|
|
|
|
styles.section,
|
|
|
|
|
{ opacity: fadeAnim, transform: [{ translateY: slideAnim }] }
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<View style={styles.sectionHeader}>
|
|
|
|
|
<Text style={styles.sectionTitle}>앨범</Text>
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
style={styles.moreButton}
|
|
|
|
|
onPress={() => navigation.navigate('AlbumList')}
|
|
|
|
|
>
|
|
|
|
|
<Text style={styles.moreButtonText}>전체보기</Text>
|
|
|
|
|
<ChevronRight size={16} color={colors.primary} />
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
</View>
|
|
|
|
|
<View style={styles.albumsGrid}>
|
|
|
|
|
{albums.map((album) => (
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
key={album.id}
|
|
|
|
|
style={styles.albumCard}
|
|
|
|
|
onPress={() => navigation.navigate('AlbumDetail', { name: album.folder_name })}
|
|
|
|
|
activeOpacity={0.98}
|
|
|
|
|
>
|
|
|
|
|
<View style={styles.albumImageContainer}>
|
|
|
|
|
<Image
|
|
|
|
|
source={{ uri: album.cover_thumb_url || album.cover_medium_url }}
|
|
|
|
|
style={styles.albumImage}
|
|
|
|
|
/>
|
|
|
|
|
</View>
|
|
|
|
|
<View style={styles.albumInfo}>
|
|
|
|
|
<Text style={styles.albumTitle} numberOfLines={1}>
|
|
|
|
|
{album.title}
|
|
|
|
|
</Text>
|
|
|
|
|
<Text style={styles.albumYear}>
|
|
|
|
|
{album.release_date?.slice(0, 4)}
|
|
|
|
|
</Text>
|
|
|
|
|
</View>
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
))}
|
|
|
|
|
</View>
|
|
|
|
|
</Animated.View>
|
|
|
|
|
|
|
|
|
|
{/* 일정 섹션 */}
|
|
|
|
|
<Animated.View
|
|
|
|
|
style={[
|
|
|
|
|
styles.scheduleSection,
|
|
|
|
|
{ opacity: fadeAnim, transform: [{ translateY: slideAnim }] }
|
|
|
|
|
]}
|
|
|
|
|
>
|
|
|
|
|
<View style={styles.sectionHeader}>
|
|
|
|
|
<Text style={styles.sectionTitle}>다가오는 일정</Text>
|
|
|
|
|
<TouchableOpacity style={styles.moreButton}>
|
|
|
|
|
<Text style={styles.moreButtonText}>전체보기</Text>
|
|
|
|
|
<ChevronRight size={16} color={colors.primary} />
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
</View>
|
|
|
|
|
{schedules.length > 0 ? (
|
|
|
|
|
<View style={styles.schedulesList}>
|
|
|
|
|
{schedules.map((schedule) => {
|
|
|
|
|
const dateInfo = formatScheduleDate(schedule.date);
|
|
|
|
|
const memberList = schedule.member_names
|
|
|
|
|
? schedule.member_names.split(',').map(n => n.trim()).filter(Boolean)
|
|
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
key={schedule.id}
|
|
|
|
|
style={styles.scheduleCard}
|
|
|
|
|
activeOpacity={0.98}
|
|
|
|
|
>
|
|
|
|
|
{/* 날짜 영역 */}
|
|
|
|
|
<View style={styles.scheduleDateContainer}>
|
|
|
|
|
{!dateInfo.isCurrentYear && (
|
|
|
|
|
<Text style={styles.scheduleDateExtra}>
|
|
|
|
|
{dateInfo.year}.{dateInfo.month}
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
{dateInfo.isCurrentYear && !dateInfo.isCurrentMonth && (
|
|
|
|
|
<Text style={styles.scheduleDateExtra}>
|
|
|
|
|
{dateInfo.month}월
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
<Text style={styles.scheduleDay}>{dateInfo.day}</Text>
|
|
|
|
|
<Text style={styles.scheduleWeekday}>{dateInfo.weekday}</Text>
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
{/* 세로 구분선 */}
|
|
|
|
|
<View style={styles.scheduleDivider} />
|
|
|
|
|
|
|
|
|
|
{/* 내용 영역 */}
|
|
|
|
|
<View style={styles.scheduleContent}>
|
|
|
|
|
<Text style={styles.scheduleTitle} numberOfLines={2}>
|
|
|
|
|
{schedule.title}
|
|
|
|
|
</Text>
|
|
|
|
|
<View style={styles.scheduleMeta}>
|
|
|
|
|
{schedule.time && (
|
|
|
|
|
<View style={styles.scheduleMetaItem}>
|
|
|
|
|
<Clock size={12} color="#9CA3AF" />
|
|
|
|
|
<Text style={styles.scheduleMetaText}>
|
|
|
|
|
{schedule.time.slice(0, 5)}
|
|
|
|
|
</Text>
|
|
|
|
|
</View>
|
|
|
|
|
)}
|
|
|
|
|
{schedule.category_name && (
|
|
|
|
|
<View style={styles.scheduleMetaItem}>
|
|
|
|
|
<Tag size={12} color="#9CA3AF" />
|
|
|
|
|
<Text style={styles.scheduleMetaText}>
|
|
|
|
|
{schedule.category_name}
|
|
|
|
|
</Text>
|
|
|
|
|
</View>
|
|
|
|
|
)}
|
|
|
|
|
</View>
|
|
|
|
|
{memberList.length > 0 && (
|
|
|
|
|
<View style={styles.memberTags}>
|
|
|
|
|
{(memberList.length >= 5 ? ['프로미스나인'] : memberList).map((name, i) => (
|
|
|
|
|
<View key={i} style={styles.memberTag}>
|
|
|
|
|
<Text style={styles.memberTagText}>{name}</Text>
|
|
|
|
|
</View>
|
|
|
|
|
))}
|
|
|
|
|
</View>
|
|
|
|
|
)}
|
|
|
|
|
</View>
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</View>
|
|
|
|
|
) : (
|
|
|
|
|
<View style={styles.emptySchedule}>
|
|
|
|
|
<Text style={styles.emptyText}>다가오는 일정이 없습니다</Text>
|
|
|
|
|
</View>
|
|
|
|
|
)}
|
|
|
|
|
</Animated.View>
|
|
|
|
|
</ScrollView>
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
|
|
|
container: {
|
|
|
|
|
flex: 1,
|
|
|
|
|
backgroundColor: BG_GRAY,
|
|
|
|
|
},
|
|
|
|
|
loadingContainer: {
|
|
|
|
|
flex: 1,
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
backgroundColor: BG_GRAY,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 헤더
|
|
|
|
|
header: {
|
|
|
|
|
backgroundColor: '#FFFFFF',
|
|
|
|
|
paddingVertical: 14,
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
shadowColor: '#000',
|
|
|
|
|
shadowOffset: { width: 0, height: 1 },
|
|
|
|
|
shadowOpacity: 0.05,
|
|
|
|
|
shadowRadius: 2,
|
|
|
|
|
elevation: 2,
|
|
|
|
|
},
|
|
|
|
|
headerTitle: {
|
|
|
|
|
fontSize: 20,
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
scrollView: {
|
|
|
|
|
flex: 1,
|
|
|
|
|
},
|
|
|
|
|
scrollContent: {
|
|
|
|
|
paddingBottom: 0,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 히어로 섹션
|
|
|
|
|
heroSection: {
|
|
|
|
|
backgroundColor: colors.primary,
|
|
|
|
|
paddingVertical: 48,
|
|
|
|
|
paddingHorizontal: 16,
|
|
|
|
|
position: 'relative',
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
},
|
|
|
|
|
heroOverlay: {
|
|
|
|
|
...StyleSheet.absoluteFillObject,
|
|
|
|
|
backgroundColor: 'rgba(0,0,0,0.1)',
|
|
|
|
|
},
|
|
|
|
|
heroContent: {
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
},
|
|
|
|
|
heroTitle: {
|
|
|
|
|
fontSize: 30,
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
color: '#FFFFFF',
|
|
|
|
|
marginBottom: 4,
|
|
|
|
|
},
|
|
|
|
|
heroSubtitle: {
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
color: '#FFFFFF',
|
|
|
|
|
fontWeight: '300',
|
|
|
|
|
marginBottom: 12,
|
|
|
|
|
},
|
|
|
|
|
heroText: {
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
color: 'rgba(255,255,255,0.8)',
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
lineHeight: 22,
|
|
|
|
|
},
|
|
|
|
|
decorCircle: {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
borderRadius: 999,
|
|
|
|
|
backgroundColor: 'rgba(255,255,255,0.1)',
|
|
|
|
|
},
|
|
|
|
|
decorCircle1: {
|
|
|
|
|
width: 128,
|
|
|
|
|
height: 128,
|
|
|
|
|
right: -32,
|
|
|
|
|
top: -32,
|
|
|
|
|
},
|
|
|
|
|
decorCircle2: {
|
|
|
|
|
width: 96,
|
|
|
|
|
height: 96,
|
|
|
|
|
left: -24,
|
|
|
|
|
bottom: -24,
|
|
|
|
|
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 섹션 공통
|
|
|
|
|
section: {
|
|
|
|
|
paddingHorizontal: 16,
|
|
|
|
|
paddingVertical: 24,
|
|
|
|
|
},
|
|
|
|
|
scheduleSection: {
|
|
|
|
|
paddingHorizontal: 16,
|
|
|
|
|
paddingTop: 16,
|
|
|
|
|
paddingBottom: 16,
|
|
|
|
|
},
|
|
|
|
|
sectionHeader: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
marginBottom: 16,
|
|
|
|
|
},
|
|
|
|
|
sectionTitle: {
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
color: '#111827',
|
|
|
|
|
},
|
|
|
|
|
moreButton: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: 4,
|
|
|
|
|
},
|
|
|
|
|
moreButtonText: {
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 멤버 섹션
|
|
|
|
|
membersGrid: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
},
|
|
|
|
|
memberItem: {
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
width: '18%',
|
|
|
|
|
},
|
|
|
|
|
memberImageContainer: {
|
|
|
|
|
width: 56,
|
|
|
|
|
height: 56,
|
|
|
|
|
borderRadius: 28,
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
backgroundColor: '#E5E7EB',
|
|
|
|
|
marginBottom: 4,
|
|
|
|
|
},
|
|
|
|
|
memberImage: {
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: '100%',
|
|
|
|
|
},
|
|
|
|
|
memberName: {
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
color: '#111827',
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 앨범 섹션
|
|
|
|
|
albumsGrid: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
gap: 12,
|
|
|
|
|
},
|
|
|
|
|
albumCard: {
|
|
|
|
|
flex: 1,
|
|
|
|
|
backgroundColor: '#FFFFFF',
|
|
|
|
|
borderRadius: 12,
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
shadowColor: '#000',
|
|
|
|
|
shadowOffset: { width: 0, height: 1 },
|
|
|
|
|
shadowOpacity: 0.05,
|
|
|
|
|
shadowRadius: 3,
|
|
|
|
|
elevation: 2,
|
|
|
|
|
},
|
|
|
|
|
albumImageContainer: {
|
|
|
|
|
aspectRatio: 1,
|
|
|
|
|
backgroundColor: '#E5E7EB',
|
|
|
|
|
},
|
|
|
|
|
albumImage: {
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: '100%',
|
|
|
|
|
},
|
|
|
|
|
albumInfo: {
|
|
|
|
|
padding: 12,
|
|
|
|
|
},
|
|
|
|
|
albumTitle: {
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
color: '#111827',
|
|
|
|
|
},
|
|
|
|
|
albumYear: {
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
color: '#9CA3AF',
|
|
|
|
|
marginTop: 2,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 일정 섹션
|
|
|
|
|
schedulesList: {
|
|
|
|
|
gap: 12,
|
|
|
|
|
},
|
|
|
|
|
scheduleCard: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
backgroundColor: '#FFFFFF',
|
|
|
|
|
padding: 16,
|
|
|
|
|
borderRadius: 12,
|
|
|
|
|
borderWidth: 1,
|
|
|
|
|
borderColor: '#F3F4F6',
|
|
|
|
|
shadowColor: '#000',
|
|
|
|
|
shadowOffset: { width: 0, height: 1 },
|
|
|
|
|
shadowOpacity: 0.03,
|
|
|
|
|
shadowRadius: 2,
|
|
|
|
|
elevation: 1,
|
|
|
|
|
},
|
|
|
|
|
scheduleDateContainer: {
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
minWidth: 50,
|
|
|
|
|
},
|
|
|
|
|
scheduleDateExtra: {
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
color: '#9CA3AF',
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
},
|
|
|
|
|
scheduleDay: {
|
|
|
|
|
fontSize: 24,
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
},
|
|
|
|
|
scheduleWeekday: {
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
color: '#9CA3AF',
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
},
|
|
|
|
|
scheduleDivider: {
|
|
|
|
|
width: 1,
|
|
|
|
|
backgroundColor: '#F3F4F6',
|
|
|
|
|
marginHorizontal: 16,
|
|
|
|
|
},
|
|
|
|
|
scheduleContent: {
|
|
|
|
|
flex: 1,
|
|
|
|
|
},
|
|
|
|
|
scheduleTitle: {
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
color: '#1F2937',
|
|
|
|
|
lineHeight: 20,
|
|
|
|
|
},
|
|
|
|
|
scheduleMeta: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
gap: 12,
|
|
|
|
|
marginTop: 8,
|
|
|
|
|
},
|
|
|
|
|
scheduleMetaItem: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: 4,
|
|
|
|
|
},
|
|
|
|
|
scheduleMetaText: {
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
color: '#9CA3AF',
|
|
|
|
|
},
|
|
|
|
|
memberTags: {
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
flexWrap: 'wrap',
|
|
|
|
|
gap: 4,
|
|
|
|
|
marginTop: 8,
|
|
|
|
|
},
|
|
|
|
|
memberTag: {
|
|
|
|
|
backgroundColor: colors.primary + '1A',
|
|
|
|
|
paddingHorizontal: 8,
|
|
|
|
|
paddingVertical: 2,
|
|
|
|
|
borderRadius: 10,
|
|
|
|
|
},
|
|
|
|
|
memberTagText: {
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
color: colors.primary,
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
},
|
|
|
|
|
emptySchedule: {
|
|
|
|
|
paddingVertical: 32,
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
},
|
|
|
|
|
emptyText: {
|
|
|
|
|
color: '#9CA3AF',
|
|
|
|
|
},
|
|
|
|
|
});
|