share modal

This commit is contained in:
2025-10-03 13:36:18 +02:00
parent 8ea7f89c7b
commit dcc50506de
13 changed files with 377 additions and 54 deletions
+2 -1
View File
@@ -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}
>
<BlurView
intensity={Platform.OS === "ios" ? 20 : 10}
intensity={Platform.OS === "ios" || isWeb ? 20 : 10}
style={{
paddingTop: 36,
paddingHorizontal: 14,
+5 -5
View File
@@ -1,10 +1,10 @@
import { View, Text, Pressable, Image, Platform } from "react-native";
import React from "react";
import BorderGradient from "./BorderGradient/BorderGradient";
import { Palette, Style } from "../styles";
import { BlurView } from "expo-blur";
import React from "react";
import { Image, Pressable, Text, View } from "react-native";
import { Palette, Style } from "../styles";
import { FONT_FAMILY } from "../styles/Fonts";
import { size } from "../styles/Style";
import BorderGradient from "./BorderGradient/BorderGradient";
const BorderGradientButton = ({
title = "Jai déjà mes paroles",
@@ -45,7 +45,7 @@ const BorderGradientButton = ({
}}
>
<BlurView
intensity={Platform.OS === "ios" ? 40 : 10}
intensity={40}
tint={tint}
style={{
width: "100%",
+61
View File
@@ -0,0 +1,61 @@
import React, { useMemo } from "react";
import { View } from "react-native";
import Svg, { Rect } from "react-native-svg";
import { generateQrMatrix } from "../utils/generateQrMatrix";
const DEFAULT_SIZE = 220;
const QrCode = ({
value,
size = DEFAULT_SIZE,
color = "#FFFFFF",
backgroundColor = "transparent",
errorCorrectionLevel = "M",
}) => {
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 (
<View
style={{
width: size,
height: size,
backgroundColor,
}}
>
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
{matrix.map((row, rowIndex) => {
return row.map((isDark, colIndex) => {
if (!isDark) {
return null;
}
const key = `${rowIndex}-${colIndex}`;
return (
<Rect
key={key}
x={colIndex * cellSize}
y={rowIndex * cellSize}
width={cellSize}
height={cellSize}
fill={color}
/>
);
});
})}
</Svg>
</View>
);
};
export default QrCode;
+75 -31
View File
@@ -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) => (
<MaterialCommunityIcons name="qrcode-scan" {...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 (
<AppActionSheet id="Share">
<View style={styles.container}>
<View style={styles.card}>
<BlurView
style={[styles.card, styles.blurCard]}
tint="dark"
intensity={40}
experimentalBlurMethod="dimezisBlurView"
>
<Text style={styles.heading}>{heading}</Text>
<View style={styles.linkRow}>
<Text numberOfLines={1} style={styles.linkText}>
@@ -232,31 +261,39 @@ const ShareModal = ({ payload }) => {
</Pressable>
</View>
</View>
{copied ? (
<Text style={styles.copiedText}>Lien copie</Text>
) : null}
</View>
{copied ? <Text style={styles.copiedText}>Lien copié</Text> : null}
</BlurView>
<View style={styles.card}>
<BlurView
intensity={70}
experimentalBlurMethod="dimezisBlurView"
style={styles.card}
>
<Text style={styles.sectionHeading}>{sectionTitle}</Text>
<View style={styles.optionsList}>
{shareOptions.map((option) => {
{shareOptions.map((option, index) => {
const Icon = option?.Icon ?? Feather;
return (
<Pressable
key={option?.key}
onPress={() => handleShareOption(option)}
style={styles.optionRow}
<BlurView
key={option?.key || index}
style={styles.optionBlur}
intensity={40}
experimentalBlurMethod="dimezisBlurView"
>
<View style={styles.optionIconWrapper}>
<Icon size={18} color={Palette.white} />
</View>
<Text style={styles.optionLabel}>{option?.label}</Text>
</Pressable>
<Pressable
onPress={() => handleShareOption(option)}
style={styles.optionRow}
>
<View style={styles.optionIconWrapper}>
<Icon size={18} color={Palette.white} />
</View>
<Text style={styles.optionLabel}>{option?.label}</Text>
</Pressable>
</BlurView>
);
})}
</View>
</View>
</BlurView>
</View>
</AppActionSheet>
);
@@ -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;
+116
View File
@@ -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 (
<Portal>
<View style={StyleSheet.absoluteFill} pointerEvents="box-none">
<Pressable
onPress={onClose}
style={[StyleSheet.absoluteFill, styles.backdrop]}
/>
<View style={styles.centerWrapper} pointerEvents="box-none">
<BlurView intensity={blurIntensity} tint="dark" style={styles.modalCard}>
<Pressable onPress={onClose} style={styles.closeButton} hitSlop={10}>
<Feather name="x" size={20} color={Palette.white} />
</Pressable>
<View style={styles.content}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.subtitle}>
Partage ce code pour guider tes contacts directement vers MusicLand.
</Text>
<View style={styles.qrWrapper}>
<QrCode value={shareUrl} size={220} backgroundColor="transparent" />
</View>
<Text numberOfLines={1} ellipsizeMode="middle" style={styles.linkText}>
{shareUrl}
</Text>
</View>
</BlurView>
</View>
</View>
</Portal>
);
};
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;
@@ -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 (
<ShareQrModal
visible={!!visible}
url={url || musiclandShareUrl}
title={title || "Scannez le QR-code"}
onClose={handleClose}
/>
);
};
export default ShareQrModalContainer;