pretty + better handling + handling in artist detail view

This commit is contained in:
danis
2023-09-18 16:45:03 +02:00
parent 431427d7ad
commit bc227fb0ea
8 changed files with 106 additions and 84 deletions

View File

@@ -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) }
),
};
}
}

View File

@@ -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;

View File

@@ -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 ? (

View File

@@ -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}

View File

@@ -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;

View File

@@ -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 });

View File

@@ -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>

View File

@@ -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;