278 lines
7.1 KiB
TypeScript
278 lines
7.1 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
VStack,
|
|
Heading,
|
|
Box,
|
|
Card,
|
|
Flex,
|
|
useBreakpointValue,
|
|
Column,
|
|
ScrollView,
|
|
} from 'native-base';
|
|
import { SafeAreaView } from 'react-native';
|
|
import { SearchContext } from '../views/SearchView';
|
|
import { useQuery } from '../Queries';
|
|
import { Translate, translate } from '../i18n/i18n';
|
|
import API from '../API';
|
|
import LoadingComponent, { LoadingView } from './Loading';
|
|
import ArtistCard from './ArtistCard';
|
|
import GenreCard from './GenreCard';
|
|
import SongCard from './SongCard';
|
|
import CardGridCustom from './CardGridCustom';
|
|
import SearchHistoryCard from './HistoryCard';
|
|
import Song from '../models/Song';
|
|
import { useNavigation } from '../Navigation';
|
|
import SongRow from '../components/SongRow';
|
|
import FavSongRow from './FavSongRow';
|
|
import { useLikeSongMutation } from '../utils/likeSongMutation';
|
|
|
|
const swaToSongCardProps = (song: Song) => ({
|
|
songId: song.id,
|
|
name: song.name,
|
|
artistName: song.artist!.name,
|
|
cover: song.cover,
|
|
});
|
|
|
|
const HomeSearchComponent = () => {
|
|
const { updateStringQuery } = React.useContext(SearchContext);
|
|
const { isLoading: isLoadingHistory, data: historyData = [] } = useQuery(
|
|
API.getSearchHistory(0, 12),
|
|
{ enabled: true }
|
|
);
|
|
const songSuggestions = useQuery(API.getSongSuggestions(['artist']));
|
|
|
|
return (
|
|
<VStack mt="5" style={{ overflow: 'hidden' }}>
|
|
<Card shadow={3} mb={5}>
|
|
<Heading margin={5}>{translate('lastSearched')}</Heading>
|
|
{isLoadingHistory ? (
|
|
<LoadingComponent />
|
|
) : (
|
|
<CardGridCustom
|
|
content={historyData.map((h) => {
|
|
return {
|
|
...h,
|
|
timestamp: h.timestamp.toLocaleString(),
|
|
onPress: () => {
|
|
updateStringQuery(h.query);
|
|
},
|
|
};
|
|
})}
|
|
cardComponent={SearchHistoryCard}
|
|
/>
|
|
)}
|
|
</Card>
|
|
<Card shadow={3} mt={5} mb={5}>
|
|
<Heading margin={5}>{translate('songsToGetBetter')}</Heading>
|
|
{!songSuggestions.data ? (
|
|
<LoadingComponent />
|
|
) : (
|
|
<CardGridCustom
|
|
content={songSuggestions.data.map(swaToSongCardProps)}
|
|
cardComponent={SongCard}
|
|
/>
|
|
)}
|
|
</Card>
|
|
</VStack>
|
|
);
|
|
};
|
|
|
|
type SongsSearchComponentProps = {
|
|
maxRows?: number;
|
|
};
|
|
|
|
const SongsSearchComponent = (props: SongsSearchComponentProps) => {
|
|
const navigation = useNavigation();
|
|
const { songData } = React.useContext(SearchContext);
|
|
const favoritesQuery = useQuery(API.getLikedSongs(['artist']));
|
|
const { mutate } = useLikeSongMutation();
|
|
|
|
return (
|
|
<ScrollView>
|
|
<Translate translationKey="songsFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
|
<Box>
|
|
{songData?.length ? (
|
|
songData.slice(0, props.maxRows).map((comp, index) => (
|
|
<SongRow
|
|
key={index}
|
|
song={comp}
|
|
isLiked={
|
|
!favoritesQuery.data?.find((query) => query?.songId == comp.id)
|
|
}
|
|
handleLike={async (state: boolean, songId: number) =>
|
|
mutate({ songId: songId, like: state })
|
|
}
|
|
onPress={() => {
|
|
API.createSearchHistoryEntry(comp.name, 'song');
|
|
navigation.navigate('Play', { songId: comp.id });
|
|
}}
|
|
/>
|
|
))
|
|
) : (
|
|
<Translate translationKey="errNoResults" />
|
|
)}
|
|
</Box>
|
|
</ScrollView>
|
|
);
|
|
};
|
|
|
|
type ItemSearchComponentProps = {
|
|
maxItems?: number;
|
|
};
|
|
|
|
const ArtistSearchComponent = (props: ItemSearchComponentProps) => {
|
|
const { artistData } = React.useContext(SearchContext);
|
|
const navigation = useNavigation();
|
|
|
|
return (
|
|
<Box>
|
|
<Translate translationKey="artistFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
|
{artistData?.length ? (
|
|
<CardGridCustom
|
|
content={artistData
|
|
.slice(0, props.maxItems ?? artistData.length)
|
|
.map((artistData) => ({
|
|
image: API.getArtistIllustration(artistData.id),
|
|
name: artistData.name,
|
|
id: artistData.id,
|
|
onPress: () => {
|
|
API.createSearchHistoryEntry(artistData.name, 'artist');
|
|
navigation.navigate('Artist', { artistId: artistData.id });
|
|
},
|
|
}))}
|
|
cardComponent={ArtistCard}
|
|
/>
|
|
) : (
|
|
<Translate translationKey="errNoResults" />
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const GenreSearchComponent = (props: ItemSearchComponentProps) => {
|
|
const { genreData } = React.useContext(SearchContext);
|
|
const navigation = useNavigation();
|
|
|
|
return (
|
|
<Box>
|
|
<Translate translationKey="genreFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
|
{genreData?.length ? (
|
|
<CardGridCustom
|
|
content={genreData.slice(0, props.maxItems ?? genreData.length).map((g) => ({
|
|
image: API.getGenreIllustration(g.id),
|
|
name: g.name,
|
|
id: g.id,
|
|
onPress: () => {
|
|
API.createSearchHistoryEntry(g.name, 'genre');
|
|
navigation.navigate('Genre', { genreId: g.id });
|
|
},
|
|
}))}
|
|
cardComponent={GenreCard}
|
|
/>
|
|
) : (
|
|
<Translate translationKey="errNoResults" />
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const FavoritesComponent = () => {
|
|
const navigation = useNavigation();
|
|
const favoritesQuery = useQuery(API.getLikedSongs());
|
|
|
|
if (favoritesQuery.isError) {
|
|
navigation.navigate('Error');
|
|
return <></>;
|
|
}
|
|
if (!favoritesQuery.data) {
|
|
return <LoadingView />;
|
|
}
|
|
|
|
return (
|
|
<ScrollView>
|
|
<Translate translationKey="songsFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
|
<Box>
|
|
{favoritesQuery.data?.map((songData) => (
|
|
<FavSongRow
|
|
key={songData.id}
|
|
song={songData.song}
|
|
addedDate={songData.addedDate}
|
|
onPress={() => {
|
|
API.createSearchHistoryEntry(songData.song.name, 'song'); //todo
|
|
navigation.navigate('Play', { songId: songData.song!.id });
|
|
}}
|
|
/>
|
|
))}
|
|
</Box>
|
|
</ScrollView>
|
|
);
|
|
};
|
|
|
|
const AllComponent = () => {
|
|
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
|
const isMobileView = screenSize == 'small';
|
|
|
|
return (
|
|
<SafeAreaView>
|
|
<Flex
|
|
flexWrap="wrap"
|
|
direction={isMobileView ? 'column' : 'row'}
|
|
justifyContent={['flex-start']}
|
|
mt={4}
|
|
>
|
|
<Column w={isMobileView ? '100%' : '50%'}>
|
|
<Box minH={isMobileView ? 100 : 200}>
|
|
<ArtistSearchComponent maxItems={6} />
|
|
</Box>
|
|
<Box minH={isMobileView ? 100 : 200}>
|
|
<GenreSearchComponent maxItems={6} />
|
|
</Box>
|
|
</Column>
|
|
<Box w={isMobileView ? '100%' : '50%'}>
|
|
<SongsSearchComponent maxRows={9} />
|
|
</Box>
|
|
</Flex>
|
|
</SafeAreaView>
|
|
);
|
|
};
|
|
|
|
const FilterSwitch = () => {
|
|
const { filter } = React.useContext(SearchContext);
|
|
const [currentFilter, setCurrentFilter] = React.useState(filter);
|
|
|
|
React.useEffect(() => {
|
|
setCurrentFilter(filter);
|
|
}, [filter]);
|
|
|
|
switch (currentFilter) {
|
|
case 'all':
|
|
return <AllComponent />;
|
|
case 'song':
|
|
return <SongsSearchComponent />;
|
|
case 'artist':
|
|
return <ArtistSearchComponent />;
|
|
case 'genre':
|
|
return <GenreSearchComponent />;
|
|
case 'favorites':
|
|
return <FavoritesComponent />;
|
|
default:
|
|
return (
|
|
<Translate translationKey="unknownError" format={(e) => `${e}: ${currentFilter}`} />
|
|
);
|
|
}
|
|
};
|
|
|
|
export const SearchResultComponent = () => {
|
|
const { stringQuery } = React.useContext(SearchContext);
|
|
const { filter } = React.useContext(SearchContext);
|
|
const shouldOutput = !!stringQuery.trim() || filter == 'favorites';
|
|
|
|
return shouldOutput ? (
|
|
<Box p={5}>
|
|
<FilterSwitch />
|
|
</Box>
|
|
) : (
|
|
<HomeSearchComponent />
|
|
);
|
|
};
|