favorites.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import React, { useEffect, useState } from 'react';
  2. import {
  3. View,
  4. Text,
  5. StyleSheet,
  6. TouchableOpacity,
  7. FlatList,
  8. TextInput,
  9. Modal,
  10. Alert,
  11. } from 'react-native';
  12. import AsyncStorage from '@react-native-async-storage/async-storage';
  13. import { AntDesign } from '@expo/vector-icons';
  14. import { Song } from '@/types/song';
  15. import {loadFavoriteFolders, saveFavoriteFolders} from "@/service/async-storage";
  16. interface Folder {
  17. id: string;
  18. name: string;
  19. songs: Song[];
  20. expanded: boolean;
  21. }
  22. export default function FavoriteScreen() {
  23. const [folders, setFolders] = useState<Folder[]>([]);
  24. const [modalVisible, setModalVisible] = useState(false);
  25. const [newFolderName, setNewFolderName] = useState('');
  26. useEffect(() => {
  27. loadFavoriteFolders().then((folders) => setFolders(folders))
  28. }, []);
  29. const toggleFolder = (id: string) => {
  30. const updated = folders.map((f) =>
  31. f.id === id ? { ...f, expanded: !f.expanded } : f
  32. );
  33. setFolders(updated);
  34. saveFavoriteFolders(updated);
  35. };
  36. const addFolder = () => {
  37. if (!newFolderName.trim()) return;
  38. const newFolder: Folder = {
  39. id: Date.now().toString(),
  40. name: newFolderName.trim(),
  41. songs: [],
  42. expanded: false,
  43. };
  44. const updated = [...folders, newFolder];
  45. setFolders(updated);
  46. saveFavoriteFolders(updated);
  47. setNewFolderName('');
  48. setModalVisible(false);
  49. };
  50. const deleteFolder = (id: string) => {
  51. Alert.alert('삭제 확인', '이 폴더를 삭제하시겠습니까?', [
  52. { text: '취소', style: 'cancel' },
  53. {
  54. text: '삭제',
  55. style: 'destructive',
  56. onPress: () => {
  57. const updated = folders.filter((f) => f.id !== id);
  58. setFolders(updated);
  59. saveFavoriteFolders(updated);
  60. },
  61. },
  62. ]);
  63. };
  64. const renderFolder = ({ item }: { item: Folder }) => (
  65. <View style={styles.folderContainer}>
  66. <TouchableOpacity
  67. onPress={() => toggleFolder(item.id)}
  68. style={styles.folderHeader}
  69. >
  70. <Text style={styles.folderTitle}>{item.name}</Text>
  71. <View style={{ flexDirection: 'row' }}>
  72. <TouchableOpacity onPress={() => deleteFolder(item.id)}>
  73. <AntDesign name="delete" size={20} color="gray" style={{ marginLeft: 10 }} />
  74. </TouchableOpacity>
  75. <AntDesign
  76. name={item.expanded ? 'up' : 'down'}
  77. size={16}
  78. color="black"
  79. style={{ marginLeft: 10 }}
  80. />
  81. </View>
  82. </TouchableOpacity>
  83. {item.expanded &&
  84. item.songs.map((song) => (
  85. <View key={song.no} style={styles.songItem}>
  86. <Text style={styles.songText}>
  87. 🎵 {song.title} - {song.singer}
  88. </Text>
  89. </View>
  90. ))}
  91. </View>
  92. );
  93. return (
  94. <View style={styles.container}>
  95. <Text style={styles.title}>⭐ 즐겨찾기</Text>
  96. <FlatList
  97. data={folders}
  98. renderItem={renderFolder}
  99. keyExtractor={(item) => item.id}
  100. contentContainerStyle={{ paddingBottom: 80 }}
  101. />
  102. <TouchableOpacity
  103. style={styles.addButton}
  104. onPress={() => setModalVisible(true)}
  105. >
  106. <Text style={styles.addButtonText}>+ 새 폴더 추가</Text>
  107. </TouchableOpacity>
  108. {/* 폴더 추가 모달 */}
  109. <Modal visible={modalVisible} transparent animationType="slide">
  110. <View style={styles.modalOverlay}>
  111. <View style={styles.modalContent}>
  112. <Text style={{ fontWeight: 'bold', marginBottom: 10 }}>폴더 이름 입력</Text>
  113. <TextInput
  114. placeholder="예: 나만의 명곡"
  115. value={newFolderName}
  116. onChangeText={setNewFolderName}
  117. style={styles.input}
  118. />
  119. <View style={{ flexDirection: 'row', marginTop: 10 }}>
  120. <TouchableOpacity
  121. style={[styles.modalButton, { backgroundColor: '#ccc' }]}
  122. onPress={() => setModalVisible(false)}
  123. >
  124. <Text>취소</Text>
  125. </TouchableOpacity>
  126. <TouchableOpacity
  127. style={[styles.modalButton, { backgroundColor: '#4CAF50' }]}
  128. onPress={addFolder}
  129. >
  130. <Text style={{ color: 'white' }}>확인</Text>
  131. </TouchableOpacity>
  132. </View>
  133. </View>
  134. </View>
  135. </Modal>
  136. </View>
  137. );
  138. }
  139. const styles = StyleSheet.create({
  140. container: { flex: 1, padding: 16 },
  141. title: { fontSize: 20, fontWeight: 'bold', marginBottom: 12 },
  142. folderContainer: { marginBottom: 12, borderBottomWidth: 1, borderColor: '#ddd' },
  143. folderHeader: {
  144. flexDirection: 'row',
  145. justifyContent: 'space-between',
  146. paddingVertical: 8,
  147. alignItems: 'center',
  148. },
  149. folderTitle: { fontSize: 16, fontWeight: 'bold' },
  150. songItem: { paddingLeft: 16, paddingVertical: 4 },
  151. songText: { fontSize: 14 },
  152. addButton: {
  153. position: 'absolute',
  154. bottom: 20,
  155. left: 20,
  156. right: 20,
  157. backgroundColor: '#007AFF',
  158. padding: 16,
  159. borderRadius: 10,
  160. alignItems: 'center',
  161. },
  162. addButtonText: { color: 'white', fontWeight: 'bold' },
  163. modalOverlay: {
  164. flex: 1,
  165. backgroundColor: '#00000099',
  166. justifyContent: 'center',
  167. alignItems: 'center',
  168. },
  169. modalContent: {
  170. width: 300,
  171. backgroundColor: 'white',
  172. borderRadius: 10,
  173. padding: 16,
  174. },
  175. input: {
  176. borderWidth: 1,
  177. borderColor: '#ddd',
  178. borderRadius: 6,
  179. padding: 8,
  180. },
  181. modalButton: {
  182. flex: 1,
  183. padding: 10,
  184. marginHorizontal: 5,
  185. borderRadius: 6,
  186. alignItems: 'center',
  187. },
  188. });