|
|
@@ -0,0 +1,211 @@
|
|
|
+import React, { useState } from 'react';
|
|
|
+import {
|
|
|
+ View,
|
|
|
+ Text,
|
|
|
+ TextInput,
|
|
|
+ TouchableOpacity,
|
|
|
+ FlatList,
|
|
|
+ StyleSheet,
|
|
|
+ Dimensions,
|
|
|
+ KeyboardAvoidingView,
|
|
|
+ Platform,
|
|
|
+ Modal,
|
|
|
+} from 'react-native';
|
|
|
+import { fetchSongs } from '@/service/api';
|
|
|
+import { addToFavoritesStorage } from '@/service/storage';
|
|
|
+import { Song, Criteria } from '@/types/song';
|
|
|
+
|
|
|
+interface Option {
|
|
|
+ label: string;
|
|
|
+ value: Criteria;
|
|
|
+}
|
|
|
+
|
|
|
+export default function SearchScreen() {
|
|
|
+ const [keyword, setKeyword] = useState<string>('');
|
|
|
+ const [criteria, setCriteria] = useState<Criteria>('title');
|
|
|
+ const [results, setResults] = useState<Song[]>([]);
|
|
|
+ const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
|
|
+
|
|
|
+ const options: Option[] = [
|
|
|
+ { label: '제목', value: 'title' },
|
|
|
+ { label: '가수', value: 'artist' },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const handleSearch = async (): Promise<void> => {
|
|
|
+ const filtered: Song[] = await fetchSongs(criteria, keyword);
|
|
|
+ setResults(filtered);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleAddToFavorites = async (song: Song): Promise<void> => {
|
|
|
+ const success: boolean = await addToFavoritesStorage(song);
|
|
|
+ if (!success) {
|
|
|
+ console.log('Failed to add to favorites');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSelectOption = (value: Criteria): void => {
|
|
|
+ setCriteria(value);
|
|
|
+ setIsDropdownVisible(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <KeyboardAvoidingView
|
|
|
+ style={styles.container}
|
|
|
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
|
+ >
|
|
|
+ <View style={styles.searchRow}>
|
|
|
+ <View style={styles.dropdownWrapper}>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={styles.dropdownButton}
|
|
|
+ onPress={() => setIsDropdownVisible(!isDropdownVisible)}
|
|
|
+ >
|
|
|
+ <Text style={styles.dropdownText}>
|
|
|
+ {options.find((opt) => opt.value === criteria)?.label || '선택'}
|
|
|
+ </Text>
|
|
|
+ <Text style={styles.dropdownArrow}>▼</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ <Modal
|
|
|
+ transparent
|
|
|
+ visible={isDropdownVisible}
|
|
|
+ animationType="fade"
|
|
|
+ onRequestClose={() => setIsDropdownVisible(false)}
|
|
|
+ >
|
|
|
+ <TouchableOpacity
|
|
|
+ style={styles.modalOverlay}
|
|
|
+ onPress={() => setIsDropdownVisible(false)}
|
|
|
+ >
|
|
|
+ <View style={styles.dropdownMenu}>
|
|
|
+ {options.map((option) => (
|
|
|
+ <TouchableOpacity
|
|
|
+ key={option.value}
|
|
|
+ style={styles.dropdownItem}
|
|
|
+ onPress={() => handleSelectOption(option.value)}
|
|
|
+ >
|
|
|
+ <Text style={styles.dropdownItemText}>{option.label}</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </Modal>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <TextInput
|
|
|
+ placeholder="검색어 입력"
|
|
|
+ value={keyword}
|
|
|
+ onChangeText={(text: string) => setKeyword(text)}
|
|
|
+ style={styles.input}
|
|
|
+ />
|
|
|
+
|
|
|
+ <TouchableOpacity onPress={handleSearch} style={styles.searchButton}>
|
|
|
+ <Text style={styles.searchText}>검색</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <FlatList<Song>
|
|
|
+ data={results}
|
|
|
+ keyExtractor={(item) => item.id}
|
|
|
+ renderItem={({ item }) => (
|
|
|
+ <TouchableOpacity onPress={() => handleAddToFavorites(item)}>
|
|
|
+ <View style={styles.item}>
|
|
|
+ <Text>
|
|
|
+ {item.id} - {item.title} ({item.artist})
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ )}
|
|
|
+ ListEmptyComponent={<Text>검색 결과가 없습니다.</Text>}
|
|
|
+ contentContainerStyle={{ flexGrow: 1 }}
|
|
|
+ />
|
|
|
+ </KeyboardAvoidingView>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+const styles = StyleSheet.create({
|
|
|
+ container: {
|
|
|
+ flex: 1,
|
|
|
+ padding: 16,
|
|
|
+ },
|
|
|
+ searchRow: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ marginBottom: 16,
|
|
|
+ width: '100%',
|
|
|
+ },
|
|
|
+ dropdownWrapper: {
|
|
|
+ width: Dimensions.get('window').width * 0.3 - 24,
|
|
|
+ marginRight: 8,
|
|
|
+ },
|
|
|
+ dropdownButton: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: '#ccc',
|
|
|
+ borderRadius: 8,
|
|
|
+ paddingHorizontal: 10,
|
|
|
+ paddingVertical: 10,
|
|
|
+ height: 40,
|
|
|
+ backgroundColor: '#fff',
|
|
|
+ },
|
|
|
+ dropdownText: {
|
|
|
+ fontSize: 16,
|
|
|
+ color: '#000',
|
|
|
+ },
|
|
|
+ dropdownArrow: {
|
|
|
+ fontSize: 12,
|
|
|
+ color: '#000',
|
|
|
+ },
|
|
|
+ modalOverlay: {
|
|
|
+ flex: 1,
|
|
|
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
|
+ justifyContent: 'flex-start',
|
|
|
+ paddingTop: 120,
|
|
|
+ paddingHorizontal: 16,
|
|
|
+ },
|
|
|
+ dropdownMenu: {
|
|
|
+ backgroundColor: '#fff',
|
|
|
+ borderRadius: 8,
|
|
|
+ width: Dimensions.get('window').width * 0.3 - 24,
|
|
|
+ elevation: 5,
|
|
|
+ shadowColor: '#000',
|
|
|
+ shadowOffset: { width: 0, height: 2 },
|
|
|
+ shadowOpacity: 0.2,
|
|
|
+ shadowRadius: 4,
|
|
|
+ },
|
|
|
+ dropdownItem: {
|
|
|
+ paddingVertical: 10,
|
|
|
+ paddingHorizontal: 10,
|
|
|
+ borderBottomWidth: 1,
|
|
|
+ borderBottomColor: '#eee',
|
|
|
+ },
|
|
|
+ dropdownItemText: {
|
|
|
+ fontSize: 16,
|
|
|
+ color: '#000',
|
|
|
+ },
|
|
|
+ input: {
|
|
|
+ flex: 1,
|
|
|
+ height: 40,
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: '#ccc',
|
|
|
+ borderRadius: 8,
|
|
|
+ paddingHorizontal: 8,
|
|
|
+ marginRight: 8,
|
|
|
+ },
|
|
|
+ searchButton: {
|
|
|
+ backgroundColor: '#007AFF',
|
|
|
+ paddingHorizontal: 8,
|
|
|
+ paddingVertical: 10,
|
|
|
+ borderRadius: 8,
|
|
|
+ },
|
|
|
+ searchText: {
|
|
|
+ color: '#fff',
|
|
|
+ fontWeight: 'bold',
|
|
|
+ },
|
|
|
+ item: {
|
|
|
+ padding: 12,
|
|
|
+ borderBottomWidth: 1,
|
|
|
+ borderBottomColor: '#eee',
|
|
|
+ width: Dimensions.get('window').width - 32,
|
|
|
+ },
|
|
|
+});
|