Aller au contenu principal

Accessibilité et retour haptique

Rendre les boutons compréhensibles par un lecteur d'écran, et faire vibrer le téléphone selon que la réponse est bonne ou mauvaise.

Objectifs de la séance​

  • comprendre ce qu'est un lecteur d'écran (TalkBack sur Android, VoiceOver sur iPhone) et pourquoi une application doit lui parler ;
  • utiliser accessibilityRole, accessibilityLabel, accessibilityState et accessibilityLiveRegion ;
  • installer une bibliothèque Expo avec npx expo install (ici expo-haptics) ;
  • déclencher une vibration différente selon le résultat, sans casser la version Web ;
  • distinguer une fonctionnalité indispensable d'un simple « bonus » dans la gestion des erreurs.

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. dans eas.json, citer le nom du profil qui produit un APK de test, et la valeur de buildType
  2. dans app.json, lire l'identifiant Android de l'application (android.package)
  3. expliquer pourquoi il a fallu recréer les variables Supabase chez EAS, alors qu'elles existent déjà dans .env.local
📌 Réponse attendue

Notions théoriques​

Qu'est-ce que l'accessibilité numérique ?​

Certains joueurs ne voient pas l'écran, ou le voient mal : ils utilisent un lecteur d'écran, un logiciel qui lit à voix haute ce qui est affiché et qui permet de commander l'application par des gestes. Il s'appelle TalkBack sur Android et VoiceOver sur iPhone. Pour qu'il soit utile, il doit savoir ce que chaque élément représente : un texte, un bouton, une case cochée...

Un composant React Native standard fournit déjà beaucoup d'informations. Mais notre Pressable (un bouton fabriqué sur mesure) est, pour le lecteur d'écran, une simple « zone cliquable » dont il ne connaît pas le rôle. On lui donne donc des propriétés d'accessibilité :

PropriétéRôleDans notre application
accessibilityRolela nature de l'élément ("button", "header", "link"...)"button" sur chaque réponse
accessibilityLabelle texte lu à voix haute (par défaut : le texte affiché)le texte de la réponse
accessibilityStatel'état : désactivé, sélectionné, coché...{ disabled, selected }
accessibilityLiveRegionune zone qui est lue automatiquement quand son contenu apparaît ou change (Android)le bloc de correction

Avec ces propriétés, TalkBack annonce par exemple : « Wireless Fidelity, bouton, sélectionné », puis, dès la réponse corrigée, lit automatiquement « Bonne réponse ! » suivi de l'explication.

Au-delà du code

L'accessibilité ne se limite pas aux propriétés. Elle demande aussi de ne pas transmettre une information uniquement par la couleur (le vert et le rouge d'une correction sont aussi écrits : « Bonne réponse ! » ou « Mauvaise réponse... »), d'avoir un contraste suffisant, et des zones à toucher assez grandes. C'est aussi une obligation légale pour de nombreux services publics et grandes organisations en France (référentiel RGAA).

Le retour haptique​

Un retour haptique est une réponse physique de l'appareil : une vibration. Elle confirme une action sans regarder l'écran, et aide aussi les personnes malvoyantes. La bibliothèque expo-haptics en propose plusieurs types ; nous utilisons les notifications, qui existent en trois variantes :

  • Haptics.NotificationFeedbackType.Success : vibration de réussite ;
  • Haptics.NotificationFeedbackType.Error : vibration d'échec ;
  • Haptics.NotificationFeedbackType.Warning : vibration d'avertissement.

La fonction Haptics.notificationAsync(type) est asynchrone : elle renvoie une promesse. Sur le Web, il n'y a pas de vibreur : on ignore donc l'appel (Platform.OS === "web").

npx expo install, et pas npm install​

Pour ajouter une bibliothèque Expo, on utilise npx expo install nom-de-la-bibliothèque. Cette commande choisit la version compatible avec votre SDK Expo (ici ~58.0.7 pour expo-haptics), alors que npm install prendrait la dernière version publiée, qui pourrait être incompatible.

Un « bonus » ne doit jamais bloquer l'application​

