Added SongCardInfo for the V2 design and type fixes

This commit is contained in:
Clément Le Bihan
2023-09-20 00:35:10 +02:00
parent 94875d4c7f
commit 8e5c65e6f2
8 changed files with 503 additions and 73 deletions

View File

@@ -39,10 +39,15 @@ const removeMe = () => '';
const protectedRoutes = () =>
({
Home: {
component: TabNavigation,
options: { title: translate('welcome'), headerShown: false },
component: HomeView,
options: { title: translate('welcome'), headerLeft: null },
link: '/',
},
HomeNew: {
component: TabNavigation,
options: { headerShown: false },
link: '/V2',
},
Play: { component: PlayView, options: { title: translate('play') }, link: '/play/:songId' },
Settings: {
component: SetttingsNavigator,

View File

@@ -0,0 +1,94 @@
import { Image, View } from 'react-native';
import { Text, Pressable, PresenceTransition } from 'native-base';
type HomeMainSongCardProps = {
image: string;
title: string;
artist: string;
onPress: () => void;
};
const HomeMainSongCard = (props: HomeMainSongCardProps) => {
// on hover darken the image and show the title and artist with fade in
return (
<Pressable onPress={props.onPress}>
{({ isHovered }) => (
<View
style={{
width: '100%',
height: '100%',
borderRadius: 12,
overflow: 'hidden',
position: 'relative',
}}
>
<Image
source={{
uri: props.image,
}}
style={{
aspectRatio: 1,
width: '100%',
height: '100%',
flexShrink: 1,
}}
/>
<PresenceTransition
style={{
width: '100%',
height: '100%',
position: 'absolute',
}}
visible={isHovered}
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
>
<View
style={{
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
justifyContent: 'flex-end',
alignItems: 'flex-start',
paddingHorizontal: 16,
paddingVertical: 36,
}}
>
<Text
style={{
color: 'white',
fontSize: 46,
fontWeight: 'bold',
}}
selectable={false}
>
{props.title}
</Text>
<Text
style={{
color: 'white',
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
}}
selectable={false}
>
{props.artist}
</Text>
</View>
</PresenceTransition>
</View>
)}
</Pressable>
);
};
HomeMainSongCard.defaultProps = {
onPress: () => {},
};
export default HomeMainSongCard;

View File

@@ -0,0 +1,259 @@
import Song from '../../models/Song';
import React from 'react';
import { Image, View } from 'react-native';
import { Pressable, Text, PresenceTransition, Icon, Button } from 'native-base';
import { Ionicons } from '@expo/vector-icons';
type SongCardInfoProps = {
song: Song;
onPress: () => void;
onPlay: () => void;
};
const CardDims = {
height: 200,
width: 200,
};
const Scores = [
{
icon: 'warning',
score: 3,
},
{
icon: 'star',
score: -225,
},
{
icon: 'trophy',
score: 100,
},
];
const SongCardInfo = (props: SongCardInfoProps) => {
const [isPlayHovered, setIsPlayHovered] = React.useState(false);
const [isHovered, setIsHovered] = React.useState(false);
const [isSlided, setIsSlided] = React.useState(false);
return (
<View
style={{
width: CardDims.width,
height: CardDims.height,
// @ts-expect-error boxShadow isn't yet supported by react native
boxShadow: '0px 4px 4px 0px rgba(0,0,0,0.25)',
backDropFilter: 'blur(2px)',
backgroundColor: 'rgba(16, 16, 20, 0.70)',
borderRadius: 12,
overflow: 'hidden',
}}
>
<Pressable
delayHoverIn={7}
isHovered={isPlayHovered ? true : undefined}
onPress={props.onPress}
style={{
width: '100%',
}}
onHoverIn={() => {
setIsHovered(true);
}}
onHoverOut={() => {
setIsHovered(false);
setIsSlided(false);
}}
>
<>
<View
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',
// @ts-expect-error gap isn't yet supported by react native
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}
initial={{
translateY: 0,
}}
animate={{
translateY: -55,
}}
onTransitionComplete={() => {
if (isHovered) {
setIsSlided(true);
}
}}
>
<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,
}}
>
<View
style={{
width: '100%',
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'space-between',
}}
>
<View
style={{
flexShrink: 1,
}}
>
<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.artistId}
</Text>
</View>
<Ionicons
style={{
flexShrink: 0,
}}
name="bookmark-outline"
size={17}
color="#6075F9"
/>
</View>
</View>
</PresenceTransition>
{/* icon bu=outon appear in the middle of the card after the first presencetransition is done */}
<PresenceTransition
style={{
width: '100%',
height: '100%',
position: 'absolute',
}}
visible={isSlided}
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
>
<View
style={{
position: 'absolute',
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
<Button
onHoverIn={() => {
setIsPlayHovered(true);
}}
onHoverOut={() => {
setIsPlayHovered(false);
}}
borderRadius={100}
marginBottom={35}
leftIcon={
<Ionicons
name="play-outline"
color={'white'}
size={20}
rounded="sm"
/>
}
onPress={props.onPlay}
/>
</View>
</PresenceTransition>
</>
</Pressable>
</View>
);
};
SongCardInfo.defaultProps = {
onPress: () => {},
onPlay: () => {},
};
export default SongCardInfo;

View File

@@ -66,6 +66,7 @@ const TabNavigation = () => {
const child = <HomeView />;
const appTabs = tabs.map((t) => {
// use the same instance of a component between desktop and mobile
return {
...t,
onPress: () => setActiveTab(t.id),
@@ -94,7 +95,20 @@ const TabNavigation = () => {
activeTabID={activeTab}
setActiveTabID={setActiveTab}
>
{child}
<View
style={{
width: 'calc(100% - 5)',
height: '100%',
backgroundColor: 'rgba(16, 16, 20, 0.50)',
borderRadius: 12,
margin: 5,
// @ts-expect-error backDropFilter isn't yet supported by react native
backDropFilter: 'blur(2px)',
padding: 15,
}}
>
{child}
</View>
</TabNavigationPhone>
) : (
<TabNavigationDesktop
@@ -104,7 +118,21 @@ const TabNavigation = () => {
isCollapsed={isDesktopCollapsed}
setIsCollapsed={setIsDesktopCollapsed}
>
{child}
<View
style={{
width: 'calc(100% - 10)',
height: '100%',
backgroundColor: 'rgba(16, 16, 20, 0.50)',
borderRadius: 12,
marginVertical: 10,
marginRight: 10,
// @ts-expect-error backDropFilter isn't yet supported by react native
backDropFilter: 'blur(2px)',
padding: 20,
}}
>
{child}
</View>
</TabNavigationDesktop>
)}
</View>

View File

@@ -1,5 +1,5 @@
import { View, Image } from 'react-native';
import { Divider, Text, Center } from 'native-base';
import { Divider, Text, Center, ScrollView } from 'native-base';
import TabNavigationButton from './TabNavigationButton';
import TabNavigationList from './TabNavigationList';
import { useAssets } from 'expo-asset';
@@ -140,10 +140,14 @@ const TabNavigationDesktop = (props: TabNavigationDesktopProps) => {
</TabNavigationList>
</View>
</View>
<View style={{
height: '100%',
width: 'calc(100% - 300px)',
}}>{props.children}</View>
<ScrollView
style={{
height: '100%',
width: 'calc(100% - 300px)',
}}
>
{props.children}
</ScrollView>
</View>
);
};

View File

@@ -1,5 +1,5 @@
import { View } from 'react-native';
import { Text, Center } from 'native-base';
import { Center, ScrollView } from 'native-base';
import TabNavigationButton from './TabNavigationButton';
import { NaviTab } from './TabNavigation';
@@ -55,10 +55,14 @@ const TabNavigationPhone = (props: TabNavigationPhoneProps) => {
</View>
</Center>
</View>
<View style={{
width: '100%',
height: 'calc(100% - 90px)',
}}>{props.children}</View>
<ScrollView
style={{
width: '100%',
height: 'calc(100% - 90px)',
}}
>
{props.children}
</ScrollView>
</View>
);
};

View File

@@ -136,6 +136,12 @@ const HomeView = () => {
size="sm"
onPress={() => navigation.navigate('Settings')}
/>
<TextButton
label={'V2'}
colorScheme="gray"
size="sm"
onPress={() => navigation.navigate('HomeNew')}
/>
</HStack>
<Box style={{ width: '100%' }}>
<Heading>

View File

@@ -1,11 +1,48 @@
import { View, Image } from 'react-native';
import { Text } from 'native-base';
import { View } from 'react-native';
import { Text, useBreakpointValue } from 'native-base';
import React from 'react';
import { useQuery } from '../../Queries';
import HomeMainSongCard from '../../components/V2/HomeMainSongCard';
import SongCardInfo from '../../components/V2/SongCardInfo';
import API from '../../API';
const bigSideRatio = 100;
const smallSideRatio = 61;
const bigSideRatio = 1000;
const smallSideRatio = 618;
type HomeCardProps = {
image: string;
title: string;
artist: string;
};
const cards = [
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688155292180560/image_homeview1.png',
title: 'Beethoven',
artist: 'Synphony No. 9',
},
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688154923090093/image_homeview2.png',
title: 'Mozart',
artist: 'Lieder Kantate KV 619',
},
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688154499457096/image_homeview3.png',
title: 'Back',
artist: 'Truc Truc',
},
{
image: 'https://media.discordapp.net/attachments/717080637038788731/1153688154109394985/image_homeview4.png',
title: 'Mozart',
artist: 'Machin Machin',
},
] as [HomeCardProps, HomeCardProps, HomeCardProps, HomeCardProps];
const HomeView = () => {
const songsQuery = useQuery(API.getSongSuggestions);
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
const isPhone = screenSize === 'small';
return (
<View
style={{
@@ -15,19 +52,13 @@ const HomeView = () => {
flexDirection: 'column',
}}
>
<View
style={{
width: '100%',
aspectRatio: 16 / 9,
}}
>
<View>
<View
style={{
alignSelf: 'stretch',
maxWidth: '1100px',
alignItems: 'stretch',
flexDirection: 'row',
display: 'flex',
gap: '0px',
flexDirection: isPhone ? 'column' : 'row',
}}
>
<View
@@ -35,23 +66,14 @@ const HomeView = () => {
flexGrow: bigSideRatio,
}}
>
<Image
source={{
uri: 'https://media.discordapp.net/attachments/717080637038788731/1153688155292180560/image_homeview1.png',
}}
style={{
aspectRatio: 1,
}}
/>
<HomeMainSongCard {...cards[0]} />
</View>
<View
style={{
flexGrow: smallSideRatio,
height: '100%',
display: 'flex',
flexDirection: 'column',
flexDirection: isPhone ? 'row' : 'column',
alignItems: 'stretch',
gap: '0px',
}}
>
<View
@@ -59,23 +81,14 @@ const HomeView = () => {
flexGrow: bigSideRatio,
}}
>
<Image
source={{
uri: 'https://media.discordapp.net/attachments/717080637038788731/1153688154923090093/image_homeview2.png',
}}
style={{
aspectRatio: 1,
}}
/>
<HomeMainSongCard {...cards[1]} />
</View>
<View
style={{
flexGrow: smallSideRatio,
width: '100%',
display: 'flex',
flexDirection: 'row-reverse',
flexDirection: isPhone ? 'column-reverse' : 'row-reverse',
alignItems: 'stretch',
gap: '0px',
}}
>
<View
@@ -83,47 +96,32 @@ const HomeView = () => {
flexGrow: bigSideRatio,
}}
>
<Image
source={{
uri: 'https://media.discordapp.net/attachments/717080637038788731/1153688154499457096/image_homeview3.png',
}}
style={{
aspectRatio: 1,
}}
/>
<HomeMainSongCard {...cards[2]} />
</View>
<View
style={{
flexGrow: smallSideRatio,
height: '100%',
display: 'flex',
flexDirection: 'column-reverse',
flexDirection: isPhone ? 'row-reverse' : 'column-reverse',
alignItems: 'stretch',
gap: '0px',
}}
>
<View
style={{
flexGrow: bigSideRatio,
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
justifyContent: 'flex-end',
}}
>
<Image
source={{
uri: 'https://media.discordapp.net/attachments/717080637038788731/1153688154109394985/image_homeview4.png',
}}
style={{
aspectRatio: 1,
}}
/>
<HomeMainSongCard {...cards[3]} />
</View>
<View
style={{
width: '100%',
flexGrow: smallSideRatio,
}}
>
<Text>More</Text>
</View>
></View>
</View>
</View>
</View>
@@ -137,7 +135,39 @@ const HomeView = () => {
width: '100%',
}}
>
<Text>Footer</Text>
<Text
style={{
color: 'white',
fontSize: 24,
fontWeight: 'bold',
marginLeft: 16,
marginBottom: 16,
marginTop: 24,
}}
>
{'Suggestions'}
</Text>
{songsQuery.isLoading && <Text>Loading...</Text>}
<View
style={{
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
alignItems: 'flex-start',
// @ts-expect-error - gap is not in the typings
gap: 16,
}}
>
{songsQuery.data?.map((song) => (
<SongCardInfo
key={song.id}
song={song}
onPress={() => {
console.log('SongCardInfo pressed');
}}
/>
))}
</View>
</View>
</View>
);