pretty + better handling + handling in artist detail view
This commit is contained in:
46
front/API.ts
46
front/API.ts
@@ -665,21 +665,17 @@ export default class API {
|
||||
}
|
||||
|
||||
public static async addLikedSong(songId: number): Promise<void> {
|
||||
await API.fetch(
|
||||
{
|
||||
route: `/auth/me/likes/${songId}`,
|
||||
method: 'POST',
|
||||
}
|
||||
)
|
||||
await API.fetch({
|
||||
route: `/auth/me/likes/${songId}`,
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
public static async removeLikedSong(songId: number): Promise<void> {
|
||||
await API.fetch(
|
||||
{
|
||||
route: `/auth/me/likes/${songId}`,
|
||||
method: 'DELETE',
|
||||
}
|
||||
)
|
||||
await API.fetch({
|
||||
route: `/auth/me/likes/${songId}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
public static getLikedSongs(): Query<likedSong[]> {
|
||||
@@ -690,21 +686,21 @@ export default class API {
|
||||
{
|
||||
route: '/auth/me/likes',
|
||||
},
|
||||
{ handler: ListHandler(LikedSongHandler)}
|
||||
{ handler: ListHandler(LikedSongHandler) }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
public static getUserPlaayHistory(): Query<SongHistoryItem[]> {
|
||||
return {
|
||||
key: ['history'],
|
||||
exec: () =>
|
||||
API.fetch(
|
||||
{
|
||||
route: '/history',
|
||||
},
|
||||
{ handler: ListHandler(SongHistoryItemHandler) }
|
||||
),
|
||||
};
|
||||
}
|
||||
public static getUserPlaayHistory(): Query<SongHistoryItem[]> {
|
||||
return {
|
||||
key: ['history'],
|
||||
exec: () =>
|
||||
API.fetch(
|
||||
{
|
||||
route: '/history',
|
||||
},
|
||||
{ handler: ListHandler(SongHistoryItemHandler) }
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +55,18 @@ const FavSongRow = ({ FavSong, onPress }: FavSongRowProps) => {
|
||||
{FavSong.addedDate.toLocaleDateString()}
|
||||
</Text>
|
||||
</HStack>
|
||||
<IconButton colorScheme="primary" variant={'ghost'} borderRadius={'full'} onPress={() => {API.removeLikedSong(FavSong.songId)}}
|
||||
<IconButton
|
||||
colorScheme="primary"
|
||||
variant={'ghost'}
|
||||
borderRadius={'full'}
|
||||
onPress={() => {
|
||||
API.removeLikedSong(FavSong.songId);
|
||||
}}
|
||||
_icon={{
|
||||
as: MaterialIcons,
|
||||
name: "favorite"
|
||||
}} />
|
||||
as: MaterialIcons,
|
||||
name: 'favorite',
|
||||
}}
|
||||
/>
|
||||
<TextButton
|
||||
flexShrink={0}
|
||||
flexGrow={0}
|
||||
@@ -75,4 +82,4 @@ const FavSongRow = ({ FavSong, onPress }: FavSongRowProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default FavSongRow;
|
||||
export default FavSongRow;
|
||||
|
||||
@@ -25,7 +25,6 @@ import Song, { SongWithArtist } from '../models/Song';
|
||||
import { useNavigation } from '../Navigation';
|
||||
import Artist from '../models/Artist';
|
||||
import SongRow from '../components/SongRow';
|
||||
import RowCustom from './RowCustom';
|
||||
import FavSongRow from './FavSongRow';
|
||||
import { LikedSongWithDetails } from '../models/LikedSong';
|
||||
|
||||
@@ -116,10 +115,11 @@ const SongsSearchComponent = (props: SongsSearchComponentProps) => {
|
||||
const navigation = useNavigation();
|
||||
const { songData } = React.useContext(SearchContext);
|
||||
const favoritesQuery = useQuery(API.getLikedSongs());
|
||||
// const songQueryWithFavorite = songData?.map((songs) => ({
|
||||
// ...songs,
|
||||
// isLiked: !favoritesQuery.data?.find((query) => query?.songId == songs.id)
|
||||
// }))
|
||||
|
||||
const handleFavoriteButton = async (state: boolean, songId: number): Promise<void> => {
|
||||
if (state == false) await API.removeLikedSong(songId);
|
||||
else await API.addLikedSong(songId);
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
@@ -132,7 +132,12 @@ const SongsSearchComponent = (props: SongsSearchComponentProps) => {
|
||||
<SongRow
|
||||
key={index}
|
||||
song={comp}
|
||||
isLiked={!favoritesQuery.data?.find((query) => query?.songId == comp.id)}
|
||||
isLiked={
|
||||
!favoritesQuery.data?.find((query) => query?.songId == comp.id)
|
||||
}
|
||||
handleLike={(state: boolean, songId: number) =>
|
||||
handleFavoriteButton(state, songId)
|
||||
}
|
||||
onPress={() => {
|
||||
API.createSearchHistoryEntry(comp.name, 'song');
|
||||
navigation.navigate('Song', { songId: comp.id });
|
||||
@@ -211,23 +216,22 @@ const GenreSearchComponent = (props: ItemSearchComponentProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
type FavoriteComponentProps = {
|
||||
maxRows?: number;
|
||||
};
|
||||
|
||||
const FavoritesComponent = (props: FavoriteComponentProps) => {
|
||||
const FavoritesComponent = () => {
|
||||
const navigation = useNavigation();
|
||||
const favoritesQuery = useQuery(API.getLikedSongs());
|
||||
const songQueries = useQueries(
|
||||
favoritesQuery.data?.map((favorite) => favorite.songId).map((songId) => API.getSong(songId)) ??
|
||||
[]
|
||||
favoritesQuery.data
|
||||
?.map((favorite) => favorite.songId)
|
||||
.map((songId) => API.getSong(songId)) ?? []
|
||||
);
|
||||
|
||||
const favSongWithDetails = favoritesQuery?.data
|
||||
?.map((favorite) => ({
|
||||
...favorite,
|
||||
details: songQueries.find((query) => query.data?.id == favorite.songId)?.data,
|
||||
})).filter((favorite) => favorite.details !== undefined).map((likedSong) => likedSong as LikedSongWithDetails);
|
||||
}))
|
||||
.filter((favorite) => favorite.details !== undefined)
|
||||
.map((likedSong) => likedSong as LikedSongWithDetails);
|
||||
|
||||
if (favoritesQuery.isError) {
|
||||
navigation.navigate('Error');
|
||||
@@ -243,19 +247,20 @@ const FavoritesComponent = (props: FavoriteComponentProps) => {
|
||||
{translate('songsFilter')}
|
||||
</Text>
|
||||
<Box>
|
||||
{favSongWithDetails?.map((songData) => (
|
||||
<FavSongRow
|
||||
FavSong={songData}
|
||||
onPress={() => {
|
||||
API.createSearchHistoryEntry(songData.details!.name, 'song'); //todo
|
||||
navigation.navigate('Song', { songId: songData.details!.id }); //todo
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
{favSongWithDetails?.map((songData) => (
|
||||
<FavSongRow
|
||||
key={songData.id}
|
||||
FavSong={songData}
|
||||
onPress={() => {
|
||||
API.createSearchHistoryEntry(songData.details!.name, 'song'); //todo
|
||||
navigation.navigate('Song', { songId: songData.details!.id }); //todo
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const AllComponent = () => {
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||
@@ -311,7 +316,7 @@ const FilterSwitch = () => {
|
||||
|
||||
export const SearchResultComponent = () => {
|
||||
const { stringQuery } = React.useContext(SearchContext);
|
||||
const {filter} = React.useContext(SearchContext)
|
||||
const { filter } = React.useContext(SearchContext);
|
||||
const shouldOutput = !!stringQuery.trim() || filter == 'favorites';
|
||||
|
||||
return shouldOutput ? (
|
||||
|
||||
@@ -3,21 +3,15 @@ import Song, { SongWithArtist } from '../models/Song';
|
||||
import RowCustom from './RowCustom';
|
||||
import TextButton from './TextButton';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import API from '../API';
|
||||
|
||||
type SongRowProps = {
|
||||
song: Song | SongWithArtist; // TODO: remove Song
|
||||
isLiked: boolean;
|
||||
onPress: () => void;
|
||||
handleLike: () => void;
|
||||
handleLike: (state: boolean, songId: number) => Promise<void>;
|
||||
};
|
||||
|
||||
const handleFavoriteButton = (state: boolean, songId: number): void => {
|
||||
if (state == false) API.removeLikedSong(songId);
|
||||
else API.addLikedSong(songId);
|
||||
}
|
||||
|
||||
const SongRow = ({ song, onPress, isLiked }: SongRowProps) => {
|
||||
const SongRow = ({ song, onPress, handleLike, isLiked }: SongRowProps) => {
|
||||
return (
|
||||
<RowCustom width={'100%'}>
|
||||
<HStack px={2} space={5} justifyContent={'space-between'}>
|
||||
@@ -62,11 +56,18 @@ const SongRow = ({ song, onPress, isLiked }: SongRowProps) => {
|
||||
{song.artistId ?? 'artist'}
|
||||
</Text>
|
||||
</HStack>
|
||||
<IconButton colorScheme="rose" variant={'ghost'} borderRadius={'full'} onPress={() => { handleFavoriteButton(isLiked, song.id)}}
|
||||
<IconButton
|
||||
colorScheme="rose"
|
||||
variant={'ghost'}
|
||||
borderRadius={'full'}
|
||||
onPress={async () => {
|
||||
await handleLike(isLiked, song.id);
|
||||
}}
|
||||
_icon={{
|
||||
as: MaterialIcons,
|
||||
name: isLiked ? "favorite-outline" : "favorite"
|
||||
}} />
|
||||
as: MaterialIcons,
|
||||
name: isLiked ? 'favorite-outline' : 'favorite',
|
||||
}}
|
||||
/>
|
||||
<TextButton
|
||||
flexShrink={0}
|
||||
flexGrow={0}
|
||||
|
||||
@@ -7,9 +7,13 @@ export const LikedSongValidator = yup
|
||||
.object({
|
||||
songId: yup.number().required(),
|
||||
addedDate: yup.date().required(),
|
||||
}).concat(ModelValidator);
|
||||
})
|
||||
.concat(ModelValidator);
|
||||
|
||||
export const LikedSongHandler: ResponseHandler<yup.InferType<typeof LikedSongValidator>, LikedSong> = {
|
||||
export const LikedSongHandler: ResponseHandler<
|
||||
yup.InferType<typeof LikedSongValidator>,
|
||||
LikedSong
|
||||
> = {
|
||||
validator: LikedSongValidator,
|
||||
transformer: (likedSong) => ({
|
||||
id: likedSong.id,
|
||||
@@ -20,10 +24,10 @@ export const LikedSongHandler: ResponseHandler<yup.InferType<typeof LikedSongVal
|
||||
interface LikedSong extends Model {
|
||||
songId: number;
|
||||
addedDate: Date;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LikedSongWithDetails extends LikedSong {
|
||||
details: Song;
|
||||
}
|
||||
|
||||
export default LikedSong;
|
||||
export default LikedSong;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Heading, useBreakpointValue, ScrollView, useColorModeValue } from 'native-base';
|
||||
import { Box, Heading, useBreakpointValue, ScrollView } from 'native-base';
|
||||
import { useQuery } from '../Queries';
|
||||
import { LoadingView } from '../components/Loading';
|
||||
import API from '../API';
|
||||
@@ -16,10 +16,16 @@ const ArtistDetailsView = ({ artistId }: RouteProps<ArtistDetailsViewProps>) =>
|
||||
const artistQuery = useQuery(API.getArtist(artistId));
|
||||
const songsQuery = useQuery(API.getSongsByArtist(artistId));
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||
const fadeColor = useColorModeValue('#ffffff', '#000000');
|
||||
const isMobileView = screenSize == 'small';
|
||||
const navigation = useNavigation();
|
||||
|
||||
const favoritesQuery = useQuery(API.getLikedSongs());
|
||||
|
||||
const handleFavoriteButton = async (state: boolean, songId: number): Promise<void> => {
|
||||
if (state == false) await API.removeLikedSong(songId);
|
||||
else await API.addLikedSong(songId);
|
||||
};
|
||||
|
||||
if (artistQuery.isError || songsQuery.isError) {
|
||||
navigation.navigate('Error');
|
||||
return <></>;
|
||||
@@ -33,8 +39,7 @@ const ArtistDetailsView = ({ artistId }: RouteProps<ArtistDetailsViewProps>) =>
|
||||
<ImageBackground
|
||||
style={{ width: '100%', height: isMobileView ? 200 : 300 }}
|
||||
source={{ uri: API.getArtistIllustration(artistQuery.data.id) }}
|
||||
>
|
||||
</ImageBackground>
|
||||
></ImageBackground>
|
||||
<Box>
|
||||
<Heading mt={-20} ml={3} fontSize={50}>
|
||||
{artistQuery.data.name}
|
||||
@@ -45,6 +50,12 @@ const ArtistDetailsView = ({ artistId }: RouteProps<ArtistDetailsViewProps>) =>
|
||||
<SongRow
|
||||
key={index}
|
||||
song={comp}
|
||||
isLiked={
|
||||
!favoritesQuery.data?.find((query) => query?.songId == comp.id)
|
||||
}
|
||||
handleLike={(state: boolean, songId: number) =>
|
||||
handleFavoriteButton(state, songId)
|
||||
}
|
||||
onPress={() => {
|
||||
API.createSearchHistoryEntry(comp.name, 'song');
|
||||
navigation.navigate('Song', { songId: comp.id });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, Heading, useBreakpointValue, ScrollView, useColorModeValue } from 'native-base';
|
||||
import { Flex, Heading, useBreakpointValue, ScrollView } from 'native-base';
|
||||
import { useQueries, useQuery } from '../Queries';
|
||||
import { LoadingView } from '../components/Loading';
|
||||
import { RouteProps, useNavigation } from '../Navigation';
|
||||
@@ -28,7 +28,6 @@ const GenreDetailsView = ({ genreId }: RouteProps<GenreDetailsViewProps>) => {
|
||||
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||
const isMobileView = screenSize == 'small';
|
||||
const fadeColor = useColorModeValue('#ffffff', '#000000');
|
||||
const navigation = useNavigation();
|
||||
|
||||
if (genreQuery.isError || songsQuery.isError) {
|
||||
@@ -44,8 +43,7 @@ const GenreDetailsView = ({ genreId }: RouteProps<GenreDetailsViewProps>) => {
|
||||
<ImageBackground
|
||||
style={{ width: '100%', height: isMobileView ? 200 : 300 }}
|
||||
source={{ uri: API.getGenreIllustration(genreQuery.data.id) }}
|
||||
>
|
||||
</ImageBackground>
|
||||
></ImageBackground>
|
||||
<Heading ml={3} fontSize={50}>
|
||||
{genreQuery.data.name}
|
||||
</Heading>
|
||||
|
||||
@@ -24,6 +24,7 @@ interface SearchContextType {
|
||||
isLoadingSong: boolean;
|
||||
isLoadingArtist: boolean;
|
||||
isLoadingGenre: boolean;
|
||||
isLoadingFavorite: boolean;
|
||||
}
|
||||
|
||||
export const SearchContext = React.createContext<SearchContextType>({
|
||||
@@ -38,6 +39,7 @@ export const SearchContext = React.createContext<SearchContextType>({
|
||||
isLoadingSong: false,
|
||||
isLoadingArtist: false,
|
||||
isLoadingGenre: false,
|
||||
isLoadingFavorite: false,
|
||||
});
|
||||
|
||||
type SearchViewProps = {
|
||||
@@ -66,7 +68,7 @@ const SearchView = (props: RouteProps<SearchViewProps>) => {
|
||||
const { isLoading: isLoadingFavorite, data: favoriteData = [] } = useQuery(
|
||||
API.getLikedSongs(),
|
||||
{ enabled: true }
|
||||
)
|
||||
);
|
||||
|
||||
const updateFilter = (newData: Filter) => {
|
||||
// called when the filter is changed
|
||||
@@ -92,6 +94,7 @@ const SearchView = (props: RouteProps<SearchViewProps>) => {
|
||||
isLoadingSong,
|
||||
isLoadingArtist,
|
||||
isLoadingGenre,
|
||||
isLoadingFavorite,
|
||||
updateFilter,
|
||||
updateStringQuery,
|
||||
}}
|
||||
@@ -105,6 +108,3 @@ const SearchView = (props: RouteProps<SearchViewProps>) => {
|
||||
};
|
||||
|
||||
export default SearchView;
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user