[FEAT] MusicList component:

Implemented MusicList for displaying music items with optimized rendering and dynamic loading.
This commit is contained in:
mathysPaul
2023-11-12 23:39:27 +01:00
parent 2d6fd3a3dc
commit bf52e7385b
2 changed files with 142 additions and 147 deletions

View File

@@ -1,11 +1,10 @@
import { FlatList, HStack, View, useBreakpointValue, useTheme, Text, Row } from "native-base";
import MusicItem, { MusicItemType } from "./MusicItem";
import React, { useState } from "react";
import { ActivityIndicator } from "react-native";
import { ArrowDown2, ArrowRotateLeft, Chart2, Cup, Icon } from "iconsax-react-native";
import { StyleSheet } from 'react-native';
import ButtonBase from "./ButtonBase";
import useColorScheme from "../../hooks/colorScheme";
import React, { useCallback, useState, useMemo } from 'react';
import { FlatList, HStack, View, useBreakpointValue, useTheme, Text, Row } from 'native-base';
import { ActivityIndicator, StyleSheet } from 'react-native';
import MusicItem, { MusicItemType } from './MusicItem';
import ButtonBase from './ButtonBase';
import { ArrowDown2, Chart2, ArrowRotateLeft, Cup, Icon } from 'iconsax-react-native';
import useColorScheme from '../../hooks/colorScheme';
interface MusicItemTitleProps {
text: string;
@@ -13,123 +12,145 @@ interface MusicItemTitleProps {
isBigScreen: boolean;
}
const MusicItemTitle = (props: MusicItemTitleProps) => {
const colorScheme = useColorScheme();
const MusicItemTitle = React.memo((props: MusicItemTitleProps) => {
const colorScheme = useColorScheme();
return (
<Row
style={{
display: 'flex',
flex: 1,
maxWidth: props.isBigScreen ? 150 : 50,
height: '100%',
alignItems: 'center',
justifyContent: props.isBigScreen ? 'flex-end' : 'center',
}}
>
{props.isBigScreen && (
<Text fontSize="lg" style={{ paddingRight: 8 }}>
{props.text}
</Text>
)}
<props.icon
size={18}
color={colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
/>
</Row>
);
};
return (
<Row
style={{
display: 'flex',
flex: 1,
maxWidth: props.isBigScreen ? 150 : 50,
height: '100%',
alignItems: 'center',
justifyContent: props.isBigScreen ? 'flex-end' : 'center',
}}
>
{props.isBigScreen && (
<Text fontSize="lg" style={{ paddingRight: 8 }}>
{props.text}
</Text>
)}
<props.icon
size={18}
color={colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
/>
</Row>
);
});
type MusicListProps = {
initialMusics: MusicItemType[];
loadMoreMusics: (page: number) => Promise<MusicItemType[]>; // fonction pour charger plus de musiques
loadMoreMusics: (page: number, musics: MusicItemType[]) => Promise<MusicItemType[]>;
musicsPerPage: number;
};
const MusicList: React.FC<MusicListProps> = ({initialMusics}) => {
const [musicData, setMusicData] = useState<MusicItemType[]>(initialMusics);
const [loading, setLoading] = useState(false);
const { colors } = useTheme();
const screenSize = useBreakpointValue({ base: 'small', md: 'md', xl: 'xl' });
const isSmallScreen = screenSize === 'small';
const isBigScreen = screenSize === 'xl';
const MusicList: React.FC<MusicListProps> = React.memo(({ initialMusics, loadMoreMusics, musicsPerPage }) => {
const [musicListState, setMusicListState] = useState({
allMusics: initialMusics,
displayedMusics: initialMusics.slice(0, musicsPerPage),
currentPage: 1,
loading: false,
hasMoreMusics: true,
});
const { colors } = useTheme();
const screenSize = useBreakpointValue({ base: 'small', md: 'md', xl: 'xl' });
const isBigScreen = screenSize === 'xl';
const loadMoreMusicItems = () => {
if (!loading) {
setLoading(true);
setTimeout(() => {
const moreItems: MusicItemType[] = [
];
setMusicData((currentItems) => [...currentItems, ...moreItems]);
setLoading(false);
}, 2000); // Simule un appel réseau avec un délai de 2 secondes.
}
};
return (
<View>
<View style={styles.container}>
<HStack
space={isSmallScreen ? 1 : 2}
style={{
backgroundColor: colors.coolGray[500],
paddingHorizontal: isSmallScreen ? 8 : 16,
paddingVertical: 12,
}}
>
<Text
fontSize="lg"
style={{ flex: 4, width: '100%', justifyContent: 'center', paddingRight: 60 }}
>
Song
</Text>
{[
{ text: 'level', icon: Chart2 },
{ text: 'lastScore', icon: ArrowRotateLeft },
{ text: 'BastScore', icon: Cup },
].map((value) => (
<MusicItemTitle
key={value.text + 'key'}
text={value.text}
icon={value.icon}
isBigScreen={isBigScreen}
/>
))}
</HStack>
<FlatList
data={musicData}
renderItem={({ item }) => <MusicItem style={{marginBottom: 2}} {...item} />}
keyExtractor={(item) => item.artist + item.song}
const loadMoreMusicItems = useCallback(async () => {
if (musicListState.loading || !musicListState.hasMoreMusics) {
return;
}
setMusicListState(prevState => ({ ...prevState, loading: true }));
let hasMoreMusics = true;
const nextEndIndex = (musicListState.currentPage + 1) * musicsPerPage;
let updatedAllMusics = musicListState.allMusics;
if (updatedAllMusics.length <= nextEndIndex) {
const newMusics = await loadMoreMusics(musicListState.currentPage, updatedAllMusics);
updatedAllMusics = [...updatedAllMusics, ...newMusics];
hasMoreMusics = newMusics.length > 0;
}
setMusicListState(prevState => ({
...prevState,
allMusics: updatedAllMusics,
displayedMusics: updatedAllMusics.slice(0, nextEndIndex),
currentPage: prevState.currentPage + 1,
loading: false,
hasMoreMusics: hasMoreMusics,
}));
}, [musicsPerPage, loadMoreMusics, musicListState]);
const headerComponent = useMemo(() => (
<HStack
space={isBigScreen ? 1 : 2}
style={{
backgroundColor: colors.coolGray[500],
paddingHorizontal: isBigScreen ? 8 : 16,
paddingVertical: 12,
marginBottom: 2,
}}
>
<Text
fontSize="lg"
style={{ flex: 4, width: '100%', justifyContent: 'center', paddingRight: 60 }}
>
Song
</Text>
{[
{ text: 'level', icon: Chart2 },
{ text: 'lastScore', icon: ArrowRotateLeft },
{ text: 'BastScore', icon: Cup },
].map((value) => (
<MusicItemTitle
key={value.text + 'key'}
text={value.text}
icon={value.icon}
isBigScreen={isBigScreen}
/>
</View>
<View style={styles.footerContainer}>
{loading ? (
<ActivityIndicator
color={colors.primary[300]}
/>
))}
</HStack>
), [colors.coolGray[500], isBigScreen]);
return (
<FlatList
style={styles.container}
ListHeaderComponent={headerComponent}
data={musicListState.displayedMusics}
renderItem={({ item }) => <MusicItem style={{marginBottom: 2}} {...item} />}
keyExtractor={(item) => item.artist + item.song}
ListFooterComponent={musicListState.hasMoreMusics ?
(<View style={styles.footerContainer}>
{musicListState.loading ? (
<ActivityIndicator color={colors.primary[300]} />
) : (
<ButtonBase
style={{borderRadius: 999}}
onPress={loadMoreMusicItems}
icon={ArrowDown2}
style={{borderRadius: 999}}
onPress={loadMoreMusicItems}
icon={ArrowDown2}
/>
)}
</View>
</View>
);
};
</View>) : null
}
/>
);
});
const styles = StyleSheet.create({
container: {
container: {
flex: 1,
gap: 2,
borderRadius: 10,
overflow: 'hidden',
},
footerContainer : {
},
footerContainer : {
height: 60,
justifyContent: 'center',
alignItems: 'center',
}
}
});
export default MusicList;
export default MusicList;

View File

@@ -169,49 +169,23 @@ export const fakeMusicData = [
export const FavoritesMusic = () => {
const { colors } = useTheme();
const screenSize = useBreakpointValue({ base: 'small', md: 'md', xl: 'xl' });
const isSmallScreen = screenSize === 'small';
const isBigScreen = screenSize === 'xl';
return (
<>
<MusicList
initialMusics={fakeMusicData}
loadMoreMusics={async (page: number) => {
console.log(page, 'Function not implemented.');
return [];
}}
/>
{/* <MusicItem
image={
'https://static.vecteezy.com/system/resources/previews/016/552/335/non_2x/luffy-kawai-chibi-cute-onepiece-anime-design-and-doodle-art-for-icon-logo-collection-and-others-free-vector.jpg'
}
liked={false}
onLike={() => {
console.log('Liked !');
}}
level={3}
lastScore={25550}
bestScore={420}
artist={'Ludwig van Beethoven'}
song={'Piano Sonata No. 8'}
/>
<MusicItem
image={
'https://static.vecteezy.com/system/resources/previews/016/552/335/non_2x/luffy-kawai-chibi-cute-onepiece-anime-design-and-doodle-art-for-icon-logo-collection-and-others-free-vector.jpg'
}
liked={true}
onLike={() => {
console.log('Liked !');
}}
level={3}
lastScore={255500000}
bestScore={42000}
artist={'Ludwig van Beethoven'}
song={'Sonata for Piano no. 20 in G major, op. 49 no. 2'}
/> */}
</>
<MusicList
initialMusics={fakeMusicData.slice(0, 20)}
musicsPerPage={50}
loadMoreMusics={async (page: number, musics: MusicItemType[]) => {
console.log(page, 'Loading more musics.');
// Calculer le début et la fin de la tranche à charger
const startIndex = musics.length;
const endIndex = startIndex + 15;
console.log('length:', fakeMusicData.length);
console.log('min:', Math.min(endIndex, fakeMusicData.length));
let tmp = fakeMusicData.slice(startIndex, Math.min(endIndex, fakeMusicData.length));
console.log('tmp:', tmp.length);
return tmp;
}}
/>
);
};