diff --git a/App.js b/App.js
index 5d78051..4807119 100644
--- a/App.js
+++ b/App.js
@@ -37,6 +37,7 @@ import Providers from "./src/providers";
import { LogBox } from "react-native";
import CommentsBottomSheet from "./src/components/bottomsheets/CommentsBottomSheet";
import "./src/utils/Sheet";
+import ShareQrModalContainer from "./src/components/modal/ShareQrModalContainer";
console.disableYellowBox = true;
console.reportErrorsAsExceptions = false;
@@ -173,6 +174,7 @@ const App = () => {
+
{/* */}
diff --git a/src/components/AppActionSheet.js b/src/components/AppActionSheet.js
index e2de421..996c752 100644
--- a/src/components/AppActionSheet.js
+++ b/src/components/AppActionSheet.js
@@ -3,6 +3,7 @@ import React from "react";
import { Platform } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { isWeb } from "../hooks/useLayoutType";
import { gutters, Palette } from "../styles";
const AppActionSheet = ({ children, props }) => {
@@ -19,7 +20,7 @@ const AppActionSheet = ({ children, props }) => {
{...props}
>
{
+ const { matrix } = useMemo(() => {
+ return generateQrMatrix({ value, errorCorrectionLevel });
+ }, [value, errorCorrectionLevel]);
+
+ const cellSize = useMemo(() => {
+ if (!matrix || !matrix.length) {
+ return 0;
+ }
+ return size / matrix.length;
+ }, [matrix, size]);
+
+ return (
+
+
+
+ );
+};
+
+export default QrCode;
+
diff --git a/src/components/modal/ShareModal.js b/src/components/modal/ShareModal.js
index e85befa..3f00bbd 100644
--- a/src/components/modal/ShareModal.js
+++ b/src/components/modal/ShareModal.js
@@ -1,3 +1,8 @@
+import {
+ Feather,
+ FontAwesome,
+ MaterialCommunityIcons,
+} from "@expo/vector-icons";
import React, {
useCallback,
useEffect,
@@ -15,21 +20,18 @@ import {
View,
} from "react-native";
import { SheetManager } from "react-native-actions-sheet";
-import {
- Feather,
- FontAwesome,
- MaterialCommunityIcons,
-} from "@expo/vector-icons";
-import AppActionSheet from "../AppActionSheet";
-import { Palette } from "../../styles";
-import { FONT_FAMILY } from "../../styles/Fonts";
-import { isWeb } from "../../hooks/useLayoutType";
+import { BlurView } from "expo-blur";
+import { setGlobal } from "reactn";
import {
musiclandShareHeading,
musiclandShareMessage,
musiclandShareUrl,
} from "../../data";
+import { isWeb } from "../../hooks/useLayoutType";
+import { Palette } from "../../styles";
+import { FONT_FAMILY } from "../../styles/Fonts";
+import AppActionSheet from "../AppActionSheet";
const ShareModal = ({ payload }) => {
const heading = payload?.heading ?? musiclandShareHeading;
@@ -38,9 +40,12 @@ const ShareModal = ({ payload }) => {
const shareMessage = payload?.shareMessage ?? musiclandShareMessage;
const linkLabel = payload?.linkLabel ?? url;
const sectionTitle = payload?.sectionTitle ?? "Partager sur...";
+ const qrTitle = payload?.qrTitle ?? "Scannez le QR-code";
+ const qrUrl = payload?.qrUrl ?? musiclandShareUrl;
const [copied, setCopied] = useState(false);
const copyTimeout = useRef(null);
+ const qrOpenTimeout = useRef(null);
const closeSheet = useCallback(() => {
SheetManager.hide("Share");
@@ -61,6 +66,9 @@ const ShareModal = ({ payload }) => {
if (copyTimeout.current) {
clearTimeout(copyTimeout.current);
}
+ if (qrOpenTimeout.current) {
+ clearTimeout(qrOpenTimeout.current);
+ }
};
}, []);
@@ -123,6 +131,22 @@ const ShareModal = ({ payload }) => {
const handleShareOption = useCallback(
async (option) => {
+ if (option?.type === "qr") {
+ const qrTitleValue = qrTitle;
+
+ closeSheet();
+ qrOpenTimeout.current = setTimeout(() => {
+ setGlobal({
+ shareQrModal: {
+ visible: true,
+ url: qrUrl,
+ title: qrTitleValue,
+ },
+ });
+ }, 200);
+ return;
+ }
+
const target = option?.target;
if (!target) {
@@ -151,7 +175,7 @@ const ShareModal = ({ payload }) => {
await handleSystemShare();
closeSheet();
},
- [closeSheet, handleSystemShare]
+ [closeSheet, handleSystemShare, qrTitle, qrUrl]
);
const onSystemShare = useCallback(async () => {
@@ -193,11 +217,11 @@ const ShareModal = ({ payload }) => {
},
{
key: "qr",
+ type: "qr",
label: "Partager par QR-code",
Icon: (props) => (
),
- target: `https://api.qrserver.com/v1/create-qr-code/?size=512x512&data=${encodedUrl}`,
},
];
}, [encodedShareText, encodedUrl, payload?.options, shareTitle]);
@@ -205,7 +229,12 @@ const ShareModal = ({ payload }) => {
return (
-
+
{heading}
@@ -232,31 +261,39 @@ const ShareModal = ({ payload }) => {
- {copied ? (
- Lien copie
- ) : null}
-
+ {copied ? Lien copié : null}
+
-
+
{sectionTitle}
- {shareOptions.map((option) => {
+ {shareOptions.map((option, index) => {
const Icon = option?.Icon ?? Feather;
return (
- handleShareOption(option)}
- style={styles.optionRow}
+
-
-
-
- {option?.label}
-
+ handleShareOption(option)}
+ style={styles.optionRow}
+ >
+
+
+
+ {option?.label}
+
+
);
})}
-
+
);
@@ -267,7 +304,8 @@ const styles = StyleSheet.create({
gap: 24,
},
card: {
- backgroundColor: Palette.glass,
+ // backgroundColor: Palette.glass,
+ overflow: "hidden",
borderRadius: 24,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.08)",
@@ -275,6 +313,9 @@ const styles = StyleSheet.create({
paddingVertical: 18,
gap: 16,
},
+ blurCard: {
+ overflow: "hidden",
+ },
heading: {
fontSize: 20,
color: Palette.white,
@@ -325,11 +366,15 @@ const styles = StyleSheet.create({
optionsList: {
gap: 12,
},
+ optionBlur: {
+ borderRadius: 18,
+ overflow: "hidden",
+ },
optionRow: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "rgba(255, 255, 255, 0.04)",
- borderRadius: 18,
+ // borderRadius: 18,
paddingHorizontal: 14,
paddingVertical: 14,
gap: 14,
@@ -351,4 +396,3 @@ const styles = StyleSheet.create({
});
export default ShareModal;
-
diff --git a/src/components/modal/ShareQrModal.js b/src/components/modal/ShareQrModal.js
new file mode 100644
index 0000000..c73a5f8
--- /dev/null
+++ b/src/components/modal/ShareQrModal.js
@@ -0,0 +1,116 @@
+import React, { useMemo } from "react";
+import { BlurView } from "expo-blur";
+import { Platform, Pressable, StyleSheet, Text, View } from "react-native";
+import { Feather } from "@expo/vector-icons";
+import { Portal } from "@gorhom/portal";
+
+import QrCode from "../QrCode";
+import { Palette } from "../../styles";
+import { FONT_FAMILY } from "../../styles/Fonts";
+
+const ShareQrModal = ({ visible, onClose, url, title = "Scannez le QR-code" }) => {
+ const shareUrl = useMemo(() => url, [url]);
+
+ if (!visible) {
+ return null;
+ }
+
+ const blurIntensity = Platform.select({ ios: 40, default: 20 });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {title}
+
+ Partage ce code pour guider tes contacts directement vers MusicLand.
+
+
+
+
+
+
+
+ {shareUrl}
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ backdrop: {
+ backgroundColor: "rgba(0, 0, 0, 0.65)",
+ },
+ centerWrapper: {
+ flex: 1,
+ alignItems: "center",
+ justifyContent: "center",
+ padding: 24,
+ },
+ modalCard: {
+ width: 360,
+ maxWidth: "90%",
+ borderRadius: 28,
+ overflow: "hidden",
+ paddingHorizontal: 24,
+ paddingVertical: 32,
+ backgroundColor: Palette.glass,
+ },
+ closeButton: {
+ position: "absolute",
+ top: 18,
+ right: 18,
+ width: 36,
+ height: 36,
+ borderRadius: 18,
+ alignItems: "center",
+ justifyContent: "center",
+ backgroundColor: "rgba(255, 255, 255, 0.12)",
+ },
+ content: {
+ alignItems: "center",
+ gap: 18,
+ },
+ title: {
+ fontSize: 20,
+ color: Palette.white,
+ fontFamily: FONT_FAMILY.HelveticaNeueMedium,
+ },
+ subtitle: {
+ fontSize: 14,
+ color: Palette.white,
+ opacity: 0.8,
+ textAlign: "center",
+ fontFamily: FONT_FAMILY.InterRegular,
+ paddingHorizontal: 8,
+ },
+ qrWrapper: {
+ padding: 16,
+ borderRadius: 24,
+ backgroundColor: "rgba(255, 255, 255, 0.08)",
+ },
+ linkText: {
+ fontSize: 13,
+ color: Palette.white,
+ opacity: 0.9,
+ textAlign: "center",
+ fontFamily: FONT_FAMILY.InterMedium,
+ },
+});
+
+export default ShareQrModal;
diff --git a/src/components/modal/ShareQrModalContainer.js b/src/components/modal/ShareQrModalContainer.js
new file mode 100644
index 0000000..839515b
--- /dev/null
+++ b/src/components/modal/ShareQrModalContainer.js
@@ -0,0 +1,30 @@
+import React from "react";
+import { useGlobal } from "reactn";
+
+import { musiclandShareUrl } from "../../data";
+import ShareQrModal from "./ShareQrModal";
+
+const ShareQrModalContainer = () => {
+ const [shareQrModal, setShareQrModal] = useGlobal("shareQrModal");
+
+ const { visible, url, title } = shareQrModal || {};
+
+ const handleClose = () => {
+ setShareQrModal({
+ visible: false,
+ url: null,
+ title: "",
+ });
+ };
+
+ return (
+
+ );
+};
+
+export default ShareQrModalContainer;
diff --git a/src/config/initialGlobalState.js b/src/config/initialGlobalState.js
index 62a8275..1376e26 100644
--- a/src/config/initialGlobalState.js
+++ b/src/config/initialGlobalState.js
@@ -8,6 +8,12 @@ export default {
webBackgroundImg: null,
webLayoutMode: "default",
+ shareQrModal: {
+ visible: false,
+ url: null,
+ title: "",
+ },
+
_config: {
colors: {
primary: Palette.primary,
diff --git a/src/screens/HitParade/HitParade.js b/src/screens/HitParade/HitParade.js
index 88b7b63..691cb23 100644
--- a/src/screens/HitParade/HitParade.js
+++ b/src/screens/HitParade/HitParade.js
@@ -187,7 +187,7 @@ const HitParade = () => {
backgroundImg={isWeb ? background.libraryBgWeb : background.hitParadeBG}
headerType="NONE"
width="100%"
- maxWidth={null}
+ maxWidth={800}
blurIntensity={isWeb ? 2 : 0}
>
diff --git a/src/screens/Library/components/MyMusic.js b/src/screens/Library/components/MyMusic.js
index 91cf9b4..ddc1867 100644
--- a/src/screens/Library/components/MyMusic.js
+++ b/src/screens/Library/components/MyMusic.js
@@ -1,18 +1,23 @@
import React, { useState } from "react";
import { View } from "react-native";
+import { SheetManager } from "react-native-actions-sheet";
+import { useGlobal } from "reactn";
+import MoreMenu from "../../../components/MoreMenu";
+import { projectsRef } from "../../../config/firebase";
import { Routes } from "../../../navigation";
import { navigate } from "../../../navigation/NavigationService";
import { useUser } from "../../../providers/UserDataProvider";
import CardContainer from "./CardContainer";
import MusicCard from "./MusicCard";
-import MoreMenu from "../../../components/MoreMenu";
-
const MyMusic = () => {
const { userProjects = [] } = useUser();
const [menuPosition, setMenuPosition] = useState(null);
const [showMenu, setShowMenu] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState(null);
const projects = Array.isArray(userProjects) ? userProjects.slice(0, 3) : [];
+
+ const [, setTooltip] = useGlobal("_tooltip");
+
return (
{
setSelectedProjectId(p.id);
setMenuPosition(posTop);
setShowMenu(
- (prev) =>
- !prev || posTop?.top !== (menuPosition?.top ?? null),
+ (prev) => !prev || posTop?.top !== (menuPosition?.top ?? null)
);
}}
/>
@@ -52,6 +56,34 @@ const MyMusic = () => {
onClose={() => setShowMenu(false)}
inPlaylist={false}
projectId={selectedProjectId}
+ extraItems={[
+ {
+ label: "Supprimer",
+ onPress: () =>
+ SheetManager.show("Delete", {
+ payload: {
+ title: "Supprimer le projet",
+ message:
+ "Cette action supprimera définitivement ce projet.",
+ onConfirm: async () => {
+ try {
+ if (!selectedProjectId) return;
+ await projectsRef.doc(selectedProjectId).delete();
+ setTooltip({
+ type: "success",
+ text: "Projet supprimé",
+ });
+ } catch (e) {
+ setTooltip({
+ type: "error",
+ text: e?.message || "Suppression impossible",
+ });
+ }
+ },
+ },
+ }),
+ },
+ ]}
/>
diff --git a/src/screens/Profile/components/EditInput.js b/src/screens/Profile/components/EditInput.js
index b0cd1a3..c9d5dfc 100644
--- a/src/screens/Profile/components/EditInput.js
+++ b/src/screens/Profile/components/EditInput.js
@@ -1,16 +1,10 @@
-import {
- View,
- Text,
- TextInput,
- Image,
- Pressable,
- Platform,
-} from "react-native";
+import { BlurView } from "expo-blur";
import React, { useState } from "react";
+import { Platform, Pressable, Text, TextInput, View } from "react-native";
+import EyeSlashSVG from "../../../assets/UI/EyeSlashSVG";
+import EyeSVG from "../../../assets/UI/EyeSVG";
import { Palette, Style } from "../../../styles";
import { FONT_FAMILY } from "../../../styles/Fonts";
-import { BlurView } from "expo-blur";
-import { icons } from "../../../assets";
const EditInput = ({
label = "",
@@ -79,7 +73,7 @@ const EditInput = ({
/>
{type === "password" && (
setShowPassword(!showPassword)}>
-
+ {showPassword ? : }
)}
diff --git a/src/utils/generateQrMatrix.js b/src/utils/generateQrMatrix.js
new file mode 100644
index 0000000..92e4ec1
--- /dev/null
+++ b/src/utils/generateQrMatrix.js
@@ -0,0 +1,37 @@
+import QRCode from "qrcode-terminal/vendor/QRCode";
+import QRErrorCorrectLevel from "qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel";
+
+const getErrorLevel = (level) => {
+ if (typeof level === "number") {
+ return level;
+ }
+
+ const normalized = String(level || "M").toUpperCase();
+ return QRErrorCorrectLevel?.[normalized] ?? QRErrorCorrectLevel.M;
+};
+
+export const generateQrMatrix = ({
+ value,
+ errorCorrectionLevel = "M",
+}) => {
+ if (!value) {
+ throw new Error("generateQrMatrix requires a value");
+ }
+
+ const qr = new QRCode(-1, getErrorLevel(errorCorrectionLevel));
+ qr.addData(value);
+ qr.make();
+
+ const size = qr.getModuleCount();
+ const matrix = new Array(size);
+
+ for (let row = 0; row < size; row += 1) {
+ matrix[row] = new Array(size);
+ for (let col = 0; col < size; col += 1) {
+ matrix[row][col] = qr.isDark(row, col);
+ }
+ }
+
+ return { matrix, size };
+};
+
diff --git a/src/utils/shareSheet.js b/src/utils/shareSheet.js
index 5095f55..d8ec1b1 100644
--- a/src/utils/shareSheet.js
+++ b/src/utils/shareSheet.js
@@ -11,6 +11,7 @@ const defaultPayload = {
url: musiclandShareUrl,
shareTitle: "MusicLand",
shareMessage: musiclandShareMessage,
+ qrUrl: musiclandShareUrl,
};
export const openShareSheet = (payload = {}) => {
@@ -21,4 +22,3 @@ export const openShareSheet = (payload = {}) => {
},
});
};
-