manager-app/lib/constants.dart
Thomas Fransolet 842f69ebc9 Web mobile : le clavier ne s'ouvrait pas sur l'ecran de login
web/index.html n'avait aucune balise <meta name="viewport">. Les navigateurs
mobiles appliquaient donc un viewport virtuel de ~980px et dezoomaient la page,
ce qui decalait les <input> DOM caches par lesquels Flutter web gere la saisie :
le tap donnait bien le focus cote Flutter, mais le navigateur n'ouvrait pas le
clavier et tentait de zoomer sur l'element — l'ecran semblait juste se rafraichir.
Effet de bord, le LayoutBuilder du login voyait maxWidth ~980, donc isMobile
etait toujours faux et la mise en page mobile ne s'appliquait jamais.

maximum-scale=1.0 evite en plus le zoom automatique de Safari iOS au focus.

Au passage :
- suppression de window.flutterWebRenderer = "html", sans effet depuis que le
  renderer HTML a ete retire de Flutter (3.29, on est en 3.44) ;
- badge de version sur l'ecran de login. kGitSha est injecte au build
  (--dart-define=GIT_SHA) et affiche a cote du numero de pubspec, pour relier
  un bundle deploye au commit exact qui l'a produit ;
- la Future de PackageInfo etait recreee a chaque build() du login, ce qui
  faisait clignoter le libelle : elle est desormais construite une seule fois.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:31:01 +02:00

