[FEAT] MusicList component:
Implemented MusicList for displaying music items with optimized rendering and dynamic loading.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user