diff --git a/app.json b/app.json index 2c501e8..360deb8 100644 --- a/app.json +++ b/app.json @@ -4,6 +4,7 @@ "slug": "musicland", "name": "MusicLand", "scheme": "musicland", + "deeplinks": ["musicland://"], "version": "0.0.1", "icon": "./assets/icon.png", "backgroundColor": "#0F0C14", @@ -25,7 +26,7 @@ "usesAppleSignIn": true, "requireFullScreen": true, "userInterfaceStyle": "dark", - "associatedDomains": ["applinks:minuit.starter"], + "associatedDomains": ["applinks:musicland-one.vercel.app"], "bundleIdentifier": "com.omedis.musicland.ai", "infoPlist": { "NSMicrophoneUsageDescription": "MusicLand uses the microphone to record your voice and audio so you can create and collaborate on music projects.", diff --git a/functions/assets/musicLandLogo.png b/functions/assets/musicLandLogo.png index 9687651..3a2e05d 100644 Binary files a/functions/assets/musicLandLogo.png and b/functions/assets/musicLandLogo.png differ diff --git a/src/components/modal/ShareModal.js b/src/components/modal/ShareModal.js index c9396f5..b476a8d 100644 --- a/src/components/modal/ShareModal.js +++ b/src/components/modal/ShareModal.js @@ -39,7 +39,9 @@ const ShareModal = ({ payload }) => { const url = payload?.url ?? musiclandShareUrl; const shareTitle = payload?.shareTitle ?? heading; const shareMessage = payload?.shareMessage ?? musiclandShareMessage; - const linkLabel = payload?.linkLabel ?? url; + const copyUrl = + payload?.copyUrl ?? payload?.qrUrl ?? payload?.url ?? musiclandShareUrl; + const linkLabel = payload?.linkLabel ?? copyUrl; const sectionTitle = payload?.sectionTitle ?? "Partager sur..."; const qrTitle = payload?.qrTitle ?? "Scannez le QR-code"; const qrUrl = payload?.qrUrl ?? musiclandShareUrl; @@ -140,13 +142,13 @@ const ShareModal = ({ payload }) => { } if (isWeb && typeof navigator !== "undefined" && navigator.clipboard) { - await navigator.clipboard.writeText(url); + await navigator.clipboard.writeText(copyUrl); } else { const Clipboard = await import("expo-clipboard"); if (typeof Clipboard?.setStringAsync === "function") { - await Clipboard.setStringAsync(url); + await Clipboard.setStringAsync(copyUrl); } else if (typeof Clipboard?.default?.setStringAsync === "function") { - await Clipboard.default.setStringAsync(url); + await Clipboard.default.setStringAsync(copyUrl); } } @@ -157,7 +159,7 @@ const ShareModal = ({ payload }) => { } catch (error) { console.error("share.copy.failed", error); } - }, [url]); + }, [copyUrl]); const handleSystemShare = useCallback(async () => { if (Platform.OS === "web" && typeof navigator !== "undefined") { diff --git a/src/providers/UniversalLinkProvider.js b/src/providers/UniversalLinkProvider.js index 49cc68d..98f7d43 100644 --- a/src/providers/UniversalLinkProvider.js +++ b/src/providers/UniversalLinkProvider.js @@ -102,19 +102,42 @@ const UniversalLinkProvider = ({ children }) => { }, [isFullyLoaded, pendingMusicId]); const handleParams = (path, _queryParams = {}) => { - if (typeof path === "string" && path.length > 0) { - const normalized = path.replace(/^\/+/, ""); - const [first, second] = normalized.split("/"); - if (first?.toLowerCase() === "music" && second) { + if ((typeof path === "string" && path.length > 0) || _queryParams?.music) { + const normalized = typeof path === "string" ? path.replace(/^\/+/, "") : ""; + const segments = normalized + .split("/") + .map((segment) => segment.trim()) + .filter(Boolean); + + const musicIndex = segments.findIndex( + (segment) => segment?.toLowerCase() === "music" + ); + + let musicIdentifier = null; + if (musicIndex !== -1 && segments[musicIndex + 1]) { + musicIdentifier = segments[musicIndex + 1]; + } else if (segments.length === 1 && segments[0]) { + musicIdentifier = segments[0]; + } else if (typeof _queryParams?.music === "string") { + musicIdentifier = _queryParams.music; + } else if (typeof _queryParams?.projectId === "string") { + musicIdentifier = _queryParams.projectId; + } + + if (musicIdentifier) { try { - setPendingMusicId(decodeURIComponent(second)); + setPendingMusicId(decodeURIComponent(musicIdentifier)); } catch (_) { - setPendingMusicId(second); + setPendingMusicId(musicIdentifier); } } } - cleanURL(); + if (isWeb) { + setTimeout(cleanURL, 1500); + } else { + cleanURL(); + } }; const cleanURL = () => { @@ -124,7 +147,8 @@ const UniversalLinkProvider = ({ children }) => { // console.log(url); url.search = ""; - window.history.replaceState({}, document.title, url.origin.toString()); + const next = `${url.origin}${url.pathname}${url.hash || ""}`; + window.history.replaceState({}, document.title, next); } }; diff --git a/src/utils/shareSheet.js b/src/utils/shareSheet.js index e7129ea..92e39c8 100644 --- a/src/utils/shareSheet.js +++ b/src/utils/shareSheet.js @@ -1,22 +1,65 @@ import { SheetManager } from "react-native-actions-sheet"; +import appJson from "../../app.json"; import { musiclandShareHeading, musiclandShareMessage, musiclandShareUrl, } from "../data"; +const APP_SCHEME = appJson?.expo?.scheme || "musicland"; +const APP_SCHEME_BASE = `${APP_SCHEME}://app`; + const defaultPayload = { heading: musiclandShareHeading, url: musiclandShareUrl, shareTitle: "MusicLand", shareMessage: musiclandShareMessage, qrUrl: musiclandShareUrl, + copyUrl: musiclandShareUrl, + linkLabel: musiclandShareUrl, + appLink: APP_SCHEME_BASE, }; const trimTrailingSlash = (value) => typeof value === "string" ? value.replace(/\/+$/, "") : ""; +const trimLeadingSlash = (value) => + typeof value === "string" ? value.replace(/^\/+/, "") : ""; + +const buildQueryString = (params = {}) => { + const entries = Object.entries(params).filter( + ([, value]) => value !== undefined && value !== null && value !== "" + ); + + if (!entries.length) { + return ""; + } + + const searchParams = new URLSearchParams(); + entries.forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((item) => { + searchParams.append(key, `${item}`); + }); + } else { + searchParams.append(key, `${value}`); + } + }); + + const query = searchParams.toString(); + return query ? `?${query}` : ""; +}; + +const createSchemeDeepLink = (path = "", params = {}) => { + const sanitizedPath = trimLeadingSlash(path); + const basePath = sanitizedPath + ? `${APP_SCHEME_BASE}/${sanitizedPath}` + : APP_SCHEME_BASE; + + return `${basePath}${buildQueryString(params)}`; +}; + const resolveBaseShareUrl = () => { if (typeof window !== "undefined" && window.location?.origin) { const origin = window.location.origin; @@ -32,7 +75,10 @@ export const createMusicSharePayload = ({ projectId, title, artist } = {}) => { const baseUrl = trimTrailingSlash(resolveBaseShareUrl()); if (!baseUrl) return null; - const shareUrl = `${baseUrl}/music/${projectId}`; + const encodedProjectId = encodeURIComponent(projectId); + const sharePath = `music/${encodedProjectId}`; + const shareUrl = `${baseUrl}/${sharePath}`; + const schemeDeepLink = createSchemeDeepLink(sharePath); let shareMessage = musiclandShareMessage; if (title) { @@ -49,7 +95,9 @@ export const createMusicSharePayload = ({ projectId, title, artist } = {}) => { shareMessage, url: shareUrl, qrUrl: shareUrl, + copyUrl: shareUrl, linkLabel: shareUrl, + appLink: schemeDeepLink, }; }; diff --git a/vercel.json b/vercel.json index 852d249..f732ca8 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,18 @@ { - "rewrites": [{ "source": "/tasks/:taskId", "destination": "/index.html" }] + "rewrites": [ + { "source": "/music/:musicId", "destination": "/index.html" }, + { "source": "/tasks/:taskId", "destination": "/index.html" }, + { "source": "/playback/:playbackId", "destination": "/index.html" } + ], + "headers": [ + { + "source": "/.well-known/apple-app-site-association", + "headers": [ + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + } + ] + } + ] } diff --git a/web/.well-known/apple-app-site-association b/web/.well-known/apple-app-site-association new file mode 100644 index 0000000..1903bdb --- /dev/null +++ b/web/.well-known/apple-app-site-association @@ -0,0 +1,18 @@ +{ + "applinks": { + "apps": [], + "details": [ + { + "appID": "W2QZ9CTMYJ.com.omedis.musicland.ai", + "paths": [ + "/music/*" + ] + } + ] + }, + "webcredentials": { + "apps": [ + "W2QZ9CTMYJ.com.omedis.musicland.ai" + ] + } +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..cc7522d --- /dev/null +++ b/web/index.html @@ -0,0 +1,122 @@ + + +
+ + + + + + + + +