209 lines
6.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
// Colors - TO FILL WIT CORRECT COLOR
//const kBackgroundColor = Color(0xFFFFFFFF);
const kTitleTextColor = Color(0xFF303030);
const kBodyTextColor = Color(0xFF393939); // TODO
const kBackgroundColor = Color(0xFFf5f5f7);
const kPrimaryColor = Color(0xFF264863); // #264863 // 308aae
const kError = Color(0xFFCA413F);
const kTextLightColor = Color(0xFFFCFDFD);
const kSecond = Color(0xFFC2C9D6);
const kWhite = Color(0xFFFFFFFF);
const kBlack = Color(0xFF000000);
const kSuccess = Color(0xFF8bc34a);
// URL de l'API, injectee au build :
// flutter build web --dart-define=API_BASE_URL=https://api.mymuseum.be
//
// Elle etait ecrite en dur a quatre endroits, donc une image de manager-web
// etait liee a un environnement sans que rien ne le signale.
const kApiBaseUrl = String.fromEnvironment('API_BASE_URL',
defaultValue: 'http://localhost:5000');
// Empreinte du build, injectee elle aussi au build :
// --dart-define=GIT_SHA=$(git rev-parse --short HEAD)
// Elle est affichee sous le titre de l'ecran de login et sert a relier un
// bundle deploye au commit exact qui l'a produit : le numero de version du
// pubspec ne suffisait pas, il ne bougeait pas d'un deploiement a l'autre.
const kGitSha = String.fromEnvironment('GIT_SHA', defaultValue: 'dev');
// Responsive
const kBreakpointMobile = 850.0;
// Shadows
const kDefaultShadow = BoxShadow(
color: kSecond,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1.5),
);
// TextStyles for cards
const kCardTitleStyle = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: kWhite,
);
const kCardSubtitleStyle = TextStyle(
fontSize: 11,
fontWeight: FontWeight.w300,
color: Color(0xCCFFFFFF),
);
const kSectionLabelStyle = TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: kTitleTextColor,
);
// ---------------------------------------------------------------------------
// Socle visuel — l'échelle typographique et les espacements viennent du CSS des
// maquettes validées (DOCS/claude design/guide-ia-screen.html et
// statistics-screen.html, tokens identiques dans les deux).
//
// Les couleurs, elles, restent celles de l'app : un écran neuf doit se fondre
// dans manager-app, pas y ouvrir une seconde palette. Seules les valeurs sans
// équivalent existant sont reprises des maquettes — les remplissages discrets,
// les bordures, le gris atténué et l'ambre d'avertissement.
// Pas de thème sombre : l'app n'en a pas.
// ---------------------------------------------------------------------------
const kGround = kBackgroundColor;
const kSurface = kWhite;
const kSurface2 = Color(0xFFF3F5F8);
const kSurface3 = Color(0xFFE7EBF1);
const kInk = kTitleTextColor;
const kInk2 = kBodyTextColor;
const kInk3 = Color(0xFF7C8B9A);
const kLine = Color(0xFFD6DDE5);
const kLineSoft = Color(0xFFE6EBF1);
const kBrand = kPrimaryColor;
const kBrand2 = Color(0xFF3C6C90);
const kBrandSoft = kSecond;
const kOnBrand = kWhite;
const kWarning = Color(0xFF9A6608);
const kSerious = kError;
const kGood = kSuccess;
const kMonoFamily = 'monospace';
const kSpace1 = 4.0;
const kSpace2 = 6.0;
const kSpace3 = 8.0;
const kSpace4 = 12.0;
const kSpace5 = 16.0;
const kSpace6 = 18.0;
const kSpace7 = 24.0;
const kSpace8 = 32.0;
const kRadiusInput = 5.0;
const kRadiusCard = 8.0;
const kRadiusShell = 10.0;
const kRadiusTrack = 4.0;
const kRadiusPill = 999.0;
const kCardPadding = EdgeInsets.fromLTRB(18, 17, 18, 19);
const kPagePadding = EdgeInsets.fromLTRB(28, 24, 28, 30);
const kCardShadow = [kDefaultShadow];
const kTitleScreen = TextStyle(
fontSize: 23,
fontWeight: FontWeight.w700,
letterSpacing: -0.41,
color: kInk,
);
const kTitleCard = TextStyle(
fontSize: 14.5,
fontWeight: FontWeight.w700,
color: kInk,
);
const kSubtitleCard = TextStyle(
fontSize: 12.5,
color: kInk3,
);
const kTextBody = TextStyle(
fontSize: 13.5,
color: kInk,
);
const kTextSmall = TextStyle(
fontSize: 13,
color: kInk2,
);
const kLabelField = TextStyle(
fontSize: 12.5,
fontWeight: FontWeight.w600,
color: kInk2,
);
const kTextHint = TextStyle(
fontSize: 12,
color: kInk3,
);
const kOverline = TextStyle(
fontSize: 11.5,
letterSpacing: 0.69,
color: kInk3,
);
const kOverlineMono = TextStyle(
fontFamily: kMonoFamily,
fontSize: 10.5,
letterSpacing: 0.95,
color: kInk3,
);
const kMetricLarge = TextStyle(
fontFamily: kMonoFamily,
fontSize: 29,
fontWeight: FontWeight.w600,
letterSpacing: -0.73,
height: 1,
color: kInk,
fontFeatures: [FontFeature.tabularFigures()],
);
const kMetricMedium = TextStyle(
fontFamily: kMonoFamily,
fontSize: 26,
fontWeight: FontWeight.w600,
letterSpacing: -0.52,
color: kInk,
fontFeatures: [FontFeature.tabularFigures()],
);
const List<String> section_types = ["Map", "Slider", "Video", "Web", "Menu", "Quiz", "Article", "PDF", "Game", "Agenda", "Weather", "Event", "Parcours"];
const List<String> map_types = ["none", "normal", "satellite", "terrain", "hybrid"];
const List<String> languages = ["FR", "NL", "EN", "DE", "IT", "ES", "CN", "PL", "AR", "UK"];
const List<String> map_types_mapBox = ["standard", "streets", "outdoors", "light", "dark", "satellite", "satellite_streets"];
const List<String> map_providers = ["Google", "MapBox"];
/// Plafond d'utilisateurs par instance, SuperAdmin exclu.
/// ⚠️ Contrôle d'interface uniquement : `UserController` ne compte rien côté serveur,
/// un POST direct sur l'API passe toujours.
const kMaxUsersPerInstance = 5;
const kTitleMaxLength = 165;
const kDescriptionMaxLength = 2500;
/// Types de ressource proposés, dans l'ordre d'affichage. Le libellé se lit
/// avec `resourceTypeLabel` : il dépend de la langue du manager.
const List<ResourceType> resource_types = [
ResourceType.Image,
ResourceType.ImageUrl,
ResourceType.Video,
ResourceType.VideoUrl,
ResourceType.Audio,
ResourceType.Pdf,
ResourceType.Json,
ResourceType.JsonUrl,
];
/*
const kTextStyle = TextStyle(
fontSize: 23,
color: kPrimaryColor,
fontWeight: FontWeight.w600,
);*/