Chapitre 9.4 — Partage & presse-papiers
Où on en est : Drill garde tout pour lui. Un score de 980 sur Symfony mérite d’être montré — on branche le partage système et le presse-papiers.
⏱️ TL;DR —
Share.share()(API core RN) ouvre la share sheet Android : le score part vers WhatsApp, Telegram, SMS… sans que Drill connaisse aucune de ces apps. expo-clipboard pour copier (le lien du défi, ch. 5.5). Le message se construit danslib/share.ts(pur, testable) — texte + le deep linkdrill://pour les amis équipés.
🎯 Objectifs
- Utiliser la share sheet native (et comprendre ce qu’elle abstrait).
- Construire des messages de partage qui donnent envie (le mini-copywriting).
- Copier au presse-papiers avec confirmation UX.
- Panorama : partager une IMAGE (react-native-view-shot + expo-sharing).
La share sheet — l’API la plus rentable du téléphone
import { Share } from 'react-native'
await Share.share({
message: '🎯 980 pts sur le quiz Symfony de Drill — 9/10 en série ! Tu fais mieux ?',
})C’est tout. Android affiche SA feuille de partage avec les apps de l’utilisateur ; Drill ne sait ni ne demande où va le texte. Une permission ? Aucune — c’est l’utilisateur qui choisit la destination, le modèle de sécurité est dans le geste.
Le retour : Share.share() résout avec { action: 'sharedAction' | 'dismissedAction' } —
ne vous y fiez pas trop sur Android (sharedAction ne garantit pas un envoi réel) ; ne
déclenchez pas de récompense dessus.
Le message : lib/share.ts
Le texte de partage est du produit, pas de la technique — il se construit, se teste et s’itère :
// lib/share.ts — pur : testable, itérable
import type { SessionSummary } from './types'
export function buildShareMessage(s: SessionSummary): string {
const accuracy = Math.round((s.correctCount / s.total) * 100)
const flair = s.isNewRecord ? '🏆 NOUVEAU RECORD — ' : ''
const streakLine = s.bestStreak >= 3 ? ` (série de ${s.bestStreak} !)` : ''
return (
`${flair}${s.score} pts sur le quiz ${s.themeTitle} 🎯\n` +
`${s.correctCount}/${s.total} bonnes réponses${streakLine} — ${accuracy} %\n` +
`Tu fais mieux ? drill://quiz?theme=${s.theme}&count=${s.total}`
)
}// L'écran résultat
<Button
label="Partager mon score"
variant="secondary"
onPress={() => Share.share({ message: buildShareMessage(summary) })}
/>Les règles du message qui marche : le CHIFFRE d’abord (le score est le héros), un défi
(« tu fais mieux ? » — la raison de cliquer), et le deep link pour les amis équipés. Limite
honnête : drill:// n’est cliquable que si Drill est installé (ch. 5.5) — pour une app
distribuée publiquement, on mettrait l’URL Play Store en plus (P13).
Le presse-papiers
npx expo install expo-clipboardimport * as Clipboard from 'expo-clipboard'
import { useState } from 'react'
function CopyChallengeButton({ url }: { url: string }) {
const [copied, setCopied] = useState(false)
return (
<Button
label={copied ? '✓ Copié !' : 'Copier le lien du défi'}
variant="ghost"
onPress={async () => {
await Clipboard.setStringAsync(url)
feedback.tap()
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}}
/>
)
}L’UX à trois temps : action → confirmation visible (le label change — jamais de copie silencieuse) → retour à la normale. Android 13+ affiche aussi sa propre confirmation système ; la vôtre reste utile (les versions antérieures n’en ont pas, et le label est plus clair).
Panorama : partager une image de score
Le niveau au-dessus — la carte de score en IMAGE (façon Wordle) — s’assemble avec deux briques :
- react-native-view-shot : capture n’importe quelle View en PNG
(
captureRef(viewRef)→ URI de fichier). On dessine une jolie carte hors écran (score, thème, date, branding Drill), on la capture. - expo-sharing :
Sharing.shareAsync(uri)— la share sheet pour un fichier (l’API Share core ne gère bien que le texte sur Android).
Le pattern complet est un excellent exercice de P19 (Drill v2) : composant ScoreCard
stylé NativeWind → ref → capture → partage. Comptez 30 lignes — l’effet « wow » le moins
cher du développement mobile.
💡 Pour un dev Next.js — L’équivalent web (
navigator.share) existe mais reste capricieux desktop. En mobile natif, la share sheet est LE canal de croissance organique : chaque partage est une pub gratuite portée par la fierté de l’utilisateur. Dans vos apps perso, c’est aussi le pont le plus simple vers « sortir les données » (un CSV de dépenses partagé vers Drive — P19).
⚠️ Piège — Ne mettez RIEN de sensible dans un message de partage construit automatiquement : le texte part en clair vers l’app que l’utilisateur choisit. Pour Drill (un score), aucun enjeu — mais le réflexe de relire « qu’est-ce qui part exactement ? » avant chaque
Share.sharedoit devenir automatique.
✏️ Exercices
1. Testez buildShareMessage sur trois cas : session ordinaire, nouveau record avec streak, 0/10 lamentable. Le message du 0/10 donne-t-il envie de partager ? Ajustez.
✅ Solution
Le 0/10 en l’état est humiliant (« 0 pts — 0 % »). Deux écoles : l’autodérision
('💀 0/10 sur Symfony… je retourne réviser. Tu feras forcément mieux :') ou masquer le
bouton sous un seuil. La bonne réponse est produit, pas technique : l’autodérision partage
MIEUX que la gloire (cf. Wordle) — on garde le bouton avec un message adapté par palier
(< 30 %, 30-70 %, > 70 %, record). buildShareMessage devient un switch sur le palier :
toujours pur, toujours testable.
2. Le bouton « Copier le défi » doit produire une URL valide même si themeTitle contient espaces/accents. Où est le bug potentiel et comment le fixer proprement ?
✅ Solution
L’URL embarque s.theme (le slug — déjà URL-safe par contrat du manifest, ch. 7.3), pas
themeTitle. Si un doute subsiste : encodeURIComponent(theme) à la construction. La leçon :
les URLs se construisent depuis les identifiants machine (slugs), jamais depuis les libellés
humains — le duo title/slug du manifest existait précisément pour ça.
3. Implémentez la carte de score partageable (panorama) en version minimale : ScoreCard stylée + captureRef + shareAsync.
✅ Solution
const cardRef = useRef<View>(null)
<View ref={cardRef} collapsable={false} className="rounded-2xl bg-ink-900 p-6">
{/* score, thème, badges — votre plus belle carte */}
</View>
<Button label="Partager en image" onPress={async () => {
const uri = await captureRef(cardRef, { format: 'png', quality: 1 })
await Sharing.shareAsync(uri)
}} />(npx expo install react-native-view-shot expo-sharing.) Le collapsable={false} est le
piège Android : sans lui, la View peut être « optimisée » hors de l’arbre natif et la capture
échoue. La carte peut même être rendue hors écran (position absolute, left: -9999).
🧠 Quiz
1. Quelle permission exige la share sheet ?
Réponse
Aucune : l’utilisateur choisit lui-même la destination dans la feuille système — le consentement EST le geste. Drill ne sait pas où part le texte.
2. Pourquoi ne pas récompenser sur action: ‘sharedAction’ ?
Réponse
Sur Android, ce retour ne garantit pas un envoi réel (il peut signaler la simple sélection). Fiabilité insuffisante pour du gameplay/récompense.
3. Les trois ingrédients d’un message de partage efficace ?
Réponse
Le chiffre en héros (score), le défi qui motive le destinataire (« tu fais mieux ? »), et un lien actionnable (deep link, + URL store pour une app publique).
4. Quelle est l’UX minimale d’une copie presse-papiers ?
Réponse
Une confirmation visible immédiate (label « ✓ Copié ! » 2 s, léger haptic) — jamais de copie silencieuse, même si Android 13+ ajoute la sienne.
5. Quelles briques pour partager une IMAGE de score ?
Réponse
react-native-view-shot (captureRef d’une View stylée → PNG) + expo-sharing
(shareAsync du fichier) — et collapsable={false} sur la View capturée (piège Android).
👉 Chapitre suivant : 9.5 — Panorama des APIs natives — fichiers, caméra, capteurs : la carte pour vos futures apps.