continue generating flow, add backend connection on main tabs
This commit is contained in:
@@ -8,10 +8,14 @@ import Animated, { Easing, FadeIn, FadeOut } from "react-native-reanimated";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { navigate } from "../../navigation/NavigationService";
|
||||
import { Routes } from "../../navigation";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
|
||||
const AllMyLikedMusic = () => {
|
||||
const [top, setTop] = useState(0);
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const { userLikedProjects: projects } = useUser();
|
||||
|
||||
return (
|
||||
<Page
|
||||
@@ -22,26 +26,33 @@ const AllMyLikedMusic = () => {
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ gap: 10 }}>
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<MusicCard
|
||||
key={index}
|
||||
onPressMore={(posTop) => {
|
||||
setTop(posTop);
|
||||
if (showMenu) {
|
||||
if (posTop === top) {
|
||||
setShowMenu(false);
|
||||
{Array.isArray(projects) &&
|
||||
projects.map((item) => (
|
||||
<MusicCard
|
||||
key={item.id}
|
||||
title={item?.title || "Sans titre"}
|
||||
imageUri={item?.coverUrl || null}
|
||||
subtitle={"MusicLand"}
|
||||
projectId={item?.id}
|
||||
likedBy={item?.likedBy || []}
|
||||
onPress={() => navigate(Routes.MusicDetails, { projectId: item.id })}
|
||||
onPressMore={(posTop) => {
|
||||
setTop(posTop);
|
||||
if (showMenu) {
|
||||
if (posTop === top) {
|
||||
setShowMenu(false);
|
||||
} else {
|
||||
setShowMenu(false);
|
||||
setTimeout(() => {
|
||||
setShowMenu(true);
|
||||
}, 300);
|
||||
}
|
||||
} else {
|
||||
setShowMenu(false);
|
||||
setTimeout(() => {
|
||||
setShowMenu(true);
|
||||
}, 300);
|
||||
setShowMenu(true);
|
||||
}
|
||||
} else {
|
||||
setShowMenu(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{showMenu && (
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(300).easing(Easing.ease)}
|
||||
|
||||
@@ -6,10 +6,14 @@ import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import { gutters, Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import useLayoutType from "../../hooks/useLayoutType";
|
||||
import { useUser } from "../../providers/UserDataProvider";
|
||||
import { navigate } from "../../navigation/NavigationService";
|
||||
import { Routes } from "../../navigation";
|
||||
|
||||
const AllMyMusic = () => {
|
||||
const [containerLayout, setContainerLayout] = useState(null);
|
||||
const { isWeb } = useLayoutType();
|
||||
const { userProjects: projects } = useUser();
|
||||
|
||||
return (
|
||||
<Page
|
||||
@@ -39,17 +43,23 @@ const AllMyMusic = () => {
|
||||
}}
|
||||
>
|
||||
<FlatList
|
||||
data={Array.from({ length: 9 })}
|
||||
data={Array.isArray(projects) ? projects : []}
|
||||
numColumns={3}
|
||||
columnWrapperStyle={{ gap: 10 }}
|
||||
contentContainerStyle={{ gap: 14 }}
|
||||
renderItem={() => (
|
||||
keyExtractor={(item) => item.id}
|
||||
// Projects are provided by provider with a live listener
|
||||
renderItem={({ item }) => (
|
||||
<Pressable
|
||||
style={{ flex: 1, gap: 4 }}
|
||||
onPress={() => console.log("PRESSED")}
|
||||
onPress={() =>
|
||||
navigate(Routes.MusicDetails, { projectId: item.id })
|
||||
}
|
||||
>
|
||||
<Image
|
||||
source={img.placeholder2}
|
||||
source={
|
||||
item?.coverUrl ? { uri: item.coverUrl } : img.placeholder2
|
||||
}
|
||||
style={{ width: "100%", height: 114, borderRadius: 8 }}
|
||||
/>
|
||||
<Text
|
||||
@@ -59,7 +69,7 @@ const AllMyMusic = () => {
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
Voiture
|
||||
{item?.title || "Sans titre"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
} from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import Page from "../../layouts/Page";
|
||||
import { background, icons, img } from "../../assets";
|
||||
import { Palette } from "../../styles";
|
||||
@@ -15,16 +15,169 @@ import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import Slider from "../../components/Slider";
|
||||
import { useRoute } from "@react-navigation/core";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import firebase, { usersRef, projectsRef, arrayUnion, arrayRemove } from "../../config/firebase";
|
||||
import { useGlobal } from "reactn";
|
||||
import useDataFromRef from "../../hooks/useDataFromRef";
|
||||
import { Audio } from "expo-av";
|
||||
|
||||
const MusicDetails = () => {
|
||||
const params = useRoute().params;
|
||||
const params = useRoute().params || {};
|
||||
const action = params?.action;
|
||||
const projectId = params?.projectId || null;
|
||||
const [fav, setFav] = useState(false);
|
||||
const [currentUID] = useGlobal("currentUID");
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [progressInfo, setProgressInfo] = useState({ pos: 0, dur: 0 });
|
||||
const wasPlayingBeforeSeek = React.useRef(false);
|
||||
|
||||
const { data: project } = useDataFromRef({
|
||||
ref: projectId
|
||||
? firebase.firestore().collection("projects").doc(projectId)
|
||||
: null,
|
||||
simpleRef: true,
|
||||
listener: true,
|
||||
condition: !!projectId,
|
||||
});
|
||||
|
||||
const { data: owner } = useDataFromRef({
|
||||
ref: project?.userId ? usersRef.doc(project.userId) : null,
|
||||
simpleRef: true,
|
||||
listener: true,
|
||||
condition: !!project?.userId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (project && currentUID) {
|
||||
const liked = Array.isArray(project?.likedBy)
|
||||
? project.likedBy.includes(currentUID)
|
||||
: false;
|
||||
setFav(liked);
|
||||
}
|
||||
}, [project?.likedBy, currentUID]);
|
||||
|
||||
const title = project?.title || "Sans titre";
|
||||
const artist = owner?.userName || "MusicLand";
|
||||
const coverUrl = project?.coverUrl || null;
|
||||
const songUrl = useMemo(() => {
|
||||
if (project?.song?.url) return project.song.url;
|
||||
const arr = Array.isArray(project?.musicUrls) ? project.musicUrls : [];
|
||||
return arr[0] || null;
|
||||
}, [project]);
|
||||
|
||||
const soundRef = React.useRef(null);
|
||||
|
||||
// Load/unload audio with expo-av for reliable status updates on iOS/Android
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
const load = async () => {
|
||||
try {
|
||||
// Unload previous
|
||||
if (soundRef.current) {
|
||||
await soundRef.current.unloadAsync();
|
||||
soundRef.current.setOnPlaybackStatusUpdate(null);
|
||||
soundRef.current = null;
|
||||
}
|
||||
if (!songUrl) return;
|
||||
const { sound } = await Audio.Sound.createAsync(
|
||||
{ uri: songUrl },
|
||||
{ shouldPlay: false },
|
||||
(status) => {
|
||||
if (!isMounted) return;
|
||||
if (!status || !status.isLoaded) return;
|
||||
const pos = status.positionMillis || 0;
|
||||
const dur = status.durationMillis || 0;
|
||||
setProgressInfo({ pos, dur });
|
||||
setIsPlaying(!!status.isPlaying);
|
||||
},
|
||||
);
|
||||
soundRef.current = sound;
|
||||
} catch (e) {
|
||||
console.log("Audio load error", e?.message);
|
||||
}
|
||||
};
|
||||
load();
|
||||
return () => {
|
||||
isMounted = false;
|
||||
(async () => {
|
||||
try {
|
||||
if (soundRef.current) {
|
||||
await soundRef.current.unloadAsync();
|
||||
soundRef.current.setOnPlaybackStatusUpdate(null);
|
||||
soundRef.current = null;
|
||||
}
|
||||
} catch (_) {}
|
||||
})();
|
||||
};
|
||||
}, [songUrl]);
|
||||
|
||||
const fmt = (ms) => {
|
||||
const total = Math.max(0, Math.floor((ms || 0) / 1000));
|
||||
const m = Math.floor(total / 60)
|
||||
.toString()
|
||||
.padStart(1, "0");
|
||||
const s = (total % 60).toString().padStart(2, "0");
|
||||
return `${m}:${s}`;
|
||||
};
|
||||
|
||||
const togglePlay = async () => {
|
||||
const sound = soundRef.current;
|
||||
if (!sound || !songUrl) return;
|
||||
try {
|
||||
const status = await sound.getStatusAsync();
|
||||
if (status?.isLoaded && status.isPlaying) {
|
||||
await sound.pauseAsync();
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
await sound.playAsync();
|
||||
setIsPlaying(true);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("MusicDetails audio error", e?.message);
|
||||
}
|
||||
};
|
||||
|
||||
const onSeek = async (ratio) => {
|
||||
try {
|
||||
const dur = progressInfo.dur || 0;
|
||||
const pos = Math.floor(dur * ratio);
|
||||
const sound = soundRef.current;
|
||||
if (sound && dur > 0) {
|
||||
await sound.setPositionAsync(pos);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("MusicDetails seek error", e?.message);
|
||||
}
|
||||
};
|
||||
|
||||
const seekBy = async (deltaSeconds) => {
|
||||
try {
|
||||
const cur = Math.floor((progressInfo.pos || 0) / 1000);
|
||||
const next = Math.max(0, cur + deltaSeconds);
|
||||
const sound = soundRef.current;
|
||||
if (sound) await sound.setPositionAsync(next * 1000);
|
||||
} catch (e) {
|
||||
console.log("MusicDetails seekBy error", e?.message);
|
||||
}
|
||||
};
|
||||
const description = useMemo(() => {
|
||||
// Try to build a readable text from lyrics if present
|
||||
if (Array.isArray(project?.lyrics)) {
|
||||
return project.lyrics
|
||||
.map((s) => (s?.lyrics || "").trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 4)
|
||||
.join("\n\n");
|
||||
}
|
||||
const c = project?.lyrics?.couplet;
|
||||
const r = project?.lyrics?.refrain;
|
||||
const parts = [c, r].filter(Boolean);
|
||||
return parts.length ? parts.join("\n\n") : "";
|
||||
}, [project]);
|
||||
|
||||
return (
|
||||
<Page
|
||||
headerType="NAVIGATION"
|
||||
title={action === "userProfile" ? "Mon profil" : "Recherche"}
|
||||
title={action === "userProfile" ? "Mon profil" : "Détail musique"}
|
||||
backgroundImg={
|
||||
action === "userProfile" ? background.profileBG : background.libraryBG2
|
||||
}
|
||||
@@ -48,14 +201,32 @@ const MusicDetails = () => {
|
||||
// stickyHeaderIndices={[1]}
|
||||
>
|
||||
<View style={{ gap: 28 }}>
|
||||
<Image source={img.placeholder4} style={styles.img} />
|
||||
<Image
|
||||
source={coverUrl ? { uri: coverUrl } : img.placeholder4}
|
||||
style={styles.img}
|
||||
/>
|
||||
<View style={{ ...Style.containerSpaceBetween }}>
|
||||
<View>
|
||||
<Text style={styles.title}>Lust for Life</Text>
|
||||
<Text style={styles.name}>Lana del Rey</Text>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<Text style={styles.name}>{artist}</Text>
|
||||
</View>
|
||||
<View style={{ ...Style.containerRow, gap: 12 }}>
|
||||
<Pressable onPress={() => setFav(!fav)}>
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
if (!projectId || !currentUID) return;
|
||||
const next = !fav;
|
||||
setFav(next);
|
||||
try {
|
||||
await projectsRef.doc(projectId).update({
|
||||
likedBy: next
|
||||
? arrayUnion(currentUID)
|
||||
: arrayRemove(currentUID),
|
||||
});
|
||||
} catch (e) {
|
||||
setFav(!next);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={fav ? icons.heart : icons.heartOutline}
|
||||
style={{ ...size({ size: 24 }) }}
|
||||
@@ -73,47 +244,91 @@ const MusicDetails = () => {
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ paddingTop: 22 }}>
|
||||
<Slider value={"0:00"} maxValue={"2:00"} />
|
||||
<Slider
|
||||
value={fmt(progressInfo.pos)}
|
||||
maxValue={fmt(progressInfo.dur)}
|
||||
progress={
|
||||
progressInfo.dur ? (progressInfo.pos || 0) / progressInfo.dur : 0
|
||||
}
|
||||
seekEnabled={!!songUrl}
|
||||
onSeekStart={async () => {
|
||||
try {
|
||||
const sound = soundRef.current;
|
||||
const status = await sound?.getStatusAsync?.();
|
||||
wasPlayingBeforeSeek.current =
|
||||
!!status?.isLoaded && !!status?.isPlaying;
|
||||
if (status?.isLoaded && status?.isPlaying) {
|
||||
await sound.pauseAsync();
|
||||
setIsPlaying(false);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Pause on seek start error", e?.message);
|
||||
}
|
||||
}}
|
||||
onSeek={onSeek}
|
||||
onSeekEnd={async () => {
|
||||
try {
|
||||
const sound = soundRef.current;
|
||||
if (sound && wasPlayingBeforeSeek.current) {
|
||||
await sound.playAsync();
|
||||
setIsPlaying(true);
|
||||
}
|
||||
wasPlayingBeforeSeek.current = false;
|
||||
} catch (e) {
|
||||
console.log("Resume after seek error", e?.message);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<View style={{ ...Style.containerRow, gap: 24, alignSelf: "center" }}>
|
||||
<Pressable>
|
||||
<Image source={icons.forward} />
|
||||
</Pressable>
|
||||
<Pressable>
|
||||
<Image source={icons.pause} />
|
||||
</Pressable>
|
||||
<Pressable>
|
||||
{/* Previous (rewind 10s) */}
|
||||
<Pressable onPress={() => seekBy(-10)}>
|
||||
<Image
|
||||
source={icons.forward}
|
||||
style={{ transform: [{ rotate: "180deg" }] }}
|
||||
style={{
|
||||
...size({ size: 30 }),
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
{/* Play / Pause */}
|
||||
<Pressable
|
||||
style={{
|
||||
...size({ size: 40 }),
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onPress={togglePlay}
|
||||
>
|
||||
<Image
|
||||
resizeMode={"contain"}
|
||||
source={isPlaying ? icons.pause : icons.play}
|
||||
style={size({ size: 34 })}
|
||||
/>
|
||||
</Pressable>
|
||||
{/* Next (forward 10s) */}
|
||||
<Pressable onPress={() => seekBy(10)}>
|
||||
<Image
|
||||
source={icons.forward}
|
||||
style={{
|
||||
...size({ size: 30 }),
|
||||
transform: [{ rotate: "180deg" }],
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ marginTop: 30, gap: 20 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 14,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
Description chanson. Viverra enim risus enim enim placerat. Integer
|
||||
pulvinar tristique suscipit risus. Id hendrerit in odio phasellus
|
||||
interdum lectus amet
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
Allô{"\n"}Écoute maman est près de toi{"\n"}Il faut lui dire "maman,
|
||||
c'est quelqu'un pour toi"{"\n"}Ah, c'est le monsieur de la dernière
|
||||
fois{"\n"}Bon, je vais la chercher{"\n"}Je crois qu'elle est dans
|
||||
son bain{"\n"}Et je sais pas si elle va pouvoir venir
|
||||
</Text>
|
||||
</View>
|
||||
{description?.length > 0 && (
|
||||
<View style={{ marginTop: 30, gap: 20 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { View, Text, Image, FlatList } from "react-native";
|
||||
import { View, Image, FlatList, Pressable } from "react-native";
|
||||
import React from "react";
|
||||
import CardContainer from "./CardContainer";
|
||||
import { Style } from "../../../styles";
|
||||
import { img } from "../../../assets";
|
||||
import { navigate } from "../../../navigation/NavigationService";
|
||||
import { Routes } from "../../../navigation";
|
||||
import { useUser } from "../../../providers/UserDataProvider";
|
||||
|
||||
const LikedMusic = () => {
|
||||
const { userLikedProjects = [] } = useUser();
|
||||
const items = Array.isArray(userLikedProjects)
|
||||
? userLikedProjects.slice(0, 6)
|
||||
: [];
|
||||
return (
|
||||
<CardContainer
|
||||
label="Musiques likées"
|
||||
@@ -14,17 +19,21 @@ const LikedMusic = () => {
|
||||
>
|
||||
<FlatList
|
||||
scrollEnabled={false}
|
||||
data={Array.from({ length: 6 })}
|
||||
data={items}
|
||||
numColumns={3}
|
||||
contentContainerStyle={{ gap: 10 }}
|
||||
columnWrapperStyle={{ gap: 6 }}
|
||||
renderItem={() => (
|
||||
<View style={{ flex: 1 }}>
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<Pressable
|
||||
style={{ flex: 1 }}
|
||||
onPress={() => navigate(Routes.MusicDetails, { projectId: item.id })}
|
||||
>
|
||||
<Image
|
||||
source={img.placeholder3}
|
||||
source={item?.coverUrl ? { uri: item.coverUrl } : img.placeholder3}
|
||||
style={{ width: "100%", height: 172, borderRadius: 10 }}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
)}
|
||||
/>
|
||||
</CardContainer>
|
||||
|
||||
@@ -12,8 +12,11 @@ import { icons, img } from "../../../assets";
|
||||
import { size } from "../../../styles/Style";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { FONT_FAMILY } from "../../../styles/Fonts";
|
||||
import { useGlobal } from "reactn";
|
||||
import { projectsRef, arrayUnion, arrayRemove } from "../../../config/firebase";
|
||||
|
||||
const MusicCard = ({ onPress, onPressMore }) => {
|
||||
const MusicCard = ({ onPress, onPressMore, title = "Sans titre", subtitle = "MusicLand", imageUri = null, projectId = null, likedBy = [] }) => {
|
||||
const [currentUID] = useGlobal("currentUID");
|
||||
const [selected, setSelected] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [layout, setLayout] = useState(null);
|
||||
@@ -26,6 +29,26 @@ const MusicCard = ({ onPress, onPressMore }) => {
|
||||
}
|
||||
}, [layout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Array.isArray(likedBy) && currentUID) {
|
||||
setSelected(likedBy.includes(currentUID));
|
||||
}
|
||||
}, [JSON.stringify(likedBy), currentUID]);
|
||||
|
||||
const toggleLike = async () => {
|
||||
if (!projectId || !currentUID) return;
|
||||
const next = !selected;
|
||||
setSelected(next);
|
||||
try {
|
||||
await projectsRef.doc(projectId).update({
|
||||
likedBy: next ? arrayUnion(currentUID) : arrayRemove(currentUID),
|
||||
});
|
||||
} catch (e) {
|
||||
// rollback on failure
|
||||
setSelected(!next);
|
||||
}
|
||||
};
|
||||
|
||||
const onPressMenu = () => {
|
||||
onPressMore?.(menuPos);
|
||||
};
|
||||
@@ -41,7 +64,7 @@ const MusicCard = ({ onPress, onPressMore }) => {
|
||||
onLayout={(e) => setLayout(e.nativeEvent.layout)}
|
||||
>
|
||||
<Image
|
||||
source={img.placeholder2}
|
||||
source={imageUri ? { uri: imageUri } : img.placeholder2}
|
||||
style={{ ...size({ size: 60 }), borderRadius: 12 }}
|
||||
/>
|
||||
<View style={styles.blurContainer}>
|
||||
@@ -57,8 +80,8 @@ const MusicCard = ({ onPress, onPressMore }) => {
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text style={styles.title}>Alors on danse</Text>
|
||||
<Text style={styles.subTitle}>Stromae</Text>
|
||||
<Text style={styles.title}>{title || "Sans titre"}</Text>
|
||||
<Text style={styles.subTitle}>{subtitle || "MusicLand"}</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
@@ -66,7 +89,7 @@ const MusicCard = ({ onPress, onPressMore }) => {
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<Pressable onPress={() => setSelected(!selected)}>
|
||||
<Pressable onPress={toggleLike}>
|
||||
<Image
|
||||
source={selected ? icons.heart : icons.heartOutline}
|
||||
style={size({ size: 24 })}
|
||||
|
||||
@@ -4,17 +4,32 @@ import CardContainer from "./CardContainer";
|
||||
import MusicCard from "./MusicCard";
|
||||
import { navigate } from "../../../navigation/NavigationService";
|
||||
import { Routes } from "../../../navigation";
|
||||
import { useUser } from "../../../providers/UserDataProvider";
|
||||
|
||||
const MyMusic = () => {
|
||||
const { userProjects = [] } = useUser();
|
||||
const projects = Array.isArray(userProjects)
|
||||
? userProjects.slice(0, 3)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
label="Mes musiques"
|
||||
onPress={() => navigate(Routes.AllMyMusic)}
|
||||
>
|
||||
<View style={{ gap: 10 }}>
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<MusicCard key={index} />
|
||||
))}
|
||||
{Array.isArray(projects) &&
|
||||
projects.map((p) => (
|
||||
<MusicCard
|
||||
key={p.id}
|
||||
title={p?.title}
|
||||
subtitle={"MusicLand"}
|
||||
imageUri={p?.coverUrl || null}
|
||||
projectId={p?.id}
|
||||
likedBy={p?.likedBy || []}
|
||||
onPress={() => navigate(Routes.MusicDetails, { projectId: p.id })}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</CardContainer>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user