La vibration est un confort. Si elle échoue (appareil sans vibreur, vibrations désactivées par l'utilisateur), l'application doit continuer comme si de rien n'était. C'est pourquoi l'erreur est volontairement ignorée avec .catch(() => {}) : afficher un message d'erreur « La vibration a échoué » serait pire que de ne rien faire. À l'inverse, une erreur de chargement des questions est indispensable à signaler : sans questions, pas de jeu. Il faut savoir distinguer ces deux cas à chaque fois.


TP pour réfléchir et résoudre des problèmes​

Votre mission : rendre l'écran de partie compréhensible au lecteur d'écran, puis ajouter les vibrations. Ce dernier tutoriel se fait sur la branche feature/extensions. Les vibrations ne se ressentent que sur un téléphone (l'APK construit à la séance précédente ou Expo Go) ; sur le Web, vérifiez simplement que rien ne casse.

Étape 1 — Créer la branche du tutoriel​

Revenez sur main, mettez-la à jour, puis créez la branche du tutoriel :

git switch main
git pull

Bonne pratique - Repartir d'un main à jour

Une branche créée depuis un main périmé crée des conflits lors de la fusion. On met toujours main à jour avant de créer une branche.

Étape 2 — Installer expo-haptics​

Dans le terminal, à la racine du projet (arrêtez le serveur avec Ctrl + C) :

npx expo install expo-haptics

La ligne "expo-haptics": "~58.0.7" apparaît dans la section dependencies de package.json. Relancez ensuite npx expo start.


Bonne pratique - Une dépendance de plus, c'est un risque de plus

Chaque bibliothèque ajoutée est du code écrit par d'autres, à mettre à jour et à surveiller. N'installez que ce qui apporte une vraie valeur : ici, une bibliothèque officielle d'Expo, maintenue avec le SDK.

Étape 3 — Donner un rôle et un nom aux boutons de réponse​

Ouvrez src/components/BoutonReponse.tsx. Dans le Pressable, ajoutez les deux propriétés qui indiquent la nature de l'élément et le texte à lire. Complétez la ligne qui définit ce que le lecteur d'écran lit :


Bonne pratique - Un libellé qui a du sens hors contexte

Le lecteur d'écran lit chaque élément isolément, sans voir la mise en page. « Cliquez ici » ne veut rien dire ; le texte de la réponse, oui. Pour un bouton avec une icône seule, le accessibilityLabel est indispensable (« Fermer », « Actualiser »).

Étape 4 — Annoncer l'état du bouton​

Toujours dans BoutonReponse, ajoutez l'objet accessibilityState. Il indique si le bouton est désactivé (pendant la vérification) et s'il est sélectionné (réponse choisie, bonne ou mauvaise). Complétez la ligne selected :


Bonne pratique - Réutiliser l'état déjà calculé

La propriété etat ("normal", "choisie", "bonne", "mauvaise", "attenue") existe déjà : elle sert à choisir la couleur. On la réutilise pour l'accessibilité au lieu de recalculer : une seule source de vérité pour l'apparence et pour la parole.

Étape 5 — Faire lire la correction automatiquement​

Quand le serveur renvoie la correction, un bloc apparaît (« Bonne réponse ! », l'explication, le bouton suivant). Une personne qui utilise TalkBack ne le sait pas : le focus reste sur le bouton qu'elle vient de toucher. On déclare donc ce bloc comme une région vivante : son contenu est lu dès qu'il apparaît. Dans src/components/PartieEnCours.tsx, complétez la ligne qui ouvre ce bloc :

La valeur "polite" (« poli ») attend que le lecteur ait fini de parler avant de lire la correction ; "assertive" l'interromprait immédiatement, ce qui est réservé aux alertes importantes.


Bonne pratique - Annoncer les changements qui arrivent sans action de l'utilisateur

Un message d'erreur, une correction ou un résultat qui apparaissent après un chargement sont invisibles pour qui ne regarde pas l'écran. Une région vivante est la solution. Elle est prise en charge sous Android ; sur iPhone, d'autres mécanismes existent (consultez la documentation de React Native avant de généraliser).

Étape 6 — Écrire la fonction de vibration​

Créez src/lib/retourHaptique.ts. Elle reçoit « la réponse est-elle correcte ? » et déclenche la vibration adaptée. Complétez d'abord le mot qui désigne la plateforme à ignorer :

Puis complétez la ligne qui déclenche la vibration et ignore une éventuelle erreur :


Bonne pratique - Isoler les effets de bord dans un petit fichier

Les vibrations sont rangées dans src/lib/, comme le client Supabase : l'écran appelle vibrerSelonResultat(...) sans rien savoir d'expo-haptics. Si la bibliothèque change, un seul fichier est à modifier.

Étape 7 — Déclencher la vibration quand la correction arrive​

Dans src/components/PartieEnCours.tsx, ajoutez l'import en haut du fichier, après useChronometre :

import { vibrerSelonResultat } from "@/lib/retourHaptique";

Puis, dans la fonction choisirReponse, appelez la vibration juste après avoir enregistré la correction, et seulement si la requête n'a pas été annulée. Complétez la ligne :


Bonne pratique - Pas de vibration pour une réponse annulée

La condition !controleur.signal.aborted empêche de faire vibrer le téléphone pour une partie que le joueur a déjà quittée : c'est le même garde-fou que pour les dispatch.

Étape 8 — Tester puis enregistrer​

  1. Navigateur (touche w) : jouez une partie. Rien ne doit casser ni afficher d'erreur dans la console : l'appel est ignoré sur le Web.
  2. Téléphone avec Expo Go (si votre version d'Expo Go le permet) : répondez juste, puis faux. Vous ressentez une vibration de succès, puis d'échec.
  3. Lecteur d'écran : sur Android, activez TalkBack (Paramètres > Accessibilité), puis touchez une réponse. Il doit annoncer le texte, le mot « bouton » et l'état, puis lire automatiquement la correction. Désactivez-le ensuite (il modifie tous les gestes du téléphone).
  4. APK : pour retrouver les vibrations dans l'application autonome, reconstruisez-la avec npx eas-cli@latest build --platform android --profile preview (facultatif).

Vérifiez les types et les tests, puis enregistrez :

npm run verifier

Bonne pratique - Tester avec les outils de ses utilisateurs

Seul un test avec TalkBack ou VoiceOver montre si l'application est réellement utilisable sans l'écran. Même cinq minutes de test révèlent des oublis qu'aucune relecture du code ne trouve.

📌 Une solution

Test de mémorisation/compréhension​


À quoi sert accessibilityRole="button" sur un Pressable ?


Quel est le rôle de accessibilityLiveRegion="polite" sur le bloc de correction ?


Pourquoi installe-t-on expo-haptics avec npx expo install plutôt qu'avec npm install ?


Pourquoi l'erreur de Haptics.notificationAsync est-elle ignorée avec .catch(() => {}) ?


Pourquoi le code de retourHaptique.ts commence-t-il par un test sur Platform.OS === "web" ?


Pourquoi écrit-on « Bonne réponse ! » en plus d'afficher la réponse en vert ?



Exercices complémentaires​

Exercice A : un titre pour les lecteurs d'écran​

Dans src/components/CarteQuestion.tsx, ajoutez accessibilityRole="header" sur le Text qui affiche l'énoncé de la question. Avec TalkBack, que change-t-il dans l'annonce ? (Cherchez le texte « titre » ou « heading ».)

Exercice B : une autre vibration​

Ajoutez dans src/lib/retourHaptique.ts une fonction vibrerQuizTermine() qui déclenche un Haptics.NotificationFeedbackType.Success, puis appelez-la depuis l'écran de résultat quand le score est parfait. Pensez à la même protection (Platform.OS et .catch).


Ce qu'il faut retenir​

NotionRésumé
Lecteur d'écranTalkBack (Android), VoiceOver (iPhone) : lit l'écran à voix haute ; il faut lui décrire les éléments.
accessibilityRole / accessibilityLabelLa nature de l'élément et le texte lu à voix haute.
accessibilityStateL'état de l'élément : disabled, selected...
accessibilityLiveRegionZone lue automatiquement quand son contenu apparaît (Android).
Couleur seuleNe jamais transmettre une information uniquement par la couleur.
npx expo installInstalle la version d'une bibliothèque compatible avec le SDK Expo.
expo-hapticsnotificationAsync(Success / Error / Warning) fait vibrer le téléphone ; ignoré sur le Web.
Fonctionnalité « bonus »Son erreur est ignorée : elle ne doit jamais bloquer l'application.

Aperçu de la prochaine séance​

Jusqu'ici, les questions viennent de votre enseignant. Dans la dernière séance, ce sont les joueurs qui vont en proposer : un formulaire à plusieurs champs, validé dans l'application et dans la base de données, une table protégée par la RLS et des droits d'insertion limités à certaines colonnes. Vous terminerez par la Pull Request du dernier tutoriel.