Studio lot 6 : mention « image générée par IA » chez le visiteur

Une reconstitution n'est pas une photographie d'archive, et rien dans l'image ne le dit :
la mention est due au visiteur. Elle est posée au-dessus des trois branches de rendu
(réseau, cache hors ligne, URL externe) plutôt qu'à chacune, pour qu'aucun point
d'affichage ne puisse l'oublier.

Traduite dans les onze langues publiées : l'app visiteur n'a pas d'infrastructure de
traduction pour ses propres libellés.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thomas Fransolet 2026-09-15 17:33:55 +02:00
parent 9e213968f5
commit bac086f1e1
2 changed files with 70 additions and 0 deletions

View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
/// Mention « image générée par IA » posée sur une image produite par le Studio (lot 6).
///
/// Elle est due au visiteur, pas au client : une reconstitution n'est pas une photographie
/// d'archive, et rien dans l'image ne le dit. Le texte suit la langue de visite l'app
/// visiteur n'a pas d'infrastructure de traduction pour ses propres libellés, les dix
/// langues publiées sont donc portées ici.
class AiGeneratedBadge extends StatelessWidget {
const AiGeneratedBadge({Key? key, required this.language}) : super(key: key);
final String? language;
static const _labels = {
'FR': 'Image générée par IA',
'NL': 'Beeld gegenereerd door AI',
'EN': 'AI-generated image',
'DE': 'KI-generiertes Bild',
'ES': 'Imagen generada por IA',
'IT': 'Immagine generata dallIA',
'PL': 'Obraz wygenerowany przez SI',
'CN': '人工智能生成的图像',
'AR': 'صورة من إنتاج الذكاء الاصطناعي',
'UK': 'Зображення, створене ШІ',
'LB': 'Bild generéiert vun der KI',
};
/// Une image générée doit porter la mention ; une image téléversée, jamais.
static bool isGenerated(ResourceDTO? resource) =>
resource?.origin == ResourceOrigin.Generated;
@override
Widget build(BuildContext context) {
final label = _labels[(language ?? 'FR').toUpperCase()] ?? _labels['FR']!;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.72),
borderRadius: BorderRadius.circular(4),
),
child: Text(
label,
style: const TextStyle(color: Colors.white, fontSize: 9, letterSpacing: 0.2),
),
);
}
}

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:tablet_app/Components/ai_generated_badge.dart';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:manager_api_new/api.dart';
@ -26,7 +27,27 @@ class CachedCustomResource extends StatelessWidget {
});
@override
/// L'image porte sa mention quelle que soit la branche de rendu prise en dessous.
/// Même geste que dans l'app visiteur : la poser ici évite d'y penser à chaque écran.
Widget build(BuildContext context) {
final resource = _resource(context);
if (!AiGeneratedBadge.isGenerated(resourceDTO)) return resource;
final tabletAppContext =
Provider.of<AppContext>(context, listen: false).getContext() as TabletAppContext;
return Stack(
children: [
Positioned.fill(child: resource),
Positioned(
left: 6,
bottom: 6,
child: AiGeneratedBadge(language: tabletAppContext.language),
),
],
);
}
Widget _resource(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
TabletAppContext tabletAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;