seach.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import React, { useState } from 'react';
  2. import {
  3. View,
  4. Text,
  5. TextInput,
  6. TouchableOpacity,
  7. FlatList,
  8. StyleSheet,
  9. Dimensions,
  10. KeyboardAvoidingView,
  11. Platform,
  12. Modal,
  13. } from 'react-native';
  14. import { fetchSongs } from '@/service/api';
  15. import { addToFavoritesStorage } from '@/service/storage';
  16. import { Song, Criteria } from '@/types/song';
  17. interface Option {
  18. label: string;
  19. value: Criteria;
  20. }
  21. export default function SearchScreen() {
  22. const [keyword, setKeyword] = useState<string>('');
  23. const [criteria, setCriteria] = useState<Criteria>('title');
  24. const [results, setResults] = useState<Song[]>([]);
  25. const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
  26. const options: Option[] = [
  27. { label: '제목', value: 'title' },
  28. { label: '가수', value: 'artist' },
  29. ];
  30. const handleSearch = async (): Promise<void> => {
  31. const filtered: Song[] = await fetchSongs(criteria, keyword);
  32. setResults(filtered);
  33. };
  34. const handleAddToFavorites = async (song: Song): Promise<void> => {
  35. const success: boolean = await addToFavoritesStorage(song);
  36. if (!success) {
  37. console.log('Failed to add to favorites');
  38. }
  39. };
  40. const handleSelectOption = (value: Criteria): void => {
  41. setCriteria(value);
  42. setIsDropdownVisible(false);
  43. };
  44. return (
  45. <KeyboardAvoidingView
  46. style={styles.container}
  47. behavior={Platform.OS === 'ios' ? 'padding' : undefined}
  48. >
  49. <View style={styles.searchRow}>
  50. <View style={styles.dropdownWrapper}>
  51. <TouchableOpacity
  52. style={styles.dropdownButton}
  53. onPress={() => setIsDropdownVisible(!isDropdownVisible)}
  54. >
  55. <Text style={styles.dropdownText}>
  56. {options.find((opt) => opt.value === criteria)?.label || '선택'}
  57. </Text>
  58. <Text style={styles.dropdownArrow}>▼</Text>
  59. </TouchableOpacity>
  60. <Modal
  61. transparent
  62. visible={isDropdownVisible}
  63. animationType="fade"
  64. onRequestClose={() => setIsDropdownVisible(false)}
  65. >
  66. <TouchableOpacity
  67. style={styles.modalOverlay}
  68. onPress={() => setIsDropdownVisible(false)}
  69. >
  70. <View style={styles.dropdownMenu}>
  71. {options.map((option) => (
  72. <TouchableOpacity
  73. key={option.value}
  74. style={styles.dropdownItem}
  75. onPress={() => handleSelectOption(option.value)}
  76. >
  77. <Text style={styles.dropdownItemText}>{option.label}</Text>
  78. </TouchableOpacity>
  79. ))}
  80. </View>
  81. </TouchableOpacity>
  82. </Modal>
  83. </View>
  84. <TextInput
  85. placeholder="검색어 입력"
  86. value={keyword}
  87. onChangeText={(text: string) => setKeyword(text)}
  88. style={styles.input}
  89. />
  90. <TouchableOpacity onPress={handleSearch} style={styles.searchButton}>
  91. <Text style={styles.searchText}>검색</Text>
  92. </TouchableOpacity>
  93. </View>
  94. <FlatList<Song>
  95. data={results}
  96. keyExtractor={(item) => item.id}
  97. renderItem={({ item }) => (
  98. <TouchableOpacity onPress={() => handleAddToFavorites(item)}>
  99. <View style={styles.item}>
  100. <Text>
  101. {item.id} - {item.title} ({item.artist})
  102. </Text>
  103. </View>
  104. </TouchableOpacity>
  105. )}
  106. ListEmptyComponent={<Text>검색 결과가 없습니다.</Text>}
  107. contentContainerStyle={{ flexGrow: 1 }}
  108. />
  109. </KeyboardAvoidingView>
  110. );
  111. }
  112. const styles = StyleSheet.create({
  113. container: {
  114. flex: 1,
  115. padding: 16,
  116. },
  117. searchRow: {
  118. flexDirection: 'row',
  119. alignItems: 'center',
  120. marginBottom: 16,
  121. width: '100%',
  122. },
  123. dropdownWrapper: {
  124. width: Dimensions.get('window').width * 0.3 - 24,
  125. marginRight: 8,
  126. },
  127. dropdownButton: {
  128. flexDirection: 'row',
  129. alignItems: 'center',
  130. justifyContent: 'space-between',
  131. borderWidth: 1,
  132. borderColor: '#ccc',
  133. borderRadius: 8,
  134. paddingHorizontal: 10,
  135. paddingVertical: 10,
  136. height: 40,
  137. backgroundColor: '#fff',
  138. },
  139. dropdownText: {
  140. fontSize: 16,
  141. color: '#000',
  142. },
  143. dropdownArrow: {
  144. fontSize: 12,
  145. color: '#000',
  146. },
  147. modalOverlay: {
  148. flex: 1,
  149. backgroundColor: 'rgba(0, 0, 0, 0.2)',
  150. justifyContent: 'flex-start',
  151. paddingTop: 120,
  152. paddingHorizontal: 16,
  153. },
  154. dropdownMenu: {
  155. backgroundColor: '#fff',
  156. borderRadius: 8,
  157. width: Dimensions.get('window').width * 0.3 - 24,
  158. elevation: 5,
  159. shadowColor: '#000',
  160. shadowOffset: { width: 0, height: 2 },
  161. shadowOpacity: 0.2,
  162. shadowRadius: 4,
  163. },
  164. dropdownItem: {
  165. paddingVertical: 10,
  166. paddingHorizontal: 10,
  167. borderBottomWidth: 1,
  168. borderBottomColor: '#eee',
  169. },
  170. dropdownItemText: {
  171. fontSize: 16,
  172. color: '#000',
  173. },
  174. input: {
  175. flex: 1,
  176. height: 40,
  177. borderWidth: 1,
  178. borderColor: '#ccc',
  179. borderRadius: 8,
  180. paddingHorizontal: 8,
  181. marginRight: 8,
  182. },
  183. searchButton: {
  184. backgroundColor: '#007AFF',
  185. paddingHorizontal: 8,
  186. paddingVertical: 10,
  187. borderRadius: 8,
  188. },
  189. searchText: {
  190. color: '#fff',
  191. fontWeight: 'bold',
  192. },
  193. item: {
  194. padding: 12,
  195. borderBottomWidth: 1,
  196. borderBottomColor: '#eee',
  197. width: Dimensions.get('window').width - 32,
  198. },
  199. });