Le fil rouge de ces corrections : des replis silencieux. Une exception rattrapee, une image absente, un fichier local jamais rattache — rien ne plantait, mais rien ne marchait non plus, et aucun message ne le disait. Splash et loader - assets/splash et assets/loader ne contenaient que des PNG 1x1 transparents. errorBuilder ne se declenche pas : le fichier est valide, juste vide. Logo et loader etaient donc invisibles PARTOUT, splash comme ecrans de chargement. assets/loader est supprime, kLoaderAsset avec. - Le splash natif etait blanc (launch_background en @android:color/white, theme Light) puis l'app basculait en sombre. Fond unifie sur #111111, cote natif comme Dart, avec values-v31 : Android 12+ ignore windowBackground et repart sur le theme sans ces attributs. - Le loader et l'image principale viennent maintenant du manager (Applications -> Mobile). InstanceImages les garde sur le device : le splash s'affiche avant tout appel API, il ne peut lire qu'un cache. Alimente au boot depuis instanceGetDetail, qui porte deja les ApplicationInstances — aucun appel supplementaire. - Ouvrir une section montrait une page blanche avec un loader au milieu. SlideFromRightRoute devient non opaque et SectionPage garde un fond transparent tant que la section n'est pas prete : le visiteur garde sous les yeux l'ecran d'ou il vient. Hors ligne - L'audio d'un article ne repartait jamais du MP3 local : audioFile n'etait jamais renseigne, le lecteur retombait toujours sur l'URL. - L'image de titre d'un article passait par NetworkImage, donc une croix rouge des que le reseau manque, alors que le fichier est sur le device. - L'accueil ne gardait en base que order/gridSpan : une visite jamais telechargee disparaissait de la grille hors ligne. La visite entiere est desormais mise en cache, et une visite injoignable reste affichee, ternie et non ouvrable, plutot qu'absente. - L'etat reseau datait du premier chargement et n'etait jamais remesure. WidgetsBindingObserver etait declare mais jamais enregistre : aucun rappel n'arrivait. L'accueil se recharge au retour dans l'app et remesure avant de refuser l'ouverture d'une visite. Divers - Le futur de getSectionDetail etait construit dans future:, donc relance a chaque rebuild — chaque rotation d'ecran refaisait l'appel reseau. - ImageCustomProvider listait le repertoire d'une visite non telechargee : exception a chaque construction de chaque image, pour finir de toute facon sur le reseau. - Scanner un QR d'une autre visite empilait les ConfigurationPage. pushAndRemoveUntil : une visite est une destination de premier niveau. - Le scanner arrive sur l'accueil, en pastille de verre comme les autres actions de cet ecran. ScannerBouton prend size et transparent, et garde son apparence d'origine ailleurs. - Le titre d'un article n'etait pas centre : customStylesBuilder ne s'applique qu'aux elements, et un titre sans balise n'en a aucun. - Le lecteur audio replie ne lancait pas la lecture au premier appui, il ouvrait le panneau. Il lit, puis ouvre. En lecture auto il reste replie. - Le dialogue de telechargement faisait la hauteur de l'ecran : un Center prend toute la hauteur maximale que lui donne un AlertDialog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README
This README would normally document whatever steps are necessary to get your application up and running.
What is this repository for?
- Quick summary
- Version
- Learn Markdown
How do I get set up?
- Summary of set up
- Configuration
- Dependencies
- Database configuration
- How to run tests
- Deployment instructions
Contribution guidelines
- Writing tests
- Code review
- Other guidelines
Who do I talk to?
- Repo owner or admin
- Other community or team contact
OPENAPI Generation cmd
flutter clean
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
Le fichier est dans le projet.
Assets
Les svg de drapeaux viennent de là https://flagicons.lipis.dev/
Publication sur le Google PlayStore
Les paramètres de signing sont les mêmes que pour l'application tablet (donc la même android key).
Puis :
flutter build appbundle
Faut pas oublier d'aller changer la version avant chaque upload de version (Puis mettre l'app bundle dans le dossier du PC, peut-être mettre sur le repos aussi ?)
Clients existants
| Flavor | Package Android | Bundle iOS | Instance ID | Nom app |
|---|---|---|---|---|
mdlf |
be.unov.mymuseum.mdlf |
be.unov.mymuseum.mdlf |
65ccc67265373befd15be511 |
MDLF |
fortsaintheribert |
be.unov.mymuseum.fortsaintheribert |
be.unov.mymuseum.fortsaintheribert |
633ee379d9405f32f166f047 |
Fort Saint-Héribert |
dev |
be.unov.myinfomate.test |
be.unov.myinfomate.test |
63514fd67ed8c735aaa4b8f2 |
MyMuseum Dev |
Builder pour un client
Les paramètres à passer à chaque build :
FLAVOR— nom du flavor (dev,mdlf,fortsaintheribert, ...)INSTANCE_ID— le GUID de l'instance dans le backendAPI_BASE_URL— l'URL de l'API backendAPI_KEY— la clé API de l'instance (générée via le Manager, endpoint/api/apikey)
La clé API est embarquée dans le binaire au moment du build. Elle est récupérable dans le Manager.
Android (App Bundle pour Play Store)
flutter build appbundle --flavor mdlf --release -t lib/main.dart \
--dart-define=FLAVOR=mdlf \
--dart-define=INSTANCE_ID=65ccc67265373befd15be511 \
--dart-define=API_BASE_URL=https://api.mymuseum.be \
--dart-define=API_KEY=CLE_API_MDLF
flutter build appbundle --flavor fortsaintheribert --release -t lib/main.dart \
--dart-define=FLAVOR=fortsaintheribert \
--dart-define=INSTANCE_ID=633ee379d9405f32f166f047 \
--dart-define=API_BASE_URL=https://api.mymuseum.be \
--dart-define=API_KEY=CLE_API_FORTSAINTHERIBERT
iOS (fichier .ipa pour App Store)
IPA = format de fichier d'une app iOS, équivalent de l'App Bundle Android. C'est ce qu'on upload sur l'App Store Connect.
flutter build ipa --flavor mdlf --release -t lib/main.dart \
--dart-define=FLAVOR=mdlf \
--dart-define=INSTANCE_ID=65ccc67265373befd15be511 \
--dart-define=API_BASE_URL=https://api.mymuseum.be \
--dart-define=API_KEY=CLE_API_MDLF
flutter build ipa --flavor fortsaintheribert --release -t lib/main.dart \
--dart-define=FLAVOR=fortsaintheribert \
--dart-define=INSTANCE_ID=633ee379d9405f32f166f047 \
--dart-define=API_BASE_URL=https://api.mymuseum.be \
--dart-define=API_KEY=CLE_API_FORTSAINTHERIBERT
Dev local (flavor dev)
flutter run --flavor dev -t lib/main.dart \
--dart-define=FLAVOR=dev \
--dart-define=INSTANCE_ID=63514fd67ed8c735aaa4b8f2 \
--dart-define=API_BASE_URL=http://192.168.x.x:5000 \
--dart-define=API_KEY=CLE_API_DEV
Icône de l'app
Pour mettre une icône spécifique par client, utilise https://easyappicon.com/ pour générer les assets,
puis place-les dans android/app/src/{flavor}/res/mipmap-*/ic_launcher.png
(ils remplaceront automatiquement l'icône par défaut au build).
Ajouter un nouveau client
Exemple avec un client nommé newclient → package be.unov.myinfomate.newclient
1. Firebase Console → projet mymuseum-3b97f
- Ajouter app Android (
be.unov.myinfomate.newclient) → re-téléchargergoogle-services.json→ le copier dansandroid/app/src/newclient/google-services.json(et mettre à jour les autres flavors aussi) - Ajouter app iOS (
be.unov.myinfomate.newclient) → téléchargerGoogleService-Info.plist→ sauvegarder dansios/config/newclient/GoogleService-Info.plist - Uploader l'APNs Auth Key dans Firebase pour la nouvelle app iOS (même clé que les autres apps)
2. Apple Developer Portal
- Créer App ID
be.unov.myinfomate.newclientavec Push Notifications activé
3. android/app/build.gradle : ajouter le flavor dans productFlavors
newclient {
dimension "client"
applicationId "be.unov.myinfomate.newclient"
resValue "string", "app_name", "Nom de l'app"
}
4. Xcode : dupliquer les 3 configs (Debug, Release, Profile) pour le nouveau flavor, créer le scheme partagé
- Build Settings →
PRODUCT_BUNDLE_IDENTIFIER=be.unov.myinfomate.newclient - Build Settings → ajouter
FLUTTER_FLAVOR=newclient
5. Mettre à jour le tableau "Clients existants" en haut de ce README avec le nouveau client
6. Ajouter les couleurs dans lib/constants.dart
Ajouter un nouveau cas dans chaque ternaire (kMainColor0, kMainColor1, kMainColor2) :
_flavor == 'newclient' ? 0xFFxxxxxx : // couleur principale du client
7. Ajouter les assets splash et loader
assets/splash/newclient.png— logo affiché au démarrage de l'app (fond transparent, ~400×400px)assets/loader/newclient.png— icône du loader in-app, affiché en rotation pendant les chargements (fond transparent, ~200×200px)
Puis ajouter le nouveau flavor dans les deux constantes de lib/constants.dart :
const kSplashLogoAsset = _flavor == 'mdlf'
? 'assets/splash/mdlf.png'
: _flavor == 'fortsaintheribert'
? 'assets/splash/fortsaintheribert.png'
: _flavor == 'newclient'
? 'assets/splash/newclient.png'
: 'assets/splash/dev.png';
const kLoaderAsset = _flavor == 'mdlf'
? 'assets/loader/mdlf.png'
: _flavor == 'fortsaintheribert'
? 'assets/loader/fortsaintheribert.png'
: _flavor == 'newclient'
? 'assets/loader/newclient.png'
: 'assets/loader/dev.png';
Si tu ne fournis pas d'image, le fallback est automatique :
Icons.museum_outlinedpour le loader,Icons.museum_outlinedpour le splash. Mais l'image doit quand même exister dansassets/(même un placeholder 1×1px) car Flutter vérifie les assets déclarés au build.
8. Tester
flutter run --flavor newclient -t lib/main.dart \
--dart-define=FLAVOR=newclient \
--dart-define=INSTANCE_ID=GUID_DU_CLIENT \
--dart-define=API_BASE_URL=https://api.mymuseum.be
⚠️ Les fichiers
ios/config/*/GoogleService-Info.plistmarqués PLACEHOLDER doivent être remplacés par les vrais fichiers téléchargés depuis Firebase Console avant de builder en production iOS.