seach.tsx 5.6 KB

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