Merge branch 'main' into feat/adc/search-view-v2

This commit is contained in:
danis
2024-01-04 21:02:40 +01:00
68 changed files with 1242 additions and 13141 deletions
+18 -38
View File
@@ -3,62 +3,42 @@ import { View } from 'react-native';
import { useBreakpointValue } from 'native-base';
import HomeMainSongCard from './HomeMainSongCard';
import GoldenRatioPanel from './GoldenRatioPanel';
import Song from '../../models/Song';
import { useNavigation } from '../../Navigation';
type HomeCardProps = {
image: string;
title: string;
artist: string;
fontSize: number;
onPress?: () => void;
type GoldenRatioProps = {
songs: Song[];
};
const cards = [
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688155292180560/image_homeview1.png',
title: 'Beethoven',
artist: 'Synphony No. 9',
fontSize: 46,
},
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688154923090093/image_homeview2.png',
title: 'Mozart',
artist: 'Lieder Kantate KV 619',
fontSize: 36,
},
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688154499457096/image_homeview3.png',
title: 'Back',
artist: 'Truc Truc',
fontSize: 26,
},
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688154109394985/image_homeview4.png',
title: 'Mozart',
artist: 'Machin Machin',
fontSize: 22,
},
] as [HomeCardProps, HomeCardProps, HomeCardProps, HomeCardProps];
const GoldenRatio = () => {
const GoldenRatio = (props: GoldenRatioProps) => {
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
const isPhone = screenSize === 'small';
const navigation = useNavigation();
const fontSizes = [46, 36, 26, 22];
const cards = props.songs.map((s, i) => ({
image: s.cover,
title: s.name,
artist: s.artist?.name ?? '',
fontSize: fontSizes.at(i) ?? fontSizes.at(-1)!,
onPress: () => navigation.navigate('Play', { songId: s.id }),
}));
return (
<GoldenRatioPanel
direction={isPhone ? 'column' : 'row'}
header={<HomeMainSongCard {...cards[0]} />}
header={<HomeMainSongCard {...cards[0]!} />}
>
<GoldenRatioPanel
direction={isPhone ? 'row' : 'column'}
header={<HomeMainSongCard {...cards[1]} />}
header={<HomeMainSongCard {...cards[1]!} />}
>
<GoldenRatioPanel
direction={isPhone ? 'column-reverse' : 'row-reverse'}
header={<HomeMainSongCard {...cards[2]} />}
header={<HomeMainSongCard {...cards[2]!} />}
>
<GoldenRatioPanel
direction={isPhone ? 'row-reverse' : 'column-reverse'}
header={<HomeMainSongCard {...cards[3]} />}
header={<HomeMainSongCard numLinesHeader={1} {...cards[3]!} />}
>
<View style={{ display: 'flex', width: '100%', height: '100%' }}></View>
</GoldenRatioPanel>
+3 -1
View File
@@ -6,6 +6,7 @@ type HomeMainSongCardProps = {
title: string;
artist: string;
fontSize: number;
numLinesHeader: number;
onPress: () => void;
};
@@ -66,7 +67,7 @@ const HomeMainSongCard = (props: HomeMainSongCardProps) => {
fontSize: props.fontSize,
fontWeight: 'bold',
}}
numberOfLines={2}
numberOfLines={props.numLinesHeader}
selectable={false}
>
{props.title}
@@ -94,6 +95,7 @@ const HomeMainSongCard = (props: HomeMainSongCardProps) => {
HomeMainSongCard.defaultProps = {
onPress: () => {},
fontSize: 16,
numLinesHeader: 2,
};
export default HomeMainSongCard;
+10 -11
View File
@@ -150,17 +150,16 @@ const SearchBarComponent = (props: { onValidate: (searchData: searchProps) => vo
gap: 10,
}}
>
{!artist
? artistsQuery.data?.map((artist, index) => (
<ArtistChipComponent
key={index}
name={artist.name}
onPress={() => {
setArtist(artist.name);
}}
/>// eslint-disable-next-line no-mixed-spaces-and-tabs
))
: null}
{!artist &&
artistsQuery.data?.map((artist, index) => (
<ArtistChipComponent
key={index}
name={artist.name}
onPress={() => {
setArtist(artist.name);
}}
/>
))}
</View>
</ScrollView>
<View>
+186 -197
View File
@@ -1,8 +1,13 @@
import Song from '../../models/Song';
import React from 'react';
import { Image, View } from 'react-native';
import { LikeButton } from './SongCardInfoLikeBtn';
import { Image, Platform, View } from 'react-native';
import { Pressable, Text, PresenceTransition, Icon, useBreakpointValue } from 'native-base';
import { Ionicons } from '@expo/vector-icons';
import { useQuery } from '../../Queries';
import API from '../../API';
import { useLikeSongMutation } from '../../utils/likeSongMutation';
import Hoverable from '../Hoverable';
type SongCardInfoProps = {
song: Song;
@@ -10,35 +15,45 @@ type SongCardInfoProps = {
onPlay: () => void;
};
const Scores = [
{
icon: 'warning',
score: 3,
},
{
icon: 'star',
score: -225,
},
{
icon: 'trophy',
score: 100,
},
];
const SongCardInfo = (props: SongCardInfoProps) => {
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
const isPhone = screenSize === 'small';
const [isPlayHovered, setIsPlayHovered] = React.useState(false);
const [isHovered, setIsHovered] = React.useState(false);
const [isSlided, setIsSlided] = React.useState(false);
const user = useQuery(API.getUserInfo);
const [isLiked, setIsLiked] = React.useState(false);
const { mutate } = useLikeSongMutation();
const CardDims = {
height: isPhone ? 160 : 200,
width: isPhone ? 160 : 200,
};
const Scores = [
{
icon: 'time',
score: props.song.lastScore ?? '-',
},
{
icon: 'trophy',
score: props.song.bestScore ?? '-',
},
];
React.useEffect(() => {
if (!user.data) {
return;
}
setIsLiked(
props.song.likedByUsers?.some(({ userId }) => userId === user.data?.id) ?? false
);
}, [user.data, props.song.likedByUsers]);
return (
<View
<Pressable
delayHoverIn={7}
onPress={Platform.OS === 'android' ? props.onPress : undefined}
style={{
width: CardDims.width,
height: CardDims.height,
@@ -48,227 +63,201 @@ const SongCardInfo = (props: SongCardInfoProps) => {
backgroundColor: 'rgba(16, 16, 20, 0.70)',
borderRadius: 12,
overflow: 'hidden',
position: 'relative',
}}
onHoverIn={() => {
setIsHovered(true);
}}
onHoverOut={() => {
setIsHovered(false);
setIsSlided(false);
}}
>
<Pressable
delayHoverIn={7}
isHovered={isPlayHovered ? true : undefined}
onPress={props.onPress}
<View
style={{
width: '100%',
}}
onHoverIn={() => {
setIsHovered(true);
}}
onHoverOut={() => {
setIsHovered(false);
setIsSlided(false);
width: CardDims.width,
height: CardDims.height,
backgroundColor: 'rgba(16, 16, 20, 0.7)',
borderRadius: 12,
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
<>
<View
<View
style={{
width: '100%',
marginBottom: 8,
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{Scores.map((score, idx) => (
<View
key={score.icon + idx}
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 5,
paddingHorizontal: 10,
}}
>
<Icon as={Ionicons} name={score.icon} size={17} color="white" />
<Text
style={{
color: 'white',
fontSize: 12,
fontWeight: 'bold',
}}
>
{score.score}
</Text>
</View>
))}
</View>
</View>
<PresenceTransition
style={{
width: '100%',
height: '100%',
position: 'absolute',
}}
visible={isHovered}
animate={{
translateY: -55,
}}
onTransitionComplete={() => {
if (isHovered) {
setIsSlided(true);
}
}}
>
<View
style={{
width: CardDims.width,
height: CardDims.height,
position: 'relative',
}}
>
<Image
source={{ uri: props.song.cover }}
style={{
width: CardDims.width,
height: CardDims.height,
backgroundColor: 'rgba(16, 16, 20, 0.7)',
borderRadius: 12,
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
<View
style={{
width: '100%',
marginBottom: 8,
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{Scores.map((score, idx) => (
<View
key={score.icon + idx}
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 5,
paddingHorizontal: 10,
}}
>
<Icon as={Ionicons} name={score.icon} size={17} color="white" />
<Text
style={{
color: 'white',
fontSize: 12,
fontWeight: 'bold',
}}
>
{score.score}
</Text>
</View>
))}
</View>
</View>
<PresenceTransition
/>
<View
style={{
position: 'absolute',
width: '100%',
height: '100%',
position: 'absolute',
}}
visible={isHovered}
initial={{
translateY: 0,
}}
animate={{
translateY: -55,
}}
onTransitionComplete={() => {
if (isHovered) {
setIsSlided(true);
}
backgroundColor: 'rgba(0, 0, 0, 0.75)',
justifyContent: 'flex-end',
alignItems: 'flex-start',
paddingHorizontal: 10,
paddingVertical: 7,
borderRadius: 12,
}}
>
<Image
source={{ uri: props.song.cover }}
style={{
position: 'relative',
width: CardDims.width,
height: CardDims.height,
borderRadius: 12,
}}
/>
<View
style={{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
justifyContent: 'flex-end',
alignItems: 'flex-start',
paddingHorizontal: 10,
paddingVertical: 7,
borderRadius: 12,
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-between',
}}
>
<View
style={{
width: '100%',
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-between',
flexShrink: 1,
}}
>
<View
<Text
numberOfLines={2}
style={{
flexShrink: 1,
color: 'white',
fontSize: 14,
fontWeight: 'bold',
marginBottom: 4,
}}
>
<Text
numberOfLines={2}
style={{
color: 'white',
fontSize: 14,
fontWeight: 'bold',
marginBottom: 4,
}}
>
{props.song.name}
</Text>
<Text
numberOfLines={1}
style={{
color: 'white',
fontSize: 12,
fontWeight: 'normal',
}}
>
{props.song.artist?.name}
</Text>
</View>
<Ionicons
{props.song.name}
</Text>
<Text
numberOfLines={1}
style={{
flexShrink: 0,
color: 'white',
fontSize: 12,
fontWeight: 'normal',
}}
name="bookmark-outline"
size={17}
color="#6075F9"
/>
>
{props.song.artist?.name}
</Text>
</View>
<LikeButton
color="#6075F9"
onPress={() => {
console.log('like');
mutate({ songId: props.song.id, like: !isLiked });
}}
isLiked={isLiked}
/>
</View>
</PresenceTransition>
<PresenceTransition
style={{
width: '100%',
height: '100%',
position: 'absolute',
</View>
</View>
</PresenceTransition>
{Platform.OS === 'web' && (
<PresenceTransition
style={{
position: 'absolute',
bottom: 35,
left: CardDims.width / 2 - 20,
}}
visible={isSlided}
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
>
<Hoverable
onHoverIn={() => {
setIsPlayHovered(true);
}}
visible={isSlided}
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
onHoverOut={() => {
setIsPlayHovered(false);
}}
>
<View
onClick={(e) => {
e.stopPropagation();
props.onPress();
}}
style={{
position: 'absolute',
width: '100%',
height: '100%',
width: 40,
height: 40,
borderRadius: 100,
display: 'flex',
justifyContent: 'flex-end',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: isPlayHovered
? 'rgba(96, 117, 249, 0.9)'
: 'rgba(96, 117, 249, 0.7)',
}}
>
<Pressable
onHoverIn={() => {
setIsPlayHovered(true);
}}
onHoverOut={() => {
setIsPlayHovered(false);
}}
borderRadius={100}
marginBottom={35}
onPress={props.onPlay}
>
{({ isPressed, isHovered }) => (
<View
style={{
width: 40,
height: 40,
borderRadius: 100,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: (() => {
if (isPressed) {
return 'rgba(96, 117, 249, 1)';
} else if (isHovered) {
return 'rgba(96, 117, 249, 0.9)';
} else {
return 'rgba(96, 117, 249, 0.7)';
}
})(),
}}
>
<Ionicons
name="play-outline"
color={'white'}
size={20}
rounded="sm"
/>
</View>
)}
</Pressable>
<Ionicons name="play-outline" color={'white'} size={20} rounded="sm" />
</View>
</PresenceTransition>
</>
</Pressable>
</View>
</Hoverable>
</PresenceTransition>
)}
</Pressable>
);
};
@@ -0,0 +1,37 @@
import { Platform, View } from 'react-native';
import { IconButton } from 'native-base';
import { MaterialIcons } from '@expo/vector-icons';
type LikeButtonProps = {
isLiked: boolean;
onPress?: () => void;
color?: string;
};
export const LikeButton = ({ isLiked, color, onPress }: LikeButtonProps) => {
if (Platform.OS === 'web') {
// painful error of no onHover event control
return (
<View onClick={onPress}>
<MaterialIcons
color={color}
name={isLiked ? 'favorite' : 'favorite-outline'}
size={17}
/>
</View>
);
}
return (
<IconButton
variant={'ghost'}
borderRadius={'full'}
size={17}
color={color}
onPress={onPress}
_icon={{
as: MaterialIcons,
name: isLiked ? 'favorite' : 'favorite-outline',
}}
/>
);
};