import { RequestStateCode } from "@/constants/commonConst";
import TrackPlayer from "@/core/trackPlayer";
import rpx from "@/utils/rpx";
import { FlashList } from "@shopify/flash-list";
import React, { useRef, useCallback, useState, useEffect } from "react";
import { FlatListProps, Pressable, StyleSheet, View } from "react-native";
import ListEmpty from "../base/listEmpty";
import ListFooter from "../base/listFooter";
import MusicItem from "../mediaItem/musicItem";
import { isSameMediaItem } from "@/utils/mediaUtils";
import Icon from "../base/icon";
import { iconSizeConst } from "@/constants/uiConst";
import useColors from "@/hooks/useColors";
interface IMusicListProps {
Header?: FlatListProps<IMusic.IMusicItem>["ListHeaderComponent"];
musicList?: IMusic.IMusicItem[];
musicSheet?: IMusic.IMusicSheetItem;
showIndex?: boolean;
onItemPress?: (
musicItem: IMusic.IMusicItem,
musicList?: IMusic.IMusicItem[],
) => void;
state: RequestStateCode;
highlightMusicItem?: IMusic.IMusicItem | null;
onRetry?: () => void;
onLoadMore?: () => void;
}
const ITEM_HEIGHT = rpx(120);
export default function MusicList(props: IMusicListProps) {
const {
Header,
musicList,
musicSheet,
showIndex,
onItemPress,
state,
onRetry,
onLoadMore,
highlightMusicItem,
} = props;
const colors = useColors();
const flashListRef = useRef<FlashList<IMusic.IMusicItem>>(null);
const [showBadge, setShowBadge] = useState(false);
const hideTimeoutRef = useRef<NodeJS.Timeout>();
const highlightIndex = React.useMemo(() => {
if (!highlightMusicItem || !musicList) return -1;
return musicList.findIndex(item => isSameMediaItem(item, highlightMusicItem));
}, [highlightMusicItem, musicList]);
const handleScrollBegin = useCallback(() => {
if (highlightIndex !== -1) {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
setShowBadge(true);
}
}, [highlightIndex]);
const handleScrollEnd = useCallback(() => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
hideTimeoutRef.current = setTimeout(() => {
setShowBadge(false);
}, 5000);
}, []);
const scrollToHighlight = useCallback(() => {
if (highlightIndex !== -1 && flashListRef.current) {
flashListRef.current.scrollToIndex({
index: highlightIndex,
animated: false,
viewPosition: 0,
});
setShowBadge(false);
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
}
}, [highlightIndex]);
useEffect(() => {
return () => {
if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current);
}
};
}, []);
return (
<View style={styles.container}>
<FlashList
ref={flashListRef}
ListHeaderComponent={Header}
ListEmptyComponent={<ListEmpty state={state} onRetry={onRetry} />}
ListFooterComponent={
musicList?.length ? <ListFooter state={state} onRetry={onRetry} /> : null
}
extraData={highlightMusicItem}
data={musicList ?? []}
estimatedItemSize={ITEM_HEIGHT}
onScrollBeginDrag={handleScrollBegin}
onScrollEndDrag={handleScrollEnd}
onMomentumScrollEnd={handleScrollEnd}
renderItem={({ index, item: musicItem }) => {
return (
<MusicItem
musicItem={musicItem}
index={showIndex ? index + 1 : undefined}
onItemPress={() => {
if (onItemPress) {
onItemPress(musicItem, musicList);
} else {
TrackPlayer.playWithReplacePlayList(
musicItem,
musicList ?? [musicItem],
);
}
}}
musicSheet={musicSheet}
highlight={isSameMediaItem(musicItem, highlightMusicItem)}
/>
);
}}
onEndReached={() => {
if (state === RequestStateCode.IDLE || state === RequestStateCode.PARTLY_DONE) {
onLoadMore?.();
}
}}
onEndReachedThreshold={0.1}
/>
{showBadge && (
<View style={styles.badge} pointerEvents="box-none">
<Pressable
style={[styles.badgeButton, { backgroundColor: colors.notification }]}
onPress={scrollToHighlight}
>
<Icon
name="crosshair"
size={iconSizeConst.normal}
color={colors.text}
/>
</Pressable>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
badge: {
position: "absolute",
bottom: rpx(80),
right: rpx(84),
zIndex: 1000,
},
badgeButton: {
width: rpx(64),
height: rpx(64),
borderRadius: rpx(32),
justifyContent: "center",
alignItems: "center",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
});