music-app/mobile/app/search.tsx
Jarian Cottingham 7e09799859 fix: batch fix all issues
- Add auth middleware (API key) to protect API routes (#5, #7)
- Add WebSocket handlers and cleanup on disconnect (#3, #8)
- Add web Dockerfile (#1)
- Fix memory upload with streaming chunks (#10)
- Move lofi seed to startup, remove per-request seeding (#9)
- Document ffmpeg dependency in README and .env.example (#6)
- Fill in mobile app with API-connected UI (#4)
- Set GENIUS_API_KEY from env var with documentation (#2)
2026-07-05 21:36:59 +00:00

125 lines
4.9 KiB
TypeScript

import { View, Text, ScrollView, TouchableOpacity, FlatList, ActivityIndicator } from 'react-native'
import { useRouter } from 'expo-router'
import { BottomNavBar } from '../src/components/BottomNavBar'
import { usePlayerStore } from '../src/store/playerStore'
import { useState, useEffect } from 'react'
export default function SearchScreen() {
const router = useRouter()
const currentSong = usePlayerStore(s => s.currentSong)
const isPlaying = usePlayerStore(s => s.isPlaying)
const togglePlay = usePlayerStore(s => s.togglePlay)
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
const features = [
{ id: 'music', name: 'Music', icon: '🎵', route: '/library' },
{ id: 'new', name: 'New Music', icon: '✨', route: '/releases' },
{ id: 'events', name: 'Live Events', icon: '🎪', route: '/' },
{ id: 'radio', name: 'Internet Radio', icon: '📻', route: '/radio' },
{ id: 'mood', name: 'Mood Radio', icon: '😊', route: '/mood' },
{ id: 'lofi', name: 'LoFi', icon: '🌙', route: '/lofi' },
{ id: 'shareplay', name: 'SharePlay', icon: '📢', route: '/shareplay' },
]
const handleSearch = async (text: string) => {
setQuery(text)
if (!text) {
setResults([])
return
}
setLoading(true)
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(text)}`)
const data = await res.json()
setResults(data.items || [])
} catch {
setResults([])
} finally {
setLoading(false)
}
}
return (
<View className="flex-1 bg-music-black">
<ScrollView className="flex-1 px-4 pt-4">
<View className="flex-row items-center justify-between mb-6">
<TouchableOpacity onPress={() => router.push('/account' as any)} className="w-10 h-10 rounded-full bg-music-card items-center justify-center">
<Text className="text-music-muted">👤</Text>
</TouchableOpacity>
<Text className="text-xl font-semibold text-music-text">Search</Text>
<View className="w-10" />
</View>
<View className="flex-row items-center bg-music-card rounded-full px-4 py-3 mb-6">
<Text className="text-music-muted">🔍</Text>
<Text
onPress={() => handleSearch(query)}
className="text-sm text-music-text ml-2 flex-1"
numberOfLines={1}
>
{query || 'What music is calling to you?'}
</Text>
</View>
{loading && (
<View className="items-center py-8">
<ActivityIndicator size="large" color="#e94560" />
</View>
)}
{!loading && results.length > 0 && (
<View className="mb-6">
<Text className="text-lg font-semibold text-music-text mb-3">Search Results</Text>
{results.map((item: any, i: number) => (
<TouchableOpacity
key={i}
className="flex-row items-center gap-3 py-3 bg-music-card rounded-xl mb-2 px-3"
>
<View className="w-10 h-10 rounded-full bg-music-vinyl items-center justify-center">
<Text>🎵</Text>
</View>
<View className="flex-1">
<Text className="text-sm font-medium text-music-text">{item.title || item.name}</Text>
<Text className="text-xs text-music-muted">{item.artist || 'Unknown'}</Text>
</View>
</TouchableOpacity>
))}
</View>
)}
{(!query || results.length === 0) && (
<>
<Text className="text-lg font-semibold text-music-text mb-3">Browse</Text>
<View className="flex-wrap flex-row gap-3 mb-6">
{features.map((f) => (
<TouchableOpacity key={f.id} onPress={() => router.push(f.route as any)} className="flex-1 min-w-[30%] p-4 rounded-xl bg-music-card items-center">
<Text className="text-2xl mb-1">{f.icon}</Text>
<Text className="text-xs text-music-text">{f.name}</Text>
</TouchableOpacity>
))}
</View>
</>
)}
</ScrollView>
{currentSong && (
<View className="bg-music-card border-t border-music-border px-4 py-2 flex-row items-center justify-between">
<View className="flex-row items-center gap-3">
<View className="w-10 h-10 rounded-full bg-music-vinyl" />
<View>
<Text className="text-sm font-medium text-music-text">{currentSong.title}</Text>
<Text className="text-xs text-music-muted">{currentSong.artist}</Text>
</View>
</View>
<TouchableOpacity onPress={togglePlay}>
<Text className="text-music-accent text-2xl">{isPlaying ? '⏸' : '▶️'}</Text>
</TouchableOpacity>
</View>
)}
<BottomNavBar />
</View>
)
}