Stack et structure
L'application mobile est l'interface citoyenne de BlockLucky sur iOS et Android. Elle couvre le parcours du citoyen : création de compte et connexion, vérification d'âge, achat et cadeau du ticket du grand tirage, salle d'attente et tirage en direct, tickets à gratter et boutique des commerçants, parrainage, profil, sécurité et RGPD. Elle n'a ni dashboard Mairie ni espace partenaire, qui n'existent que sur le frontend web. Comme le web, elle ne gère aucune clé : le portefeuille de chaque citoyen est créé et utilisé par le backend (voir Portefeuilles intégrés).
Le code se trouve dans mobile-app/. Il est écrit en TypeScript avec Expo et React Native. L'interface suit la maquette Figma de l'application : police Inter, palette jaune « soleil » et encre bleu nuit, cartes arrondies et icônes SVG (voir Thème et composants).
Stack technique
Les versions viennent de mobile-app/package.json. Contrairement au web, la plupart sont des plages (^, ~) ; les versions exactes sont figées par package-lock.json.
| Rôle | Paquet | Version demandée |
|---|---|---|
| Plateforme | expo |
^58.0.22 |
| Moteur natif | react-native |
0.88.1 |
| Bibliothèque UI | react |
19.3.0 |
| Navigation | @react-navigation/native, native-stack, bottom-tabs |
^7.5.1, ^7.21.0, ^7.21.0 |
| État et appels API | @reduxjs/toolkit, react-redux |
^2.11.2, ^9.2.0 |
| Temps réel | socket.io-client |
^4.8.1 |
| Police Inter | @expo-google-fonts/inter |
^0.4.2 |
| Connexion Google | expo-auth-session, expo-web-browser |
~58.0.18, ~58.0.8 |
| Sons du grand tirage | expo-audio |
~58.0.12 |
| Photo de la pièce d'identité | expo-image-picker |
~58.0.10 |
| Stockage sécurisé | expo-secure-store |
~58.0.2 |
| Presse-papiers | expo-clipboard |
~58.0.4 |
| Zones sûres | react-native-safe-area-context |
~5.10.1 |
| Icônes SVG | react-native-svg |
15.16.0 |
| Cible web d'Expo | react-dom, react-native-web |
19.3.0, ^0.21.4 |
| Langage | typescript |
~6.0.3 |
| Lint | eslint 8, eslint-config-universe |
^8.57.1, ^12.1.0 |
Node.js 22.12 ou plus est requis. La CI utilise la version du fichier .nvmrc à la racine.
Dépendances installées mais inutilisées
Plusieurs paquets figurent dans package.json sans être importés dans src/ :
| Paquet | Situation |
|---|---|
axios |
api.client.ts crée un client axios avec des nouvelles tentatives, mais seul API_BASE_URL est importé ailleurs ; toutes les requêtes passent par fetch via RTK Query (voir Résilience réseau) |
expo-notifications |
Aucun import : pas de notification push native (voir Temps réel et notifications) |
expo-crypto |
Aucun import |
lottie-react-native |
Aucun import ; les trois animations de assets/animations ne sont pas affichées, la roue du tirage est animée avec Animated de React Native |
react-native-gesture-handler |
Aucun import ; la carte à gratter utilise PanResponder de React Native |
Ces dépendances alourdissent le binaire sans rien apporter. Elles sont à retirer ou à brancher. Les fichiers assets/fonts/Inter-*.ttf ne sont plus lus non plus : la police vient du paquet @expo-google-fonts/inter.
Arborescence
mobile-app/
├── __tests__/ tests Jest (sans exécuteur installé)
├── assets/
│ ├── animations/ confetti, loading-spinner, roulette-spin (Lottie, inutilisés)
│ ├── fonts/ Inter Regular, Medium, Bold (non lues)
│ ├── icons/ 42 icônes SVG de la maquette
│ ├── images/ icon, adaptive-icon, splash, favicon
│ └── sounds/ roue-tirage.mp3 et chrono.mp3, fournis par l'équipe
├── src/
│ ├── App.tsx point d'entrée, chargement de la police Inter
│ ├── navigation/ RootNavigator, AuthStack, MainTabs, hooks typés
│ ├── content/ legal.ts : textes des six documents légaux, copie du fichier du web
│ ├── screens/ 32 écrans : auth, lottery, marketplace, settings, sponsors
│ ├── components/
│ │ ├── auth/ formulaires d'authentification, bouton Google
│ │ ├── draw/ roue, salle d'attente, discussion, résultats
│ │ ├── lottery/ carte d'échéance, paliers, offres à gratter, formats
│ │ ├── marketplace/ zone à gratter, cartes d'offres et de codes
│ │ ├── profile/ identité, parrainage, résumé, listes de réglages
│ │ ├── settings/ cartes de l'accueil des paramètres, nom affiché, enregistrement des préférences
│ │ ├── ui/ composants de base et icônes SVG
│ │ └── web3/ IntegratedWalletCard, IntegratedWalletButton, ExplorerLink (inutilisés)
│ ├── hooks/ useAuth, useCitizenWallet, useSocket, usePoints, usePushNotifications, useAppTheme, useSound
│ ├── services/ api.client, secureStorage, socket.client, sentry.client
│ ├── store/ store Redux, RTK Query (api/) et slices
│ ├── theme/ ThemeProvider, colors, spacing, typography
│ ├── types/ types de l'API, de la navigation et de l'utilisateur
│ └── utils/ date, formatters, validators
├── app.json configuration Expo
├── eas.json profils EAS Build
├── babel.config.js preset babel-preset-expo
├── metro.config.js configuration Metro par défaut d'Expo
└── .env.examplePoint d'entrée
package.json déclare "main": "src/App.tsx". mobile-app/src/App.tsx appelle registerRootComponent(App). Avant tout rendu, App charge cinq graisses d'Inter avec useFonts (Inter_400Regular, Inter_500Medium, Inter_600SemiBold, Inter_700Bold, Inter_700Bold_Italic). Tant qu'elles ne sont pas prêtes, il affiche une vue vide de couleur #F6F7FA. Il monte ensuite, dans cet ordre :
- le
ProviderRedux avec le store unique destore/index.ts; SafeAreaProvider;ThemeProvider(thème, langue, dimensions d'écran) ;AppShell, qui choisit le style de la barre d'état selon le thème et rendRootNavigator.
Au montage, App appelle sentryClient.init(). Ce « client Sentry » (sentry.client.ts) n'est pas le SDK Sentry : il garde en mémoire les 20 derniers rapports d'erreur et n'envoie rien à aucun serveur, même si EXPO_PUBLIC_SENTRY_DSN est renseigné.
flowchart TD A["App.tsx : useFonts Inter"] --> R["Provider Redux"] R --> S["SafeAreaProvider"] S --> T["ThemeProvider"] T --> SH["AppShell : StatusBar"] SH --> RN["RootNavigator"] RN -->|"non connecté"| AU["AuthStack"] RN -->|"connecté"| MT["MainTabs et écrans empilés"] RN --> BS["Fenêtres globales ResultDialog"]
Organisation du code applicatif
| Dossier | Contenu | Page détaillée |
|---|---|---|
src/navigation |
Navigateurs, hooks de navigation typés et fenêtres globales | Navigation et écrans |
src/store |
baseApi et trois fichiers d'endpoints, slices auth, lottery, ui |
État et appels API |
src/services |
Stockage sécurisé, client Socket.IO, client axios inutilisé, faux client Sentry | Authentification, Temps réel |
src/hooks |
Hooks métier | Portefeuille et loterie |
src/theme, src/components/ui |
Jetons de couleur, espacements, typographie, composants de base et icônes | Thème et composants |
Les imports sont relatifs (../../hooks/useAuth) : il n'y a pas d'alias @/ comme sur le web.
Configuration
mobile-app/.env.example liste huit variables. Expo intègre au bundle toute variable préfixée EXPO_PUBLIC_ au moment du démarrage de Metro ou du build.
| Variable | Valeur d'exemple | Lue par |
|---|---|---|
EXPO_PUBLIC_API_URL |
http://localhost:2053/api |
api.client.ts, donc baseApi |
EXPO_PUBLIC_WS_URL |
http://localhost:2053 |
socket.client.ts |
EXPO_PUBLIC_SENTRY_DSN |
vide | sentry.client.ts, qui ne l'utilise que pour écrire une trace locale |
EXPO_PUBLIC_WEB3AUTH_CLIENT_ID |
blocklucky-etherbay-client |
aucun fichier : vestige d'une intégration Web3Auth abandonnée |
EXPO_PUBLIC_HELP_CENTER_URL |
vide | HelpSupportScreen.tsx : adresse du futur centre d'aide ; vide, la carte affiche « Bientôt » |
Trois autres variables, listées vides, sont lues par SocialAuthButtons.tsx :
| Variable | Rôle |
|---|---|
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID |
Identifiant client OAuth Google utilisé sur iOS |
EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID |
Identifiant client OAuth Google utilisé sur Android |
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID |
Identifiant client OAuth Google utilisé sur la cible web |
Sans la variable de la plateforme courante, le bouton « Continuer avec Google » reste affiché mais désactivé, avec le message « Connexion Google non configurée sur cette application. ». Les identifiants doivent aussi figurer dans GOOGLE_CLIENT_IDS côté backend (voir Authentification et stockage sécurisé).
Important
localhost désigne le téléphone lui-même. Sur un appareil physique ou un émulateur Android, l'URL doit pointer vers l'adresse IP de la machine qui fait tourner le backend. La procédure est dans Tester sur un téléphone.
L'ancien README mentionne EXPO_PUBLIC_CHAIN_ID=11155111 : aucune ligne du code ne lit cette variable.
Scripts npm
| Commande | Effet |
|---|---|
npm start |
expo start : serveur Metro et QR code |
npm run android |
expo start --android |
npm run ios |
expo start --ios |
npm run web |
expo start --web ; react-dom et react-native-web sont désormais installés, mais la cible web n'est vérifiée ni par la CI ni par un test |
npm run typecheck |
tsc --noEmit |
npm run lint |
ESLint avec universe/native et universe/shared/typescript-analysis |
Il n'existe pas de script test et Jest n'est pas installé, bien que le dossier __tests__ contienne trois fichiers et que l'ancien README cite npm test (voir Builds et publication).
Code mort à connaître
Le hook useGaslessWeb3.ts, qui présentait les achats comme sponsorisés par la plateforme, et le composant WalletConnectButton.tsx, qui invitait à « connecter » un portefeuille externe, ont été supprimés. La refonte de l'interface a aussi supprimé LoginScreen.tsx, SettingsStack.tsx et les composants ChatBox, CountdownTimer, LeaderboardList, POAPBadge, RouletteAnimation, TicketCard et WinnerClaimCard. Les citoyens ne connectent pas de portefeuille externe.
Il reste du code sans appelant :
| Élément | Situation |
|---|---|
IntegratedWalletCard, IntegratedWalletButton |
Plus aucun écran ne les importe ; ExplorerLink n'est plus utilisé que par IntegratedWalletCard |
generateDeterministicWallet de useCitizenWallet |
Plus appelée depuis la suppression de l'écran de connexion par email |
getOnboarding de authApi |
Déclaré, aucun écran ne l'appelle |
triggerSimulatedOsPush de usePushNotifications |
Plus aucun écran ne l'appelle : le bandeau de notification simulé ne s'affiche plus |
notificationDrawerOpen du slice ui |
Le tiroir a été remplacé par l'écran Notifications |
Composants ScratchOfferCard |
Deux composants du même nom, l'un dans components/lottery (accueil), l'autre dans components/marketplace (choix du ticket à gratter) |