fix: link provider

This commit is contained in:
2025-11-05 10:15:50 +01:00
parent 91818e58ec
commit 8180e0b375
5 changed files with 196 additions and 36 deletions
+91
View File
@@ -33,6 +33,92 @@ const UniversalLinkProvider = ({ children }) => {
const [pendingNavigation, setPendingNavigation] = useState(null);
const lastUrlRef = useRef(null);
const handleLinkRedirect = async (queryParams = {}) => {
const schemeParam = queryParams?.scheme;
const fallbackParam = queryParams?.fallback;
const scheme =
typeof schemeParam === "string" && schemeParam.trim().length > 0
? schemeParam.trim()
: null;
const fallback =
typeof fallbackParam === "string" && fallbackParam.trim().length > 0
? fallbackParam.trim()
: null;
if (isWeb) {
if (scheme) {
let fallbackTimer = null;
if (fallback) {
fallbackTimer = window.setTimeout(() => {
try {
window.location.href = fallback;
} catch (error) {
console.error("link.redirect.fallback.error", error);
}
}, 1500);
}
try {
window.location.href = scheme;
} catch (error) {
console.error("link.redirect.scheme.error", error);
if (fallbackTimer) window.clearTimeout(fallbackTimer);
if (fallback) {
try {
window.location.href = fallback;
} catch (fallbackError) {
console.error("link.redirect.fallback.error", fallbackError);
}
}
}
return;
}
if (fallback) {
try {
window.location.href = fallback;
} catch (error) {
console.error("link.redirect.fallback.error", error);
}
}
return;
}
if (!scheme && fallback) {
try {
await Linking.openURL(fallback);
} catch (error) {
console.error("link.redirect.native.fallback.error", error);
}
return;
}
if (!scheme) return;
try {
const canOpen = await Linking.canOpenURL(scheme);
if (canOpen) {
await Linking.openURL(scheme);
} else if (fallback) {
await Linking.openURL(fallback);
} else {
await Linking.openURL(APP_STORE_FALLBACK_URL);
}
} catch (error) {
console.error("link.redirect.native.scheme.error", error);
try {
if (fallback) {
await Linking.openURL(fallback);
} else {
await Linking.openURL(APP_STORE_FALLBACK_URL);
}
} catch (fallbackError) {
console.error("link.redirect.native.fallback.error", fallbackError);
}
}
};
useEffect(() => {
const handleDeepLink = async (event) => {
// console.log("event", event);
@@ -127,6 +213,11 @@ const UniversalLinkProvider = ({ children }) => {
const normalizedPath = normalizePath(path);
if (!normalizedPath) return;
if (normalizedPath.toLowerCase() === "link") {
handleLinkRedirect(queryParams);
return;
}
const [resource, identifier] = normalizedPath.split("/").filter(Boolean);
if (!resource || !identifier) return;
+30 -22
View File
@@ -8,7 +8,7 @@ import React, {
useRef,
useState,
} from "react";
import { Image, Platform, Pressable, Share, Text, View } from "react-native";
import { Image, Platform, Pressable, Text, View } from "react-native";
import Carousel from "react-native-reanimated-carousel";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { icons, img } from "../../assets";
@@ -28,6 +28,10 @@ import {
LIKE_TARGET,
toggleProjectLike,
} from "../../utils/likes";
import {
createPlaybackSharePayload,
openShareSheet,
} from "../../utils/shareSheet";
import { SheetManager } from "react-native-actions-sheet";
import { Feather } from "@expo/vector-icons";
@@ -109,7 +113,13 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const projectTitle = typeof item?.title === "string" ? item.title.trim() : "";
const creatorName = item?.userName;
const creatorName = useMemo(() => {
if (owner?.displayName) return owner.displayName;
if (owner?.artistName) return owner.artistName;
if (owner?.userName) return owner.userName;
if (typeof item?.userName === "string") return item.userName;
return "";
}, [item?.userName, owner?.artistName, owner?.displayName, owner?.userName]);
const videoPlayer = useVideoPlayer(videoUrl || null, (p) => {
p.loop = false;
@@ -182,7 +192,23 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
return () => global.clearInterval(id);
}, [isActive, videoPlayer]);
// partage: géré via l'API native Share directement dans l'UI
const sharePayload = useMemo(() => {
if (!item?.id) return null;
return createPlaybackSharePayload({
projectId: item.id,
title: projectTitle || undefined,
artist: creatorName || undefined,
playbackUrl: videoUrl || undefined,
});
}, [creatorName, item?.id, projectTitle, videoUrl]);
const handleShare = useCallback(() => {
if (sharePayload) {
openShareSheet(sharePayload);
}
}, [sharePayload]);
// partage: via feuille unifiée (prend en charge QR)
return (
<View
style={{
@@ -358,25 +384,7 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
</Text>
)}
</Pressable>
<Pressable
onPress={async () => {
try {
const url = item?.playbackUrl || "";
const title = item?.title || "Partager";
const base = item?.title
? `Découvre « ${item.title} » sur MusicLand`
: `Découvre ce playback sur MusicLand`;
const message = url ? `${base}\n${url}` : base;
await Share.share(
Platform.select({
ios: url ? { url, message, title } : { message, title },
default: { message, title },
})
);
} catch (e) {}
}}
>
<Pressable onPress={handleShare}>
<Image source={icons.share} style={size({ size: 26 })} />
</Pressable>
<Pressable onPress={handleReport} style={{ alignItems: "center" }}>
@@ -9,7 +9,6 @@ import React, {
import {
Image,
Pressable,
Share,
StyleSheet,
Text,
View,
@@ -30,6 +29,10 @@ import {
LIKE_TARGET,
toggleProjectLike,
} from "../../../utils/likes";
import {
createPlaybackSharePayload,
openShareSheet,
} from "../../../utils/shareSheet";
import { SheetManager } from "react-native-actions-sheet";
import { Feather } from "@expo/vector-icons";
@@ -254,6 +257,30 @@ const PlaybackItem = ({
const descriptionText =
item?.description || item?.title || "Description chanson";
const creatorName = useMemo(() => {
if (owner?.displayName) return owner.displayName;
if (owner?.artistName) return owner.artistName;
if (owner?.userName) return owner.userName;
if (typeof item?.userName === "string") return item.userName;
return "";
}, [item?.userName, owner?.artistName, owner?.displayName, owner?.userName]);
const sharePayload = useMemo(() => {
if (!item?.id) return null;
return createPlaybackSharePayload({
projectId: item.id,
title: typeof item?.title === "string" ? item.title.trim() : undefined,
artist: creatorName || undefined,
playbackUrl: videoUrl || undefined,
});
}, [creatorName, item?.id, item?.title, videoUrl]);
const handleShare = useCallback(() => {
if (sharePayload) {
openShareSheet(sharePayload);
}
}, [sharePayload]);
const handleReport = useCallback(() => {
if (!item?.id) return;
SheetManager.show("Report", {
@@ -481,18 +508,7 @@ const PlaybackItem = ({
)}
</Pressable>
<Pressable
onPress={async () => {
try {
const url = item?.playbackUrl || "";
const title = item?.title || "Partager";
const base = item?.title
? `Découvre « ${item.title} » sur MusicLand`
: `Découvre ce playback sur MusicLand`;
const message = url ? `${base}\n${url}` : base;
await Share.share({ message, title, url });
} catch (_e) {}
}}
onPress={handleShare}
style={[styles.actionButton, styles.actionSpacing]}
>
<Image
+42
View File
@@ -135,6 +135,48 @@ export const createMusicSharePayload = ({ projectId, title, artist } = {}) => {
};
};
export const createPlaybackSharePayload = ({
projectId,
title,
artist,
playbackUrl,
} = {}) => {
if (!projectId) return null;
const baseUrl = trimTrailingSlash(resolveBaseShareUrl());
if (!baseUrl) return null;
const encodedProjectId = encodeURIComponent(projectId);
const sharePath = `playback/${encodedProjectId}`;
const shareUrl = `${baseUrl}/${sharePath}`;
const schemeDeepLink = createSchemeDeepLink(sharePath);
const qrUrl = createShareQrUrl({
sharePath,
fallbackUrl: playbackUrl || shareUrl,
});
let shareMessage = "Découvre ce playback sur MusicLand.";
if (title) {
shareMessage = `Découvre "${title}"`;
if (artist) {
shareMessage += ` par ${artist}`;
}
shareMessage += " sur MusicLand.";
} else if (artist) {
shareMessage = `Découvre ce playback de ${artist} sur MusicLand.`;
}
return {
heading: "Partager le playback",
shareTitle: title ? `${title} - MusicLand` : "MusicLand",
shareMessage,
url: shareUrl,
qrUrl,
copyUrl: shareUrl,
linkLabel: shareUrl,
appLink: schemeDeepLink,
};
};
export const openShareSheet = (payload = {}) => {
SheetManager.show("Share", {
payload: {
+4 -1
View File
@@ -1,8 +1,11 @@
{
"rewrites": [
{ "source": "/link", "destination": "/index.html" },
{ "source": "/link/:path*", "destination": "/index.html" },
{ "source": "/music/:musicId", "destination": "/index.html" },
{ "source": "/tasks/:taskId", "destination": "/index.html" },
{ "source": "/playback/:playbackId", "destination": "/index.html" }
{ "source": "/playback/:playbackId", "destination": "/index.html" },
{ "source": "/playbacks/:playbackId", "destination": "/index.html" }
],
"headers": [
{