diff --git a/functions/src/cover.js b/functions/src/cover.js index e591487..81b6b8a 100644 --- a/functions/src/cover.js +++ b/functions/src/cover.js @@ -32,40 +32,56 @@ const normalizeArtistName = (value) => { return trimmedValue || 'MusicLand' } -const getArtistFontSize = (artistName) => { - if (artistName.length <= 16) return 52 - if (artistName.length <= 24) return 44 +const normalizeSongTitle = (value) => { + if (typeof value !== 'string') { + return 'Sans titre' + } + const trimmedValue = value.trim() + return trimmedValue || 'Sans titre' +} + +const getTitleFontSize = (songTitle) => { + if (songTitle.length <= 16) return 38 + if (songTitle.length <= 24) return 32 + return 26 +} + +const getUsernameFontSize = (username) => { + if (username.length <= 16) return 52 + if (username.length <= 24) return 44 return 36 } -const buildTextOverlay = (artistName) => { - const safeArtistName = escapeSvgText(artistName) - const artistFontSize = getArtistFontSize(artistName) +const buildTextOverlay = ({ songTitle, username }) => { + const safeSongTitle = escapeSvgText(songTitle) + const safeUsername = escapeSvgText(username) + const titleFontSize = getTitleFontSize(songTitle) + const usernameFontSize = getUsernameFontSize(username) return Buffer.from(` ${safeArtistName} + >${safeSongTitle} PLAYBACKER + >${safeUsername} `) } @@ -90,26 +106,28 @@ const buildCircularMask = (size) => { } async function loadCoverIdentity(project) { - const artistName = normalizeArtistName(project.userName) + const username = normalizeArtistName(project.userName) + const songTitle = normalizeSongTitle(project.title) const userId = typeof project.userId === 'string' ? project.userId.trim() : '' if (!userId) { - return { artistName, profilePictureURL: null } + return { songTitle, username, profilePictureURL: project.coverCenterImageURL || null } } try { const userSnapshot = await refList.users.doc(userId).get() const userData = userSnapshot.exists ? userSnapshot.data() : null return { - artistName: normalizeArtistName(project.userName || userData?.userName), - profilePictureURL: userData?.profilePictureURL || null, + songTitle, + username: normalizeArtistName(project.userName || userData?.userName), + profilePictureURL: project.coverCenterImageURL || userData?.profilePictureURL || null, } } catch (error) { logger.warn('[Cover] Unable to load profile picture', { projectId: project.id, error: error.message, }) - return { artistName, profilePictureURL: null } + return { songTitle, username, profilePictureURL: project.coverCenterImageURL || null } } } @@ -169,9 +187,9 @@ async function prepareMusiclandCover(project) { throw new Error('Utilisateur du projet introuvable') } - const { artistName, profilePictureURL } = await loadCoverIdentity(project) + const { songTitle, username, profilePictureURL } = await loadCoverIdentity(project) const profileOverlay = await buildProfileOverlay(profilePictureURL) - const overlays = [{ input: buildTextOverlay(artistName), left: 0, top: 0 }] + const overlays = [{ input: buildTextOverlay({ songTitle, username }), left: 0, top: 0 }] if (profileOverlay) { overlays.unshift({ diff --git a/src/assets/UI/musiclandPlaybackCover.png b/src/assets/UI/musiclandPlaybackCover.png index a9ac203..96e389d 100644 Binary files a/src/assets/UI/musiclandPlaybackCover.png and b/src/assets/UI/musiclandPlaybackCover.png differ diff --git a/src/components/MusiclandPlaybackCover.js b/src/components/MusiclandPlaybackCover.js index ed81039..adbfc9b 100644 --- a/src/components/MusiclandPlaybackCover.js +++ b/src/components/MusiclandPlaybackCover.js @@ -5,13 +5,16 @@ import { img } from '../assets' import { FONT_FAMILY } from '../styles/Fonts' const MusiclandPlaybackCover = ({ - artistName, + songTitle, + username, profilePictureURL, coverUrl, style, }) => { - const displayName = - typeof artistName === 'string' && artistName.trim() ? artistName.trim() : 'MusicLand' + const displayTitle = + typeof songTitle === 'string' && songTitle.trim() ? songTitle.trim() : 'Sans titre' + const displayUsername = + typeof username === 'string' && username.trim() ? username.trim() : 'MusicLand' return ( @@ -36,11 +39,18 @@ const MusiclandPlaybackCover = ({ adjustsFontSizeToFit minimumFontScale={0.55} numberOfLines={1} - style={styles.artistName} + style={styles.songTitle} > - {displayName} + {displayTitle} + + + {displayUsername} - PLAYBACKER ) : null} @@ -63,19 +73,19 @@ const styles = StyleSheet.create({ top: '39.84375%', borderRadius: 999, }, - artistName: { + songTitle: { position: 'absolute', - top: '76.5%', - left: '16%', - width: '68%', + top: '15%', + left: '10%', + width: '80%', color: '#FFFFFF', fontFamily: FONT_FAMILY.InterRegular, - fontSize: 21, - lineHeight: 26, - letterSpacing: 1.2, + fontSize: 14, + lineHeight: 18, + letterSpacing: 0.8, textAlign: 'center', }, - playbacker: { + username: { position: 'absolute', bottom: '3%', left: '10%', diff --git a/src/screens/cover/ChooseCoverType.js b/src/screens/cover/ChooseCoverType.js index 6ef901c..7be3090 100644 --- a/src/screens/cover/ChooseCoverType.js +++ b/src/screens/cover/ChooseCoverType.js @@ -330,7 +330,8 @@ export const ChooseCoverType = () => { { const coverUrl = templateCover?.result || null const isPreparing = selectedProject?.coverStatus === 'GENERATING' const hasPreparationError = selectedProject?.coverStatus === 'ERROR' - const artistName = + const username = typeof selectedProject?.userName === 'string' && selectedProject.userName.trim() ? selectedProject.userName.trim() : currentUserData?.userName || 'MusicLand' + const songTitle = selectedProject?.title || 'Sans titre' const profilePictureURL = currentUserData?.profilePictureURL || null const centerImageURL = selectedProject?.coverCenterImageURL || profilePictureURL @@ -119,7 +120,8 @@ const PouchReady = () => { throw new Error('cover_project_identity_missing') } const coverBlob = await createMusiclandPlaybackCover({ - artistName, + songTitle, + username, profilePictureURL: centerImageURL, }) const storagePath = `users/${userId}/projects/${selectedProjectId}/cover-musicland-playbacker.png` @@ -170,13 +172,14 @@ const PouchReady = () => { setIsSaving(false) } }, [ - artistName, coverUrl, centerImageURL, selectedOption, selectedProject, selectedProjectId, + songTitle, updateProjectData, + username, ]) return ( @@ -193,7 +196,8 @@ const PouchReady = () => { /> { }) } -const getArtistFontSize = (artistName) => { - if (artistName.length <= 16) return 52 - if (artistName.length <= 24) return 44 +const getTitleFontSize = (songTitle) => { + if (songTitle.length <= 16) return 38 + if (songTitle.length <= 24) return 32 + return 26 +} + +const getUsernameFontSize = (username) => { + if (username.length <= 16) return 52 + if (username.length <= 24) return 44 return 36 } @@ -67,7 +73,7 @@ const drawProfilePicture = (context, profilePicture) => { context.restore() } -export const createMusiclandPlaybackCover = async ({ artistName, profilePictureURL }) => { +export const createMusiclandPlaybackCover = async ({ songTitle, username, profilePictureURL }) => { const templateAsset = Asset.fromModule(img.musiclandPlaybackCover) await templateAsset.downloadAsync() const assetUri = templateAsset.localUri || templateAsset.uri @@ -91,10 +97,12 @@ export const createMusiclandPlaybackCover = async ({ artistName, profilePictureU context.fillStyle = '#FFFFFF' context.textBaseline = 'middle' - context.font = `400 ${getArtistFontSize(artistName)}px Inter, Arial, sans-serif` - drawCenteredText(context, artistName, 824, getArtistFontSize(artistName), 3) - context.font = '400 54px Inter, Arial, sans-serif' - drawCenteredText(context, 'PLAYBACKER', 963, 54, 5) + const titleFontSize = getTitleFontSize(songTitle) + context.font = `400 ${titleFontSize}px Inter, Arial, sans-serif` + drawCenteredText(context, songTitle, 170, titleFontSize, 2) + const usernameFontSize = getUsernameFontSize(username) + context.font = `400 ${usernameFontSize}px Inter, Arial, sans-serif` + drawCenteredText(context, username, 963, usernameFontSize, 4) return new Promise((resolve, reject) => { canvas.toBlob((blob) => {