|
|
@@ -1,19 +1,201 @@
|
|
|
-import React, { useState } from 'react';
|
|
|
+import React, { useEffect, useState } from 'react';
|
|
|
import {
|
|
|
View,
|
|
|
Text,
|
|
|
StyleSheet,
|
|
|
+ TouchableOpacity,
|
|
|
+ FlatList,
|
|
|
+ TextInput,
|
|
|
+ Modal,
|
|
|
+ Alert,
|
|
|
} from 'react-native';
|
|
|
+import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
+import { AntDesign } from '@expo/vector-icons';
|
|
|
+import { Song } from '@/types/song';
|
|
|
+import {loadFavoriteFolders, saveFavoriteFolders} from "@/service/async-storage";
|
|
|
+
|
|
|
+interface Folder {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ songs: Song[];
|
|
|
+ expanded: boolean;
|
|
|
+}
|
|
|
|
|
|
export default function FavoriteScreen() {
|
|
|
+ const [folders, setFolders] = useState<Folder[]>([]);
|
|
|
+ const [modalVisible, setModalVisible] = useState(false);
|
|
|
+ const [newFolderName, setNewFolderName] = useState('');
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ loadFavoriteFolders().then((folders) => setFolders(folders))
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const toggleFolder = (id: string) => {
|
|
|
+ const updated = folders.map((f) =>
|
|
|
+ f.id === id ? { ...f, expanded: !f.expanded } : f
|
|
|
+ );
|
|
|
+ setFolders(updated);
|
|
|
+ saveFavoriteFolders(updated);
|
|
|
+ };
|
|
|
+
|
|
|
+ const addFolder = () => {
|
|
|
+ if (!newFolderName.trim()) return;
|
|
|
+
|
|
|
+ const newFolder: Folder = {
|
|
|
+ id: Date.now().toString(),
|
|
|
+ name: newFolderName.trim(),
|
|
|
+ songs: [],
|
|
|
+ expanded: false,
|
|
|
+ };
|
|
|
+ const updated = [...folders, newFolder];
|
|
|
+ setFolders(updated);
|
|
|
+ saveFavoriteFolders(updated);
|
|
|
+ setNewFolderName('');
|
|
|
+ setModalVisible(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ const deleteFolder = (id: string) => {
|
|
|
+ Alert.alert('삭제 확인', '이 폴더를 삭제하시겠습니까?', [
|
|
|
+ { text: '취소', style: 'cancel' },
|
|
|
+ {
|
|
|
+ text: '삭제',
|
|
|
+ style: 'destructive',
|
|
|
+ onPress: () => {
|
|
|
+ const updated = folders.filter((f) => f.id !== id);
|
|
|
+ setFolders(updated);
|
|
|
+ saveFavoriteFolders(updated);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ };
|
|
|
+
|
|
|
+ const renderFolder = ({ item }: { item: Folder }) => (
|
|
|
+ <View style={styles.folderContainer}>
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={() => toggleFolder(item.id)}
|
|
|
+ style={styles.folderHeader}
|
|
|
+ >
|
|
|
+ <Text style={styles.folderTitle}>{item.name}</Text>
|
|
|
+ <View style={{ flexDirection: 'row' }}>
|
|
|
+ <TouchableOpacity onPress={() => deleteFolder(item.id)}>
|
|
|
+ <AntDesign name="delete" size={20} color="gray" style={{ marginLeft: 10 }} />
|
|
|
+ </TouchableOpacity>
|
|
|
+ <AntDesign
|
|
|
+ name={item.expanded ? 'up' : 'down'}
|
|
|
+ size={16}
|
|
|
+ color="black"
|
|
|
+ style={{ marginLeft: 10 }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ {item.expanded &&
|
|
|
+ item.songs.map((song) => (
|
|
|
+ <View key={song.no} style={styles.songItem}>
|
|
|
+ <Text style={styles.songText}>
|
|
|
+ 🎵 {song.title} - {song.singer}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+
|
|
|
return (
|
|
|
- <View>
|
|
|
- <Text>⭐즐겨찾기</Text>
|
|
|
+ <View style={styles.container}>
|
|
|
+ <Text style={styles.title}>⭐ 즐겨찾기</Text>
|
|
|
+ <FlatList
|
|
|
+ data={folders}
|
|
|
+ renderItem={renderFolder}
|
|
|
+ keyExtractor={(item) => item.id}
|
|
|
+ contentContainerStyle={{ paddingBottom: 80 }}
|
|
|
+ />
|
|
|
+
|
|
|
+ <TouchableOpacity
|
|
|
+ style={styles.addButton}
|
|
|
+ onPress={() => setModalVisible(true)}
|
|
|
+ >
|
|
|
+ <Text style={styles.addButtonText}>+ 새 폴더 추가</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ {/* 폴더 추가 모달 */}
|
|
|
+ <Modal visible={modalVisible} transparent animationType="slide">
|
|
|
+ <View style={styles.modalOverlay}>
|
|
|
+ <View style={styles.modalContent}>
|
|
|
+ <Text style={{ fontWeight: 'bold', marginBottom: 10 }}>폴더 이름 입력</Text>
|
|
|
+ <TextInput
|
|
|
+ placeholder="예: 나만의 명곡"
|
|
|
+ value={newFolderName}
|
|
|
+ onChangeText={setNewFolderName}
|
|
|
+ style={styles.input}
|
|
|
+ />
|
|
|
+ <View style={{ flexDirection: 'row', marginTop: 10 }}>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.modalButton, { backgroundColor: '#ccc' }]}
|
|
|
+ onPress={() => setModalVisible(false)}
|
|
|
+ >
|
|
|
+ <Text>취소</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.modalButton, { backgroundColor: '#4CAF50' }]}
|
|
|
+ onPress={addFolder}
|
|
|
+ >
|
|
|
+ <Text style={{ color: 'white' }}>확인</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </Modal>
|
|
|
</View>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
|
-
|
|
|
-});
|
|
|
+ container: { flex: 1, padding: 16 },
|
|
|
+ title: { fontSize: 20, fontWeight: 'bold', marginBottom: 12 },
|
|
|
+ folderContainer: { marginBottom: 12, borderBottomWidth: 1, borderColor: '#ddd' },
|
|
|
+ folderHeader: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ paddingVertical: 8,
|
|
|
+ alignItems: 'center',
|
|
|
+ },
|
|
|
+ folderTitle: { fontSize: 16, fontWeight: 'bold' },
|
|
|
+ songItem: { paddingLeft: 16, paddingVertical: 4 },
|
|
|
+ songText: { fontSize: 14 },
|
|
|
+ addButton: {
|
|
|
+ position: 'absolute',
|
|
|
+ bottom: 20,
|
|
|
+ left: 20,
|
|
|
+ right: 20,
|
|
|
+ backgroundColor: '#007AFF',
|
|
|
+ padding: 16,
|
|
|
+ borderRadius: 10,
|
|
|
+ alignItems: 'center',
|
|
|
+ },
|
|
|
+ addButtonText: { color: 'white', fontWeight: 'bold' },
|
|
|
+ modalOverlay: {
|
|
|
+ flex: 1,
|
|
|
+ backgroundColor: '#00000099',
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ },
|
|
|
+ modalContent: {
|
|
|
+ width: 300,
|
|
|
+ backgroundColor: 'white',
|
|
|
+ borderRadius: 10,
|
|
|
+ padding: 16,
|
|
|
+ },
|
|
|
+ input: {
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: '#ddd',
|
|
|
+ borderRadius: 6,
|
|
|
+ padding: 8,
|
|
|
+ },
|
|
|
+ modalButton: {
|
|
|
+ flex: 1,
|
|
|
+ padding: 10,
|
|
|
+ marginHorizontal: 5,
|
|
|
+ borderRadius: 6,
|
|
|
+ alignItems: 'center',
|
|
|
+ },
|
|
|
+});
|