Ecran VR, section Scene3D et medias immersifs
Canal VR - L'entree VR affiche VrScreen au lieu de « non configuree » : sous-onglet Configuration (AppConfigurationLinkScreen reutilise) et sous-onglet Casques (pincode d'appairage, etat, batterie, version, dernier vu). - Le dialogue de plan SuperAdmin porte aussi les canaux et les add-ons (assistant, contenu immersif). Activer un canal cree son ApplicationInstance s'il n'existe pas ; le desactiver ne supprime rien. Medias immersifs - Types Image360, Video360, Model3D : icones, libelles, extensions, apercu dans la Mediatheque et facettes. - Au depot, la pastille de type bascule Image <-> Image 360 et Video <-> Video 360 : rien dans un .jpg ne dit qu'il est equirectangulaire. Un .glb est reconnu seul. - ImageCompressor ne touche plus aux 360 ni aux GLB, sur les deux chemins d'upload : ramenee a 2560 px, une equirectangulaire devient illisible dans un casque. Scene3D - Nouveau type de section (famille des lieux) et son ecran de configuration : modele, mode objet/decor, points d'interet. - Fond immersif d'une configuration, visible si l'instance a l'add-on : le type est deduit de la ressource choisie, avec une image de repli. Client API edite a la main en miroir du backend : SectionScene3DApi, Scene3DDTO, ImmersiveBackgroundDTO, Position3D, AppType sur les devices, ResourceType etendu. Libelles FR/EN/NL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0c3bc31b5e
commit
c8947880ba
@ -19,6 +19,14 @@ IconData getResourceIcon(elementType) {
|
|||||||
return Icons.file_present_outlined;
|
return Icons.file_present_outlined;
|
||||||
case ResourceType.JsonUrl:
|
case ResourceType.JsonUrl:
|
||||||
return Icons.file_present_rounded;
|
return Icons.file_present_rounded;
|
||||||
|
// Sans ces trois-là, un média immersif s'affichait avec le point
|
||||||
|
// d'exclamation du cas par défaut — ce qui se lit comme une erreur.
|
||||||
|
case ResourceType.Image360:
|
||||||
|
return Icons.panorama_photosphere;
|
||||||
|
case ResourceType.Video360:
|
||||||
|
return Icons.threesixty;
|
||||||
|
case ResourceType.Model3D:
|
||||||
|
return Icons.view_in_ar;
|
||||||
}
|
}
|
||||||
return Icons.announcement;
|
return Icons.announcement;
|
||||||
}
|
}
|
||||||
@ -17,6 +17,7 @@ String getSectionTypeName(AppLocalizations l, SectionType? type) {
|
|||||||
case SectionType.Weather: return l.sectionTypeWeather;
|
case SectionType.Weather: return l.sectionTypeWeather;
|
||||||
case SectionType.Event: return l.sectionTypeEvent;
|
case SectionType.Event: return l.sectionTypeEvent;
|
||||||
case SectionType.Parcours: return l.sectionTypeParcours;
|
case SectionType.Parcours: return l.sectionTypeParcours;
|
||||||
|
case SectionType.Scene3D: return l.sectionTypeScene3D;
|
||||||
default: return l.sectionTypeDefault;
|
default: return l.sectionTypeDefault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,6 +39,7 @@ String getSectionTypeDescription(AppLocalizations l, SectionType? type) {
|
|||||||
case SectionType.Weather: return l.sectionTypeDescWeather;
|
case SectionType.Weather: return l.sectionTypeDescWeather;
|
||||||
case SectionType.Event: return l.sectionTypeDescEvent;
|
case SectionType.Event: return l.sectionTypeDescEvent;
|
||||||
case SectionType.Parcours: return l.sectionTypeDescParcours;
|
case SectionType.Parcours: return l.sectionTypeDescParcours;
|
||||||
|
case SectionType.Scene3D: return l.sectionTypeDescScene3D;
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,6 +72,8 @@ IconData getSectionIcon(elementType) {
|
|||||||
return Icons.event;
|
return Icons.event;
|
||||||
case SectionType.Parcours:
|
case SectionType.Parcours:
|
||||||
return Icons.route;
|
return Icons.route;
|
||||||
|
case SectionType.Scene3D:
|
||||||
|
return Icons.view_in_ar;
|
||||||
}
|
}
|
||||||
return Icons.question_mark;
|
return Icons.question_mark;
|
||||||
}
|
}
|
||||||
125
lib/Components/immersive_background_picker.dart
Normal file
125
lib/Components/immersive_background_picker.dart
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
import 'package:manager_app/Components/resource_input_container.dart';
|
||||||
|
import 'package:manager_app/constants.dart';
|
||||||
|
import 'package:manager_app/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
/// Le fond immersif d'un lieu, tel que le gestionnaire le choisit.
|
||||||
|
///
|
||||||
|
/// **Le type n'est pas demandé, il est déduit** de la ressource choisie : une
|
||||||
|
/// `Image360` donne un panorama, une `Video360` une vidéo, un `Model3D` une scène.
|
||||||
|
/// Demander « panorama ou vidéo ? » après avoir fait choisir une vidéo, c'est poser
|
||||||
|
/// une question dont la réponse est déjà sur l'écran — et ouvrir la possibilité d'y
|
||||||
|
/// répondre faux, ce qui donnerait un fond noir sans explication.
|
||||||
|
///
|
||||||
|
/// Le repli est un champ séparé, et il est proposé à côté plutôt que caché : trois
|
||||||
|
/// canaux sur quatre ne rendent pas un panorama, et c'est lui qu'ils afficheront.
|
||||||
|
class ImmersiveBackgroundPicker extends StatefulWidget {
|
||||||
|
const ImmersiveBackgroundPicker({
|
||||||
|
super.key,
|
||||||
|
required this.value,
|
||||||
|
required this.onChanged,
|
||||||
|
required this.title,
|
||||||
|
required this.hint,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ImmersiveBackgroundDTO? value;
|
||||||
|
final ValueChanged<ImmersiveBackgroundDTO?> onChanged;
|
||||||
|
final String title;
|
||||||
|
final String hint;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ImmersiveBackgroundPicker> createState() => _ImmersiveBackgroundPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ImmersiveBackgroundPickerState extends State<ImmersiveBackgroundPicker> {
|
||||||
|
static const _immersiveTypes = [
|
||||||
|
ResourceType.Image360,
|
||||||
|
ResourceType.Video360,
|
||||||
|
ResourceType.Model3D,
|
||||||
|
];
|
||||||
|
|
||||||
|
static ImmersiveBackgroundKind _kindOf(ResourceType? type) {
|
||||||
|
if (type == ResourceType.Video360) return ImmersiveBackgroundKind.Video360;
|
||||||
|
if (type == ResourceType.Model3D) return ImmersiveBackgroundKind.Scene3D;
|
||||||
|
return ImmersiveBackgroundKind.Pano;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _kindLabel(AppLocalizations l, ImmersiveBackgroundKind? kind) {
|
||||||
|
if (kind == ImmersiveBackgroundKind.Video360) return l.backgroundKindVideo360;
|
||||||
|
if (kind == ImmersiveBackgroundKind.Scene3D) return l.backgroundKindScene3D;
|
||||||
|
return l.backgroundKindPano;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
final value = widget.value;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(widget.title,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
|
||||||
|
const SizedBox(height: kSpace2),
|
||||||
|
Text(widget.hint, style: kTextHint),
|
||||||
|
const SizedBox(height: kSpace4),
|
||||||
|
ResourceInputContainer(
|
||||||
|
label: l.backgroundResourceLabel,
|
||||||
|
initialValue: value?.resourceId,
|
||||||
|
inResourceTypes: _immersiveTypes,
|
||||||
|
onChanged: (ResourceDTO resource) {
|
||||||
|
final updated = ImmersiveBackgroundDTO(
|
||||||
|
resourceId: resource.id,
|
||||||
|
kind: _kindOf(resource.type),
|
||||||
|
fallbackResourceId: value?.fallbackResourceId,
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
widget.onChanged(updated);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (value?.resourceId != null) ...[
|
||||||
|
const SizedBox(height: kSpace2),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.info_outline, size: 14, color: kInk3),
|
||||||
|
const SizedBox(width: kSpace2),
|
||||||
|
Expanded(
|
||||||
|
child: Text(_kindLabel(l, value!.kind), style: kTextHint),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: kSpace5),
|
||||||
|
ResourceInputContainer(
|
||||||
|
label: l.backgroundFallbackLabel,
|
||||||
|
initialValue: value.fallbackResourceId,
|
||||||
|
inResourceTypes: const [ResourceType.Image],
|
||||||
|
onChanged: (ResourceDTO resource) {
|
||||||
|
final updated = ImmersiveBackgroundDTO(
|
||||||
|
resourceId: value.resourceId,
|
||||||
|
kind: value.kind,
|
||||||
|
fallbackResourceId: resource.id,
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
widget.onChanged(updated);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: kSpace2),
|
||||||
|
Text(l.backgroundFallbackHint, style: kTextHint),
|
||||||
|
const SizedBox(height: kSpace4),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {});
|
||||||
|
widget.onChanged(null);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close, size: 16),
|
||||||
|
label: Text(l.backgroundRemove),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@ import 'package:manager_app/l10n/app_localizations.dart';
|
|||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
|
|
||||||
const List<String> kAcceptedExtensions = [
|
const List<String> kAcceptedExtensions = [
|
||||||
'jpg', 'jpeg', 'png', 'gif', 'mp3', 'mp4', 'webm', 'pdf', 'json',
|
'jpg', 'jpeg', 'png', 'gif', 'mp3', 'mp4', 'webm', 'pdf', 'json', 'glb',
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Une entrée en attente d'envoi : soit un fichier local, soit une URL.
|
/// Une entrée en attente d'envoi : soit un fichier local, soit une URL.
|
||||||
@ -22,10 +22,14 @@ const List<String> kAcceptedExtensions = [
|
|||||||
class PickedResource {
|
class PickedResource {
|
||||||
final PlatformFile? file;
|
final PlatformFile? file;
|
||||||
final String? url;
|
final String? url;
|
||||||
final ResourceType? type;
|
|
||||||
|
|
||||||
const PickedResource.file(this.file, this.type) : url = null;
|
/// Modifiable : l'extension donne le type de départ, mais rien dans un `.jpg`
|
||||||
const PickedResource.url(this.url, this.type) : file = null;
|
/// ne dit qu'il est équirectangulaire — c'est le gestionnaire qui le déclare,
|
||||||
|
/// avant l'envoi, en touchant la pastille.
|
||||||
|
ResourceType? type;
|
||||||
|
|
||||||
|
PickedResource.file(this.file, this.type) : url = null;
|
||||||
|
PickedResource.url(this.url, this.type) : file = null;
|
||||||
|
|
||||||
bool get isUrl => url != null;
|
bool get isUrl => url != null;
|
||||||
String get name => isUrl ? url! : file!.name;
|
String get name => isUrl ? url! : file!.name;
|
||||||
@ -306,6 +310,65 @@ class _ResourcePickerState extends State<ResourcePicker> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bascule entre un média plat et sa version 360°.
|
||||||
|
///
|
||||||
|
/// Aucune extension ne dit qu'une photo est équirectangulaire : un panorama 360
|
||||||
|
/// est un `.jpg` comme un autre. C'est donc au gestionnaire de le déclarer, et
|
||||||
|
/// ça change deux choses — la ressource n'est plus compressée (une 360 ramenée
|
||||||
|
/// à 2560 px devient une bouillie dans un casque), et le casque sait qu'il peut
|
||||||
|
/// l'afficher en skybox.
|
||||||
|
static const Map<ResourceType, ResourceType> _immersiveOf = {
|
||||||
|
ResourceType.Image: ResourceType.Image360,
|
||||||
|
ResourceType.Image360: ResourceType.Image,
|
||||||
|
ResourceType.Video: ResourceType.Video360,
|
||||||
|
ResourceType.Video360: ResourceType.Video,
|
||||||
|
};
|
||||||
|
|
||||||
|
Widget _typeChip(AppLocalizations l, PickedResource entry) {
|
||||||
|
final toggle = _immersiveOf[entry.type];
|
||||||
|
final isImmersive = entry.type == ResourceType.Image360 ||
|
||||||
|
entry.type == ResourceType.Video360;
|
||||||
|
|
||||||
|
final chip = Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: kSpace2, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isImmersive ? kBrandSoft : kSurface3,
|
||||||
|
borderRadius: BorderRadius.circular(kRadiusInput),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(resourceTypeLabel(l, entry.type),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.5,
|
||||||
|
color: isImmersive ? kInk : kInk2,
|
||||||
|
fontWeight: isImmersive ? FontWeight.w600 : FontWeight.w400)),
|
||||||
|
// Sans ce chevron, rien ne distingue une pastille cliquable d'un simple
|
||||||
|
// libellé : le basculement 360° n'était découvrable qu'en survolant.
|
||||||
|
if (toggle != null) ...[
|
||||||
|
const SizedBox(width: 3),
|
||||||
|
Icon(Icons.swap_horiz,
|
||||||
|
size: 12, color: isImmersive ? kInk : kInk3),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (toggle == null) return chip;
|
||||||
|
|
||||||
|
return Tooltip(
|
||||||
|
message: l.mediaToggle360Hint,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(kRadiusInput),
|
||||||
|
onTap: () {
|
||||||
|
setState(() => entry.type = toggle);
|
||||||
|
widget.onChanged();
|
||||||
|
},
|
||||||
|
child: chip,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _list(AppLocalizations l) {
|
Widget _list(AppLocalizations l) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@ -335,16 +398,7 @@ class _ResourcePickerState extends State<ResourcePicker> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: kSpace3),
|
const SizedBox(width: kSpace3),
|
||||||
Container(
|
_typeChip(l, entry),
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: kSpace2, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: kSurface3,
|
|
||||||
borderRadius: BorderRadius.circular(kRadiusInput),
|
|
||||||
),
|
|
||||||
child: Text(resourceTypeLabel(l, entry.type),
|
|
||||||
style: const TextStyle(fontSize: 10.5, color: kInk2)),
|
|
||||||
),
|
|
||||||
const SizedBox(width: kSpace2),
|
const SizedBox(width: kSpace2),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.close, size: 15),
|
icon: const Icon(Icons.close, size: 15),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
|
||||||
/// Compression des images à l'upload.
|
/// Compression des images à l'upload.
|
||||||
///
|
///
|
||||||
@ -25,7 +26,18 @@ class ImageCompressor {
|
|||||||
/// L'original est renvoyé tel quel si le format n'est pas gérable, si le
|
/// L'original est renvoyé tel quel si le format n'est pas gérable, si le
|
||||||
/// décodage échoue, ou si la compression n'a rien gagné — une image déjà
|
/// décodage échoue, ou si la compression n'a rien gagné — une image déjà
|
||||||
/// petite et bien encodée peut grossir en repassant par un ré-encodage.
|
/// petite et bien encodée peut grossir en repassant par un ré-encodage.
|
||||||
static CompressedImage compress(Uint8List bytes, String? extension, String fallbackMimeType) {
|
///
|
||||||
|
/// [type] décide aussi : une image 360 n'est **jamais** compressée. Ramenée à
|
||||||
|
/// 2560 px de côté long, une équirectangulaire de 8192×4096 perd les trois
|
||||||
|
/// quarts de sa définition — sur un écran ça se voit à peine, dans un casque
|
||||||
|
/// elle couvre tout le champ de vision et devient une bouillie.
|
||||||
|
static CompressedImage compress(Uint8List bytes, String? extension, String fallbackMimeType,
|
||||||
|
{ResourceType? type}) {
|
||||||
|
if (type == ResourceType.Image360 || type == ResourceType.Video360 ||
|
||||||
|
type == ResourceType.Model3D) {
|
||||||
|
return CompressedImage(bytes, fallbackMimeType, false);
|
||||||
|
}
|
||||||
|
|
||||||
if (!handles(extension)) {
|
if (!handles(extension)) {
|
||||||
return CompressedImage(bytes, fallbackMimeType, false);
|
return CompressedImage(bytes, fallbackMimeType, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,430 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:html' as html;
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui_web' as ui_web;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
import 'package:manager_app/Components/message_notification.dart';
|
||||||
|
import 'package:manager_app/Components/resource_input_container.dart';
|
||||||
|
import 'package:manager_app/Models/managerContext.dart';
|
||||||
|
import 'package:manager_app/app_context.dart';
|
||||||
|
import 'package:manager_app/constants.dart';
|
||||||
|
import 'package:manager_app/l10n/app_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
/// Configuration d'une section **maquette 3D** — item E7 du lot XR-4.
|
||||||
|
///
|
||||||
|
/// Deux choses : choisir le modèle dans la médiathèque, et **poser ses points
|
||||||
|
/// d'intérêt dessus**. Le second se fait dans le viewer 3D, affiché ici en iframe.
|
||||||
|
///
|
||||||
|
/// <b>Pourquoi une iframe et pas un rendu Flutter.</b> Le viewer existe déjà, en
|
||||||
|
/// TypeScript/three.js (`vr-app/viewer`, décision D3) : il charge un GLB, laisse
|
||||||
|
/// déplacer des objets à la souris, et sert aussi à la piste Scène 3D. Le refaire en
|
||||||
|
/// Flutter demanderait un moteur 3D et rejouerait le même travail. Le manager sait
|
||||||
|
/// déjà monter une iframe — `policy_screen`, `web_view` et `pdf_web_viewer` le font.
|
||||||
|
///
|
||||||
|
/// <b>Le protocole n'est pas inventé ici</b> : le viewer parle en **manifeste de
|
||||||
|
/// scène** (`bridge.ts`), le même objet que lit le casque (`SceneManifest.cs`). On lui
|
||||||
|
/// envoie donc un manifeste minimal — le modèle comme décor, les points comme
|
||||||
|
/// *hotspots* — et il renvoie les positions dès qu'on en déplace un. Un hotspot porte
|
||||||
|
/// un `geoPointId` précisément pour ce va-et-vient.
|
||||||
|
class Scene3DConfig extends StatefulWidget {
|
||||||
|
final Scene3DDTO initialValue;
|
||||||
|
final ValueChanged<Scene3DDTO> onChanged;
|
||||||
|
|
||||||
|
const Scene3DConfig({
|
||||||
|
Key? key,
|
||||||
|
required this.initialValue,
|
||||||
|
required this.onChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Scene3DConfig> createState() => _Scene3DConfigState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Scene3DConfigState extends State<Scene3DConfig> {
|
||||||
|
/// Le repère du manifeste, vérifié littéralement des deux côtés. Un miroir d'axes
|
||||||
|
/// est invisible sur une maquette symétrique et se paie très cher découvert tard.
|
||||||
|
static const _coordinateSystem = 'gltf/y-up/right-handed/meters';
|
||||||
|
|
||||||
|
/// Le viewer est servi à côté du manager. En développement il tourne sur son
|
||||||
|
/// propre port (`npm run dev`), d'où la variable de compilation.
|
||||||
|
static const _viewerUrl =
|
||||||
|
String.fromEnvironment('VIEWER_URL', defaultValue: '/viewer/index.html');
|
||||||
|
|
||||||
|
late Scene3DDTO _dto;
|
||||||
|
late final String _viewType;
|
||||||
|
html.IFrameElement? _frame;
|
||||||
|
bool _viewerReady = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_dto = widget.initialValue;
|
||||||
|
_viewType = 'model3d-viewer-${DateTime.now().microsecondsSinceEpoch}';
|
||||||
|
|
||||||
|
ui_web.platformViewRegistry.registerViewFactory(_viewType, (int _) {
|
||||||
|
final frame = html.IFrameElement()
|
||||||
|
..src = _viewerUrl
|
||||||
|
..style.border = 'none'
|
||||||
|
..style.width = '100%'
|
||||||
|
..style.height = '100%';
|
||||||
|
_frame = frame;
|
||||||
|
return frame;
|
||||||
|
});
|
||||||
|
|
||||||
|
html.window.onMessage.listen(_onViewerMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onViewerMessage(html.MessageEvent event) {
|
||||||
|
final message = _asMap(event.data);
|
||||||
|
if (message == null) return;
|
||||||
|
|
||||||
|
switch (message['type']) {
|
||||||
|
case 'ready':
|
||||||
|
// Le viewer prévient quand il est prêt : envoyer avant qu'il ne le soit,
|
||||||
|
// c'est un manifeste perdu sans erreur.
|
||||||
|
_viewerReady = true;
|
||||||
|
_sendManifest();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'changed':
|
||||||
|
_readHotspots(message['hotspots']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
debugPrint('[Model3D] viewer : ${message['message']}');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic>? _asMap(dynamic data) {
|
||||||
|
if (data is Map) return data.cast<String, dynamic>();
|
||||||
|
if (data is String) {
|
||||||
|
try {
|
||||||
|
final decoded = jsonDecode(data);
|
||||||
|
if (decoded is Map) return decoded.cast<String, dynamic>();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reporte sur les points ce que le gestionnaire vient de déplacer, **et
|
||||||
|
/// l'enregistre**.
|
||||||
|
///
|
||||||
|
/// L'écriture part tout de suite plutôt qu'au bouton « Enregistrer » de la
|
||||||
|
/// section : déplacer un point est un geste continu, et personne ne pense à
|
||||||
|
/// sauvegarder après avoir bougé une bille dans une maquette.
|
||||||
|
void _readHotspots(dynamic raw) {
|
||||||
|
if (raw is! List) return;
|
||||||
|
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
for (final entry in raw) {
|
||||||
|
final hotspot = _asMap(entry);
|
||||||
|
final transform = hotspot == null ? null : _asMap(hotspot['transform']);
|
||||||
|
final position = transform?['position'];
|
||||||
|
if (hotspot == null || position is! List || position.length < 3) continue;
|
||||||
|
|
||||||
|
final geoPointId = (hotspot['geoPointId'] as num?)?.toInt();
|
||||||
|
final point = (_dto.points ?? []).firstWhere(
|
||||||
|
(p) => p.id == geoPointId,
|
||||||
|
orElse: () => GeoPointDTO(),
|
||||||
|
);
|
||||||
|
if (point.id == null) continue;
|
||||||
|
|
||||||
|
point.localTransform = Position3D(
|
||||||
|
x: (position[0] as num).toDouble(),
|
||||||
|
y: (position[1] as num).toDouble(),
|
||||||
|
z: (position[2] as num).toDouble(),
|
||||||
|
rotationY: _yawFromQuaternion(transform?['rotation']),
|
||||||
|
);
|
||||||
|
changed = true;
|
||||||
|
_savePoint(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) widget.onChanged(_dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _savePoint(GeoPointDTO point) async {
|
||||||
|
try {
|
||||||
|
await _api.sectionScene3DUpdatePoint(point);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[Model3D] position non enregistrée : $e');
|
||||||
|
if (mounted) {
|
||||||
|
showNotification(kError, kWhite,
|
||||||
|
AppLocalizations.of(context)!.model3DPointSaveError, context, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionScene3DApi get _api =>
|
||||||
|
(Provider.of<AppContext>(context, listen: false).getContext() as ManagerAppContext)
|
||||||
|
.clientAPI!
|
||||||
|
.sectionScene3DApi!;
|
||||||
|
|
||||||
|
Future<void> _addPoint() async {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
final sectionId = _dto.id;
|
||||||
|
if (sectionId == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final created = await _api.sectionScene3DCreatePoint(
|
||||||
|
sectionId,
|
||||||
|
GeoPointDTO(
|
||||||
|
title: [TranslationDTO(language: 'FR', value: l.model3DNewPoint)],
|
||||||
|
description: [],
|
||||||
|
contents: [],
|
||||||
|
// À l'origine du modèle : c'est là que l'éditeur le montrera, à portée
|
||||||
|
// de souris, pour qu'on le pose au bon endroit.
|
||||||
|
localTransform: Position3D(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (created == null) return;
|
||||||
|
|
||||||
|
setState(() => (_dto.points ??= []).add(created));
|
||||||
|
widget.onChanged(_dto);
|
||||||
|
_sendManifest();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[Model3D] création de point : $e');
|
||||||
|
if (mounted) {
|
||||||
|
showNotification(kError, kWhite, l.model3DPointSaveError, context, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deletePoint(GeoPointDTO point) async {
|
||||||
|
if (point.id == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _api.sectionScene3DDeletePoint(point.id!);
|
||||||
|
setState(() => _dto.points?.removeWhere((p) => p.id == point.id));
|
||||||
|
widget.onChanged(_dto);
|
||||||
|
_sendManifest();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[Model3D] suppression de point : $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Le manifeste ne stocke qu'un quaternion ; nous n'avons besoin que du lacet —
|
||||||
|
/// un panneau posé sur une maquette ne bascule pas.
|
||||||
|
double? _yawFromQuaternion(dynamic rotation) {
|
||||||
|
if (rotation is! List || rotation.length < 4) return null;
|
||||||
|
|
||||||
|
final x = (rotation[0] as num).toDouble();
|
||||||
|
final y = (rotation[1] as num).toDouble();
|
||||||
|
final z = (rotation[2] as num).toDouble();
|
||||||
|
final w = (rotation[3] as num).toDouble();
|
||||||
|
|
||||||
|
final yaw = math.atan2(2 * (w * y + x * z), 1 - 2 * (y * y + x * x));
|
||||||
|
return yaw * 180 / math.pi;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> _quaternionFromYaw(double? degrees) {
|
||||||
|
if (degrees == null || degrees == 0) return [0, 0, 0, 1];
|
||||||
|
final half = degrees * math.pi / 180 / 2;
|
||||||
|
return [0, math.sin(half), 0, math.cos(half)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construit le manifeste minimal que le viewer sait lire : le modèle est le
|
||||||
|
/// décor, les points sont les hotspots. Tout le reste est comblé par ses valeurs
|
||||||
|
/// par défaut — c'est une maquette, pas une scène immersive.
|
||||||
|
void _sendManifest() {
|
||||||
|
final frame = _frame;
|
||||||
|
final url = _dto.model3DSource;
|
||||||
|
if (frame?.contentWindow == null || !_viewerReady || url == null) return;
|
||||||
|
|
||||||
|
const assetId = 'model';
|
||||||
|
|
||||||
|
final manifest = {
|
||||||
|
'manifestVersion': 1,
|
||||||
|
'sceneId': _dto.id ?? '',
|
||||||
|
'instanceId': _dto.instanceId ?? '',
|
||||||
|
'configurationId': _dto.configurationId ?? '',
|
||||||
|
'version': 1,
|
||||||
|
'coordinateSystem': _coordinateSystem,
|
||||||
|
'world': {
|
||||||
|
'kind': 'mesh',
|
||||||
|
'assetId': assetId,
|
||||||
|
'transform': {
|
||||||
|
'position': [0, 0, 0],
|
||||||
|
'rotation': [0, 0, 0, 1],
|
||||||
|
'scale': [1, 1, 1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'assets': [
|
||||||
|
{
|
||||||
|
'id': assetId,
|
||||||
|
'resourceId': _dto.model3DResourceId,
|
||||||
|
'url': url,
|
||||||
|
'mimeType': 'model/gltf-binary',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'hotspots': (_dto.points ?? []).map((point) {
|
||||||
|
final position = point.localTransform;
|
||||||
|
return {
|
||||||
|
'id': 'poi-${point.id}',
|
||||||
|
'geoPointId': point.id,
|
||||||
|
'transform': {
|
||||||
|
'position': [position?.x ?? 0, position?.y ?? 0, position?.z ?? 0],
|
||||||
|
'rotation': _quaternionFromYaw(position?.rotationY),
|
||||||
|
'scale': [1, 1, 1],
|
||||||
|
},
|
||||||
|
'title': _localized(point.title),
|
||||||
|
'description': _localized(point.description),
|
||||||
|
'contents': const [],
|
||||||
|
};
|
||||||
|
}).toList(),
|
||||||
|
};
|
||||||
|
|
||||||
|
frame!.contentWindow!.postMessage(
|
||||||
|
{'type': 'load', 'manifest': manifest, 'mode': 'edit'},
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, String>> _localized(List<TranslationDTO>? translations) =>
|
||||||
|
(translations ?? [])
|
||||||
|
.map((t) => {'language': t.language ?? '', 'value': t.value ?? ''})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ResourceInputContainer(
|
||||||
|
label: l.model3DResourceLabel,
|
||||||
|
initialValue: _dto.model3DResourceId,
|
||||||
|
inResourceTypes: const [ResourceType.Model3D],
|
||||||
|
onChanged: (ResourceDTO resource) {
|
||||||
|
setState(() {
|
||||||
|
_dto.model3DResourceId = resource.id;
|
||||||
|
_dto.model3DSource = resource.url;
|
||||||
|
});
|
||||||
|
widget.onChanged(_dto);
|
||||||
|
_sendManifest();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: kSpace5),
|
||||||
|
// Ce que le visiteur fera de la scène : manipuler un objet, ou être dedans.
|
||||||
|
// Aucun fichier ne peut le deviner — même GLB, même points, deux usages.
|
||||||
|
Text(l.scene3DModeTitle,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
|
||||||
|
const SizedBox(height: kSpace2),
|
||||||
|
SegmentedButton<Scene3DMode>(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(
|
||||||
|
value: Scene3DMode.Asset,
|
||||||
|
icon: const Icon(Icons.view_in_ar, size: 16),
|
||||||
|
label: Text(l.scene3DModeAsset),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: Scene3DMode.Scene,
|
||||||
|
icon: const Icon(Icons.panorama_photosphere, size: 16),
|
||||||
|
label: Text(l.scene3DModeScene),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {_dto.mode ?? Scene3DMode.Asset},
|
||||||
|
onSelectionChanged: (selection) {
|
||||||
|
setState(() => _dto.mode = selection.first);
|
||||||
|
widget.onChanged(_dto);
|
||||||
|
_sendManifest();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: kSpace2),
|
||||||
|
Text(
|
||||||
|
(_dto.mode ?? Scene3DMode.Asset) == Scene3DMode.Asset
|
||||||
|
? l.scene3DModeAssetHint
|
||||||
|
: l.scene3DModeSceneHint,
|
||||||
|
style: kTextHint,
|
||||||
|
),
|
||||||
|
const SizedBox(height: kSpace5),
|
||||||
|
if (_dto.model3DResourceId == null)
|
||||||
|
Text(l.model3DPickModelFirst, style: kTextHint)
|
||||||
|
else ...[
|
||||||
|
Text(l.model3DPlacePoints, style: kTextHint),
|
||||||
|
const SizedBox(height: kSpace3),
|
||||||
|
// Hauteur fixe : une iframe n'a pas de taille intrinsèque, et sans
|
||||||
|
// contrainte elle s'effondre à zéro dans une colonne.
|
||||||
|
SizedBox(
|
||||||
|
height: 480,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(kRadiusCard),
|
||||||
|
child: HtmlElementView(viewType: _viewType),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: kSpace5),
|
||||||
|
_pointList(l),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// La liste double le viewer, elle ne le remplace pas : on ajoute et on supprime
|
||||||
|
/// ici, on place là-bas. Elle sert aussi de repère quand un point s'est retrouvé
|
||||||
|
/// derrière la maquette et qu'on ne le voit plus.
|
||||||
|
Widget _pointList(AppLocalizations l) {
|
||||||
|
final points = _dto.points ?? [];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(l.model3DPointsTitle(points.length.toString()),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
|
||||||
|
const Spacer(),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: _addPoint,
|
||||||
|
icon: const Icon(Icons.add, size: 16),
|
||||||
|
label: Text(l.model3DAddPoint),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (points.isEmpty)
|
||||||
|
Text(l.model3DNoPoint, style: kTextHint)
|
||||||
|
else
|
||||||
|
...points.map((point) {
|
||||||
|
final position = point.localTransform;
|
||||||
|
final placed = position != null &&
|
||||||
|
(position.x != 0 || position.y != 0 || position.z != 0);
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
dense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
leading: Icon(placed ? Icons.place : Icons.place_outlined,
|
||||||
|
size: 18, color: placed ? kPrimaryColor : kInk3),
|
||||||
|
title: Text(
|
||||||
|
ConfigurationExportTitle(point.title) ?? l.model3DNewPoint,
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
placed
|
||||||
|
? '${position.x.toStringAsFixed(2)} · ${position.y.toStringAsFixed(2)} · ${position.z.toStringAsFixed(2)}'
|
||||||
|
: l.model3DPointNotPlaced,
|
||||||
|
style: kTextHint,
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 18),
|
||||||
|
color: kInk3,
|
||||||
|
onPressed: () => _deletePoint(point),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Le titre du point dans la langue d'édition, ou le premier disponible.
|
||||||
|
String? ConfigurationExportTitle(List<TranslationDTO>? title) {
|
||||||
|
if (title == null || title.isEmpty) return null;
|
||||||
|
final fr = title.firstWhere((t) => t.language == 'FR',
|
||||||
|
orElse: () => title.first);
|
||||||
|
return (fr.value ?? '').isEmpty ? null : fr.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,6 +42,7 @@ import 'dart:html' as html;
|
|||||||
import 'SubSection/Weather/weather_config.dart';
|
import 'SubSection/Weather/weather_config.dart';
|
||||||
import 'SubSection/Event/event_config.dart';
|
import 'SubSection/Event/event_config.dart';
|
||||||
import 'SubSection/SectionParcours/section_parcours_config.dart';
|
import 'SubSection/SectionParcours/section_parcours_config.dart';
|
||||||
|
import 'SubSection/Scene3D/scene3d_config.dart';
|
||||||
|
|
||||||
class SectionDetailScreen extends StatefulWidget {
|
class SectionDetailScreen extends StatefulWidget {
|
||||||
final String id;
|
final String id;
|
||||||
@ -516,6 +517,13 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
|
|||||||
sectionDetailDTO = updatedParcours;
|
sectionDetailDTO = updatedParcours;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
case SectionType.Scene3D:
|
||||||
|
return Scene3DConfig(
|
||||||
|
initialValue: sectionDetailDTO as Scene3DDTO,
|
||||||
|
onChanged: (Scene3DDTO updatedModel) {
|
||||||
|
sectionDetailDTO = updatedModel;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,6 +571,9 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
|
|||||||
case SectionType.Parcours:
|
case SectionType.Parcours:
|
||||||
sectionDetailDTO = ParcoursDTO.fromJson(rawSectionData)!;
|
sectionDetailDTO = ParcoursDTO.fromJson(rawSectionData)!;
|
||||||
break;
|
break;
|
||||||
|
case SectionType.Scene3D:
|
||||||
|
sectionDetailDTO = Scene3DDTO.fromJson(rawSectionData)!;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:manager_app/Components/check_input_container.dart';
|
import 'package:manager_app/Components/check_input_container.dart';
|
||||||
import 'package:manager_app/Components/confirmation_dialog.dart';
|
import 'package:manager_app/Components/confirmation_dialog.dart';
|
||||||
|
import 'package:manager_app/Components/immersive_background_picker.dart';
|
||||||
import 'package:manager_app/Components/resource_input_container.dart';
|
import 'package:manager_app/Components/resource_input_container.dart';
|
||||||
import 'package:manager_app/Components/common_loader.dart';
|
import 'package:manager_app/Components/common_loader.dart';
|
||||||
import 'package:manager_app/Components/message_notification.dart';
|
import 'package:manager_app/Components/message_notification.dart';
|
||||||
@ -125,6 +126,10 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
|
|||||||
],
|
],
|
||||||
rail: [
|
rail: [
|
||||||
_cardImages(config, l),
|
_cardImages(config, l),
|
||||||
|
// Seulement pour les lieux qui ont l'add-on immersif : ailleurs, ce
|
||||||
|
// serait un réglage qu'on ne peut ni remplir ni voir.
|
||||||
|
if (managerCtx.instanceDTO?.hasImmersiveContent == true)
|
||||||
|
_cardBackground(config, l),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -261,6 +266,21 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Card Fond immersif ──
|
||||||
|
|
||||||
|
Widget _cardBackground(ConfigurationDTO config, AppLocalizations l) {
|
||||||
|
return Pane(
|
||||||
|
icon: Icons.panorama_photosphere,
|
||||||
|
title: l.backgroundTitle,
|
||||||
|
child: ImmersiveBackgroundPicker(
|
||||||
|
value: config.immersiveBackground,
|
||||||
|
title: l.backgroundTitle,
|
||||||
|
hint: l.backgroundHintConfiguration,
|
||||||
|
onChanged: (value) => setState(() => config.immersiveBackground = value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Card Sections ──
|
// ── Card Sections ──
|
||||||
|
|
||||||
Widget _cardSections(ConfigurationDTO config, AppContext appContext) {
|
Widget _cardSections(ConfigurationDTO config, AppContext appContext) {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const Map<String, List<SectionType>> _sectionFamilies = {
|
|||||||
SectionType.Map,
|
SectionType.Map,
|
||||||
SectionType.Parcours,
|
SectionType.Parcours,
|
||||||
SectionType.Event,
|
SectionType.Event,
|
||||||
|
SectionType.Scene3D,
|
||||||
],
|
],
|
||||||
"interactive": [
|
"interactive": [
|
||||||
SectionType.Quiz,
|
SectionType.Quiz,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import 'package:manager_app/Screens/ApiKeys/api_keys_screen.dart';
|
|||||||
import 'package:manager_app/Screens/Audit/audit_screen.dart';
|
import 'package:manager_app/Screens/Audit/audit_screen.dart';
|
||||||
import 'package:manager_app/Screens/Configurations/configurations_screen.dart';
|
import 'package:manager_app/Screens/Configurations/configurations_screen.dart';
|
||||||
import 'package:manager_app/Screens/Kiosk_devices/kiosk_screen.dart';
|
import 'package:manager_app/Screens/Kiosk_devices/kiosk_screen.dart';
|
||||||
|
import 'package:manager_app/Screens/Vr_devices/vr_screen.dart';
|
||||||
import 'package:manager_app/Screens/Resources/resources_screen.dart';
|
import 'package:manager_app/Screens/Resources/resources_screen.dart';
|
||||||
import 'package:manager_app/Screens/Statistics/statistics_screen.dart';
|
import 'package:manager_app/Screens/Statistics/statistics_screen.dart';
|
||||||
import 'package:manager_app/Screens/GuideIa/guide_ia_screen.dart';
|
import 'package:manager_app/Screens/GuideIa/guide_ia_screen.dart';
|
||||||
@ -138,6 +139,16 @@ class _MainScreenState extends State<MainScreen> {
|
|||||||
final planList = plans ?? [];
|
final planList = plans ?? [];
|
||||||
String? selectedPlanId = instanceDetail?.subscriptionPlanId;
|
String? selectedPlanId = instanceDetail?.subscriptionPlanId;
|
||||||
|
|
||||||
|
// Canaux et add-ons : ils vivent dans le même dialogue que le plan parce que
|
||||||
|
// c'est la même décision commerciale, prise au même moment. Les séparer
|
||||||
|
// obligerait à ouvrir deux écrans pour vendre une offre.
|
||||||
|
bool isMobile = instanceDetail?.isMobile ?? false;
|
||||||
|
bool isTablet = instanceDetail?.isTablet ?? false;
|
||||||
|
bool isWeb = instanceDetail?.isWeb ?? false;
|
||||||
|
bool isVR = instanceDetail?.isVR ?? false;
|
||||||
|
bool isAssistant = instanceDetail?.isAssistant ?? false;
|
||||||
|
bool hasImmersiveContent = instanceDetail?.hasImmersiveContent ?? false;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => StatefulBuilder(
|
builder: (dialogContext) => StatefulBuilder(
|
||||||
@ -145,29 +156,80 @@ class _MainScreenState extends State<MainScreen> {
|
|||||||
title: Text(AppLocalizations.of(context)!.planDialogTitle(inst.name ?? '')),
|
title: Text(AppLocalizations.of(context)!.planDialogTitle(inst.name ?? '')),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 360,
|
width: 360,
|
||||||
child: planList.isEmpty
|
child: SingleChildScrollView(
|
||||||
? Text(AppLocalizations.of(context)!.noPlansAvailable)
|
child: Column(
|
||||||
: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
RadioListTile<String?>(
|
if (planList.isEmpty)
|
||||||
title: Text(AppLocalizations.of(context)!.noPlan),
|
Text(AppLocalizations.of(context)!.noPlansAvailable)
|
||||||
value: null,
|
else ...[
|
||||||
groupValue: selectedPlanId,
|
RadioListTile<String?>(
|
||||||
onChanged: (v) => setDialogState(() => selectedPlanId = v),
|
title: Text(AppLocalizations.of(context)!.noPlan),
|
||||||
|
value: null,
|
||||||
|
groupValue: selectedPlanId,
|
||||||
|
onChanged: (v) => setDialogState(() => selectedPlanId = v),
|
||||||
|
),
|
||||||
|
...planList.map((plan) => RadioListTile<String?>(
|
||||||
|
title: Text(plan.name),
|
||||||
|
subtitle: Text(
|
||||||
|
'${_formatQuotaBytes(plan.storageQuotaBytes, unlimitedLabel: AppLocalizations.of(dialogContext)!.unlimitedStorage)} · ${plan.aiTokensPerMonth == 0 ? AppLocalizations.of(dialogContext)!.unlimitedAI : AppLocalizations.of(dialogContext)!.aiTokensPerMonth(_formatTokens(plan.aiTokensPerMonth))}',
|
||||||
|
style: const TextStyle(fontSize: 11),
|
||||||
),
|
),
|
||||||
...planList.map((plan) => RadioListTile<String?>(
|
value: plan.id,
|
||||||
title: Text(plan.name),
|
groupValue: selectedPlanId,
|
||||||
subtitle: Text(
|
onChanged: (v) => setDialogState(() => selectedPlanId = v),
|
||||||
'${_formatQuotaBytes(plan.storageQuotaBytes, unlimitedLabel: AppLocalizations.of(dialogContext)!.unlimitedStorage)} · ${plan.aiTokensPerMonth == 0 ? AppLocalizations.of(dialogContext)!.unlimitedAI : AppLocalizations.of(dialogContext)!.aiTokensPerMonth(_formatTokens(plan.aiTokensPerMonth))}',
|
)),
|
||||||
style: const TextStyle(fontSize: 11),
|
],
|
||||||
),
|
const Divider(height: 24),
|
||||||
value: plan.id,
|
Text(AppLocalizations.of(dialogContext)!.channelsSectionTitle,
|
||||||
groupValue: selectedPlanId,
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
|
||||||
onChanged: (v) => setDialogState(() => selectedPlanId = v),
|
SwitchListTile(
|
||||||
)),
|
dense: true,
|
||||||
],
|
title: Text(AppLocalizations.of(dialogContext)!.menuMobile),
|
||||||
|
value: isMobile,
|
||||||
|
onChanged: (v) => setDialogState(() => isMobile = v),
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(AppLocalizations.of(dialogContext)!.menuKiosk),
|
||||||
|
value: isTablet,
|
||||||
|
onChanged: (v) => setDialogState(() => isTablet = v),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(AppLocalizations.of(dialogContext)!.menuWeb),
|
||||||
|
value: isWeb,
|
||||||
|
onChanged: (v) => setDialogState(() => isWeb = v),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(AppLocalizations.of(dialogContext)!.menuVr),
|
||||||
|
subtitle: Text(AppLocalizations.of(dialogContext)!.channelVrHint,
|
||||||
|
style: const TextStyle(fontSize: 11)),
|
||||||
|
value: isVR,
|
||||||
|
onChanged: (v) => setDialogState(() => isVR = v),
|
||||||
|
),
|
||||||
|
const Divider(height: 24),
|
||||||
|
Text(AppLocalizations.of(dialogContext)!.addonsSectionTitle,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
|
||||||
|
SwitchListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(AppLocalizations.of(dialogContext)!.addonAssistant),
|
||||||
|
value: isAssistant,
|
||||||
|
onChanged: (v) => setDialogState(() => isAssistant = v),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(AppLocalizations.of(dialogContext)!.addonImmersive),
|
||||||
|
subtitle: Text(AppLocalizations.of(dialogContext)!.addonImmersiveHint,
|
||||||
|
style: const TextStyle(fontSize: 11)),
|
||||||
|
value: hasImmersiveContent,
|
||||||
|
onChanged: (v) => setDialogState(() => hasImmersiveContent = v),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -186,19 +248,38 @@ class _MainScreenState extends State<MainScreen> {
|
|||||||
isPushNotification: instanceDetail?.isPushNotification,
|
isPushNotification: instanceDetail?.isPushNotification,
|
||||||
hasStats: instanceDetail?.hasStats,
|
hasStats: instanceDetail?.hasStats,
|
||||||
hasAdvancedStats: instanceDetail?.hasAdvancedStats,
|
hasAdvancedStats: instanceDetail?.hasAdvancedStats,
|
||||||
isMobile: instanceDetail?.isMobile,
|
isMobile: isMobile,
|
||||||
isTablet: instanceDetail?.isTablet,
|
isTablet: isTablet,
|
||||||
isWeb: instanceDetail?.isWeb,
|
isWeb: isWeb,
|
||||||
isVR: instanceDetail?.isVR,
|
isVR: isVR,
|
||||||
isAssistant: instanceDetail?.isAssistant,
|
isAssistant: isAssistant,
|
||||||
|
hasImmersiveContent: hasImmersiveContent,
|
||||||
aiTokensThisMonth: instanceDetail?.aiTokensThisMonth,
|
aiTokensThisMonth: instanceDetail?.aiTokensThisMonth,
|
||||||
aiUsageMonthKey: instanceDetail?.aiUsageMonthKey,
|
aiUsageMonthKey: instanceDetail?.aiUsageMonthKey,
|
||||||
subscriptionPlanId: selectedPlanId ?? '',
|
subscriptionPlanId: selectedPlanId ?? '',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Activer le drapeau ne suffit pas : sans `ApplicationInstance`, le
|
||||||
|
// menu du canal s'affiche mais l'écran annonce « non configurée ».
|
||||||
|
// C'est exactement ce qui obligeait à passer par deux appels d'API
|
||||||
|
// à la main pour ouvrir le canal VR.
|
||||||
|
final created = await _ensureApplicationInstances(
|
||||||
|
managerCtx,
|
||||||
|
inst.id,
|
||||||
|
instanceDetail,
|
||||||
|
isMobile: isMobile,
|
||||||
|
isTablet: isTablet,
|
||||||
|
isWeb: isWeb,
|
||||||
|
isVR: isVR,
|
||||||
|
);
|
||||||
|
|
||||||
onSaved?.call();
|
onSaved?.call();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.planUpdated)));
|
final message = created > 0
|
||||||
|
? AppLocalizations.of(context)!.channelApplicationCreated
|
||||||
|
: AppLocalizations.of(context)!.planUpdated;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
@ -214,6 +295,56 @@ class _MainScreenState extends State<MainScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Crée les `ApplicationInstance` des canaux qu'on vient d'activer, et seulement
|
||||||
|
/// celles-là. Renvoie combien ont été créées.
|
||||||
|
///
|
||||||
|
/// ⚠️ **On ne supprime rien en désactivant un canal.** Une `ApplicationInstance`
|
||||||
|
/// porte les langues, les liens de configuration et l'historique des appareils :
|
||||||
|
/// la détruire parce qu'on a décoché une case ferait disparaître le travail du
|
||||||
|
/// client. Décocher masque le canal, ça ne l'efface pas — et le recocher le
|
||||||
|
/// retrouve intact.
|
||||||
|
Future<int> _ensureApplicationInstances(
|
||||||
|
ManagerAppContext managerCtx,
|
||||||
|
String instanceId,
|
||||||
|
InstanceDTO? detail, {
|
||||||
|
required bool isMobile,
|
||||||
|
required bool isTablet,
|
||||||
|
required bool isWeb,
|
||||||
|
required bool isVR,
|
||||||
|
}) async {
|
||||||
|
final existing = detail?.applicationInstanceDTOs ?? [];
|
||||||
|
final wanted = <AppType, bool>{
|
||||||
|
AppType.Mobile: isMobile,
|
||||||
|
AppType.Tablet: isTablet,
|
||||||
|
AppType.Web: isWeb,
|
||||||
|
AppType.VR: isVR,
|
||||||
|
};
|
||||||
|
|
||||||
|
var created = 0;
|
||||||
|
|
||||||
|
for (final entry in wanted.entries) {
|
||||||
|
if (!entry.value) continue;
|
||||||
|
if (existing.any((ai) => ai.appType == entry.key)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await managerCtx.clientAPI!.applicationInstanceApi!.applicationInstanceCreate(
|
||||||
|
ApplicationInstanceDTO(
|
||||||
|
instanceId: instanceId,
|
||||||
|
appType: entry.key,
|
||||||
|
// Le français comme langue de départ : le manager est livré en FR/EN/NL
|
||||||
|
// et une application sans aucune langue n'affiche rien.
|
||||||
|
languages: ['FR'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
created++;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[SuperAdmin] création ApplicationInstance ${entry.key} : $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
static String _formatQuotaBytes(int? bytes, {required String unlimitedLabel}) {
|
static String _formatQuotaBytes(int? bytes, {required String unlimitedLabel}) {
|
||||||
if (bytes == null || bytes == 0) return unlimitedLabel;
|
if (bytes == null || bytes == 0) return unlimitedLabel;
|
||||||
if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(0)} MB';
|
if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(0)} MB';
|
||||||
@ -741,7 +872,7 @@ class _MainScreenState extends State<MainScreen> {
|
|||||||
if (applicationInstanceVR == null) return Center(child: Text(AppLocalizations.of(context)!.vrAppNotConfigured));
|
if (applicationInstanceVR == null) return Center(child: Text(AppLocalizations.of(context)!.vrAppNotConfigured));
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(AppLocalizations.of(context)!.vrAppNotConfigured)
|
child: VrScreen(applicationInstanceDTO: applicationInstanceVR)
|
||||||
);
|
);
|
||||||
case 'configurations' :
|
case 'configurations' :
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|||||||
@ -11,6 +11,10 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
getElementForResource(BuildContext context, dynamic resourceDTO, AppContext appContext) {
|
getElementForResource(BuildContext context, dynamic resourceDTO, AppContext appContext) {
|
||||||
switch(resourceDTO.type) {
|
switch(resourceDTO.type) {
|
||||||
|
// Une 360 est une image equirectangulaire : l'apercu plat est deforme, mais
|
||||||
|
// il montre la bonne ressource. Sans ce cas, le panneau de la mediatheque
|
||||||
|
// restait vide sans rien dire.
|
||||||
|
case ResourceType.Image360:
|
||||||
case ResourceType.Image:
|
case ResourceType.Image:
|
||||||
return Image.network(
|
return Image.network(
|
||||||
resourceDTO.url,
|
resourceDTO.url,
|
||||||
@ -31,7 +35,6 @@ getElementForResource(BuildContext context, dynamic resourceDTO, AppContext appC
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
case ResourceType.ImageUrl:
|
case ResourceType.ImageUrl:
|
||||||
return Image.network(
|
return Image.network(
|
||||||
resourceDTO.url,
|
resourceDTO.url,
|
||||||
@ -52,7 +55,6 @@ getElementForResource(BuildContext context, dynamic resourceDTO, AppContext appC
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
case ResourceType.Audio:
|
case ResourceType.Audio:
|
||||||
return AudioPlayerFloatingContainer(audioBytes: null, resourceURl: resourceDTO.url, isAuto: true);
|
return AudioPlayerFloatingContainer(audioBytes: null, resourceURl: resourceDTO.url, isAuto: true);
|
||||||
/*return FutureBuilder(
|
/*return FutureBuilder(
|
||||||
@ -82,6 +84,7 @@ getElementForResource(BuildContext context, dynamic resourceDTO, AppContext appC
|
|||||||
}
|
}
|
||||||
);*/
|
);*/
|
||||||
//return Text("Fichier audio - aucune visualisation possible");
|
//return Text("Fichier audio - aucune visualisation possible");
|
||||||
|
case ResourceType.Video360:
|
||||||
case ResourceType.Video:
|
case ResourceType.Video:
|
||||||
if(resourceDTO.url == null) {
|
if(resourceDTO.url == null) {
|
||||||
return Center(child: Text(AppLocalizations.of(context)!.errorLoadingVideo));
|
return Center(child: Text(AppLocalizations.of(context)!.errorLoadingVideo));
|
||||||
@ -107,6 +110,11 @@ getElementForResource(BuildContext context, dynamic resourceDTO, AppContext appC
|
|||||||
|
|
||||||
case ResourceType.JsonUrl:
|
case ResourceType.JsonUrl:
|
||||||
return SelectableText(resourceDTO.url!);
|
return SelectableText(resourceDTO.url!);
|
||||||
|
|
||||||
|
case ResourceType.Model3D:
|
||||||
|
return Center(
|
||||||
|
child: Text(AppLocalizations.of(context)!.mediaModel3DNoPreview,
|
||||||
|
textAlign: TextAlign.center));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,9 @@ const Map<ResourceType, String> _extensionByType = {
|
|||||||
ResourceType.Pdf: '.pdf',
|
ResourceType.Pdf: '.pdf',
|
||||||
ResourceType.Json: '.json',
|
ResourceType.Json: '.json',
|
||||||
ResourceType.JsonUrl: '.json',
|
ResourceType.JsonUrl: '.json',
|
||||||
|
ResourceType.Image360: '.jpg',
|
||||||
|
ResourceType.Video360: '.mp4',
|
||||||
|
ResourceType.Model3D: '.glb',
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Type d'une ressource locale, déduit de son extension. Un seul endroit : le
|
/// Type d'une ressource locale, déduit de son extension. Un seul endroit : le
|
||||||
@ -37,6 +40,10 @@ ResourceType? resourceTypeForExtension(String? extension) {
|
|||||||
return ResourceType.Pdf;
|
return ResourceType.Pdf;
|
||||||
case 'json':
|
case 'json':
|
||||||
return ResourceType.Json;
|
return ResourceType.Json;
|
||||||
|
// Un .glb n'est jamais autre chose qu'un modèle 3D — contrairement à un .jpg,
|
||||||
|
// qui peut être plat ou équirectangulaire sans que le fichier le dise.
|
||||||
|
case 'glb':
|
||||||
|
return ResourceType.Model3D;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -61,6 +68,8 @@ String mimeTypeForExtension(String? extension) {
|
|||||||
return 'application/pdf';
|
return 'application/pdf';
|
||||||
case 'json':
|
case 'json':
|
||||||
return 'application/json';
|
return 'application/json';
|
||||||
|
case 'glb':
|
||||||
|
return 'model/gltf-binary';
|
||||||
default:
|
default:
|
||||||
return 'application/octet-stream';
|
return 'application/octet-stream';
|
||||||
}
|
}
|
||||||
@ -77,6 +86,9 @@ String resourceTypeLabel(AppLocalizations l, ResourceType? type) {
|
|||||||
case ResourceType.Pdf: return l.resourceTypePdf;
|
case ResourceType.Pdf: return l.resourceTypePdf;
|
||||||
case ResourceType.Json: return l.resourceTypeJson;
|
case ResourceType.Json: return l.resourceTypeJson;
|
||||||
case ResourceType.JsonUrl: return l.resourceTypeJsonUrl;
|
case ResourceType.JsonUrl: return l.resourceTypeJsonUrl;
|
||||||
|
case ResourceType.Image360: return l.resourceTypeImage360;
|
||||||
|
case ResourceType.Video360: return l.resourceTypeVideo360;
|
||||||
|
case ResourceType.Model3D: return l.resourceTypeModel3D;
|
||||||
default: return '—';
|
default: return '—';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -283,7 +283,8 @@ class _ResourcesScreenState extends State<ResourcesScreen> {
|
|||||||
setState(() => isUploading = true);
|
setState(() => isUploading = true);
|
||||||
try {
|
try {
|
||||||
final compressed = ImageCompressor.compress(
|
final compressed = ImageCompressor.compress(
|
||||||
file.bytes!, file.extension, mimeTypeForExtension(file.extension));
|
file.bytes!, file.extension, mimeTypeForExtension(file.extension),
|
||||||
|
type: resource.type);
|
||||||
|
|
||||||
final storage = FirebaseStorage.instance;
|
final storage = FirebaseStorage.instance;
|
||||||
final ref = storage
|
final ref = storage
|
||||||
@ -408,7 +409,8 @@ Future<void> create(
|
|||||||
// se fait sur sizeBytes à la création, il doit voir la taille réellement
|
// se fait sur sizeBytes à la création, il doit voir la taille réellement
|
||||||
// téléversée et non celle du fichier d'origine.
|
// téléversée et non celle du fichier d'origine.
|
||||||
final compressed = ImageCompressor.compress(platformFile.bytes!,
|
final compressed = ImageCompressor.compress(platformFile.bytes!,
|
||||||
platformFile.extension, mimeTypeForExtension(platformFile.extension));
|
platformFile.extension, mimeTypeForExtension(platformFile.extension),
|
||||||
|
type: entry.type);
|
||||||
|
|
||||||
final resourceDTO = ResourceDTO(
|
final resourceDTO = ResourceDTO(
|
||||||
label: platformFile.name,
|
label: platformFile.name,
|
||||||
|
|||||||
316
lib/Screens/Vr_devices/vr_devices_tab.dart
Normal file
316
lib/Screens/Vr_devices/vr_devices_tab.dart
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
import 'package:manager_app/Components/common_loader.dart';
|
||||||
|
import 'package:manager_app/Components/message_notification.dart';
|
||||||
|
import 'package:manager_app/Models/managerContext.dart';
|
||||||
|
import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart';
|
||||||
|
import 'package:manager_app/Screens/Kiosk_devices/change_device_info_modal.dart';
|
||||||
|
import 'package:manager_app/app_context.dart';
|
||||||
|
import 'package:manager_app/constants.dart';
|
||||||
|
import 'package:manager_app/l10n/app_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
/// Flotte de casques. Comme pour le kiosk, la grille liste les liens de
|
||||||
|
/// configuration de l'application VR — un lien existe avant qu'un casque ne s'y
|
||||||
|
/// soit appairé, et l'appairage se fait depuis le casque avec le code PIN.
|
||||||
|
class VrDevicesTab extends StatefulWidget {
|
||||||
|
final ApplicationInstanceDTO applicationInstanceDTO;
|
||||||
|
const VrDevicesTab({super.key, required this.applicationInstanceDTO});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VrDevicesTab> createState() => _VrDevicesTabState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VrDevicesTabState extends State<VrDevicesTab> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
final appContext = Provider.of<AppContext>(context);
|
||||||
|
final ManagerAppContext managerAppContext = appContext.getContext();
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (managerAppContext.pinCode != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: kSpace5),
|
||||||
|
child: Text(
|
||||||
|
l.pinCode(managerAppContext.pinCode.toString()),
|
||||||
|
style: const TextStyle(fontSize: 25.0, fontWeight: FontWeight.w300),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: kSpace3),
|
||||||
|
child: Text(l.headsets),
|
||||||
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: getAppConfigurationLink(appContext, widget.applicationInstanceDTO),
|
||||||
|
builder: (context, AsyncSnapshot<dynamic> snapshot) {
|
||||||
|
List<AppConfigurationLinkDTO>? links = snapshot.data;
|
||||||
|
|
||||||
|
if (snapshot.connectionState != ConnectionState.done) {
|
||||||
|
return const SizedBox(height: 160, child: Center(child: CommonLoader()));
|
||||||
|
}
|
||||||
|
if (links == null) return Text(l.noData);
|
||||||
|
|
||||||
|
return GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 260,
|
||||||
|
childAspectRatio: 1.25,
|
||||||
|
mainAxisSpacing: 10,
|
||||||
|
crossAxisSpacing: 10,
|
||||||
|
),
|
||||||
|
itemCount: links.length,
|
||||||
|
itemBuilder: (context, index) => _HeadsetCard(link: links[index]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HeadsetCard extends StatefulWidget {
|
||||||
|
final AppConfigurationLinkDTO link;
|
||||||
|
const _HeadsetCard({required this.link});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_HeadsetCard> createState() => _HeadsetCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HeadsetCardState extends State<_HeadsetCard> {
|
||||||
|
bool _hovered = false;
|
||||||
|
|
||||||
|
/// Batterie, version et dernier vu ne sont pas dans `DeviceDTO` : ils ne
|
||||||
|
/// viennent que du détail, un appel par casque. Acceptable sur une flotte de
|
||||||
|
/// bornes, qui se compte en unités.
|
||||||
|
Future<DeviceDetailDTO?>? _detail;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final device = widget.link.device;
|
||||||
|
if (device?.id != null) {
|
||||||
|
final ctx = Provider.of<AppContext>(context, listen: false).getContext() as ManagerAppContext;
|
||||||
|
_detail = ctx.clientAPI!.deviceApi!.deviceGetDetail(device!.id!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
final device = widget.link.device;
|
||||||
|
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _hovered = true),
|
||||||
|
onExit: (_) => setState(() => _hovered = false),
|
||||||
|
child: AnimatedScale(
|
||||||
|
scale: _hovered ? 1.03 : 1.0,
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kTextLightColor,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: kSecond,
|
||||||
|
spreadRadius: _hovered ? 1 : 0.5,
|
||||||
|
blurRadius: _hovered ? 10 : 5,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: device == null ? _unpaired(l) : _paired(l, device),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _unpaired(AppLocalizations l) {
|
||||||
|
final title = widget.link.configuration?.label ?? '';
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.visibility_outlined, color: kBodyTextColor.withValues(alpha: 0.5), size: 34),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
l.noHeadsetPaired,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: kBodyTextColor),
|
||||||
|
),
|
||||||
|
if (title.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(fontSize: 11, color: kBodyTextColor.withValues(alpha: 0.6)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _paired(AppLocalizations l, DeviceDTO device) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
device.name ?? device.identifier ?? '',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: device.connected == true ? Colors.green : kError,
|
||||||
|
borderRadius: BorderRadius.circular(25.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: kSpace2),
|
||||||
|
Text(
|
||||||
|
device.configuration ?? l.noConfiguration,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
FutureBuilder(
|
||||||
|
future: _detail,
|
||||||
|
builder: (context, AsyncSnapshot<DeviceDetailDTO?> snapshot) {
|
||||||
|
final detail = snapshot.data;
|
||||||
|
if (detail == null) return const SizedBox();
|
||||||
|
final silent = _silentFor(detail.lastSeen);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (silent != null) _silentBanner(l, silent),
|
||||||
|
if (detail.batteryLevel != null)
|
||||||
|
_infoLine(Icons.battery_std_outlined, '${l.headsetBattery} ${detail.batteryLevel}'),
|
||||||
|
if (detail.appVersion != null)
|
||||||
|
_infoLine(Icons.info_outline, '${l.headsetAppVersion} ${detail.appVersion}'),
|
||||||
|
_infoLine(
|
||||||
|
Icons.schedule,
|
||||||
|
detail.lastSeen == null
|
||||||
|
? l.headsetNeverSeen
|
||||||
|
: l.headsetLastSeen(DateFormat('dd/MM/yyyy HH:mm').format(detail.lastSeen!)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
color: kPrimaryColor,
|
||||||
|
onPressed: () => _edit(l, device),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Un casque qui bat toutes les 3 minutes et qu'on n'a plus entendu depuis
|
||||||
|
/// **une heure** n'est pas « en veille » : il est éteint, hors wifi, ou son app
|
||||||
|
/// est tombée. Le seuil est volontairement large — vingt battements manqués —
|
||||||
|
/// pour qu'une alerte veuille dire quelque chose.
|
||||||
|
static const _silenceThreshold = Duration(hours: 1);
|
||||||
|
|
||||||
|
Duration? _silentFor(DateTime? lastSeen) {
|
||||||
|
if (lastSeen == null) return null;
|
||||||
|
final silence = DateTime.now().difference(lastSeen.toLocal());
|
||||||
|
return silence >= _silenceThreshold ? silence : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// La pastille verte dit « connecté » parce que le serveur l'a écrit au dernier
|
||||||
|
/// battement — elle ne sait pas qu'il date d'hier. C'est ce mensonge-là que cette
|
||||||
|
/// bannière corrige.
|
||||||
|
Widget _silentBanner(AppLocalizations l, Duration silence) {
|
||||||
|
final label = silence.inHours >= 24
|
||||||
|
? l.headsetSilentDays(silence.inDays.toString())
|
||||||
|
: l.headsetSilentHours(silence.inHours.toString());
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 4),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: kSpace2, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kError.withValues(alpha: 0.12),
|
||||||
|
borderRadius: BorderRadius.circular(kRadiusInput),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.warning_amber_rounded, size: 13, color: kError),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Flexible(
|
||||||
|
child: Text(label,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(fontSize: 11, color: kError, fontWeight: FontWeight.w600)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _infoLine(IconData icon, String label) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 13, color: kInk3),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 11, color: kInk3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _edit(AppLocalizations l, DeviceDTO device) {
|
||||||
|
final appContext = Provider.of<AppContext>(context, listen: false);
|
||||||
|
|
||||||
|
showChangeInfo(
|
||||||
|
l.updateHeadsetBtn,
|
||||||
|
device,
|
||||||
|
widget.link,
|
||||||
|
(DeviceDTO outputDevice, AppConfigurationLinkDTO outputLink) async {
|
||||||
|
final ctx = appContext.getContext() as ManagerAppContext;
|
||||||
|
await ctx.clientAPI!.deviceApi!.deviceUpdateMainInfos(outputDevice);
|
||||||
|
outputLink.configurationId = outputDevice.configurationId;
|
||||||
|
await ctx.clientAPI!.applicationInstanceApi!.applicationInstanceUpdateApplicationLink(outputLink);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
showNotification(kSuccess, kWhite, l.appUpdatedSuccess, context, null);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
context,
|
||||||
|
appContext,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
128
lib/Screens/Vr_devices/vr_screen.dart
Normal file
128
lib/Screens/Vr_devices/vr_screen.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart';
|
||||||
|
import 'package:manager_app/Screens/Vr_devices/vr_devices_tab.dart';
|
||||||
|
import 'package:manager_app/Components/immersive_background_picker.dart';
|
||||||
|
import 'package:manager_app/Components/message_notification.dart';
|
||||||
|
import 'package:manager_app/app_context.dart';
|
||||||
|
import 'package:manager_app/constants.dart';
|
||||||
|
import 'package:manager_app/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
/// Le canal VR a les deux faces des autres canaux — ce qu'on diffuse et sur quoi
|
||||||
|
/// on le diffuse — mais contrairement au kiosk la flotte porte des informations
|
||||||
|
/// propres au casque (batterie, version, dernier vu) qui n'ont pas leur place
|
||||||
|
/// dans l'écran de configuration.
|
||||||
|
class VrScreen extends StatefulWidget {
|
||||||
|
final ApplicationInstanceDTO applicationInstanceDTO;
|
||||||
|
const VrScreen({super.key, required this.applicationInstanceDTO});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VrScreen> createState() => _VrScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VrScreenState extends State<VrScreen> {
|
||||||
|
int _tab = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_tabs(l),
|
||||||
|
const SizedBox(height: kSpace7),
|
||||||
|
Expanded(
|
||||||
|
child: _tab == 0
|
||||||
|
? Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: AppConfigurationLinkScreen(
|
||||||
|
applicationInstanceDTO: widget.applicationInstanceDTO,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_backgroundCard(l),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: VrDevicesTab(
|
||||||
|
applicationInstanceDTO: widget.applicationInstanceDTO,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Le fond du menu d'accueil du casque. Il vit ici et pas dans l'écran de
|
||||||
|
/// configuration partagé : celui-ci sert aussi au mobile, au web et au kiosk, qui
|
||||||
|
/// n'ont pas de menu à décorer.
|
||||||
|
///
|
||||||
|
/// Enregistré à chaque changement, comme l'image principale juste au-dessus — c'est
|
||||||
|
/// la convention de cet écran, et deux comportements d'enregistrement sur la même
|
||||||
|
/// page se paient en contenus perdus.
|
||||||
|
Widget _backgroundCard(AppLocalizations l) {
|
||||||
|
final appContext = Provider.of<AppContext>(context, listen: false);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(kSpace5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kSurface,
|
||||||
|
border: Border(top: BorderSide(color: kLine)),
|
||||||
|
),
|
||||||
|
child: ImmersiveBackgroundPicker(
|
||||||
|
value: widget.applicationInstanceDTO.immersiveBackground,
|
||||||
|
title: l.backgroundTitle,
|
||||||
|
hint: l.backgroundHintMenu,
|
||||||
|
onChanged: (value) async {
|
||||||
|
setState(() => widget.applicationInstanceDTO.immersiveBackground = value);
|
||||||
|
|
||||||
|
final updated = await updateApplicationInstance(
|
||||||
|
appContext, widget.applicationInstanceDTO);
|
||||||
|
|
||||||
|
if (updated != null && mounted) {
|
||||||
|
showNotification(kSuccess, kWhite, l.appUpdatedSuccess, context, null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tabs(AppLocalizations l) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: kLine))),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_tabButton(l.vrTabConfig, 0),
|
||||||
|
_tabButton(l.vrTabHeadsets, 1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tabButton(String label, int index) {
|
||||||
|
final selected = _tab == index;
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => setState(() => _tab = index),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 9),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: selected ? kBrand : Colors.transparent,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: selected ? kInk : kInk3,
|
||||||
|
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,6 +32,9 @@ class Client {
|
|||||||
SectionMapApi? _sectionMapApi;
|
SectionMapApi? _sectionMapApi;
|
||||||
SectionMapApi? get sectionMapApi => _sectionMapApi;
|
SectionMapApi? get sectionMapApi => _sectionMapApi;
|
||||||
|
|
||||||
|
SectionScene3DApi? _sectionScene3DApi;
|
||||||
|
SectionScene3DApi? get sectionScene3DApi => _sectionScene3DApi;
|
||||||
|
|
||||||
SectionQuizApi? _sectionQuizApi;
|
SectionQuizApi? _sectionQuizApi;
|
||||||
SectionQuizApi? get sectionQuizApi => _sectionQuizApi;
|
SectionQuizApi? get sectionQuizApi => _sectionQuizApi;
|
||||||
|
|
||||||
@ -76,6 +79,7 @@ class Client {
|
|||||||
_resourceApi = ResourceApi(_apiClient);
|
_resourceApi = ResourceApi(_apiClient);
|
||||||
_deviceApi = DeviceApi(_apiClient);
|
_deviceApi = DeviceApi(_apiClient);
|
||||||
_sectionMapApi = SectionMapApi(_apiClient);
|
_sectionMapApi = SectionMapApi(_apiClient);
|
||||||
|
_sectionScene3DApi = SectionScene3DApi(_apiClient);
|
||||||
_sectionQuizApi = SectionQuizApi(_apiClient);
|
_sectionQuizApi = SectionQuizApi(_apiClient);
|
||||||
_sectionAgendaApi = SectionAgendaApi(_apiClient);
|
_sectionAgendaApi = SectionAgendaApi(_apiClient);
|
||||||
_sectionEventApi = SectionEventApi(_apiClient);
|
_sectionEventApi = SectionEventApi(_apiClient);
|
||||||
|
|||||||
@ -203,6 +203,12 @@ const List<ResourceType> resource_types = [
|
|||||||
ResourceType.Pdf,
|
ResourceType.Pdf,
|
||||||
ResourceType.Json,
|
ResourceType.Json,
|
||||||
ResourceType.JsonUrl,
|
ResourceType.JsonUrl,
|
||||||
|
// Les médias immersifs sont dans la même liste que les autres : c'est elle qui
|
||||||
|
// alimente les facettes de la Médiathèque. Absents d'ici, ils existaient en base
|
||||||
|
// mais restaient introuvables — pas de filtre « mes 360 », pas de compteur.
|
||||||
|
ResourceType.Image360,
|
||||||
|
ResourceType.Video360,
|
||||||
|
ResourceType.Model3D,
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -1005,7 +1005,7 @@
|
|||||||
"@_mediatheque_dialogue": {},
|
"@_mediatheque_dialogue": {},
|
||||||
"mediaAddTitle": "Add a resource",
|
"mediaAddTitle": "Add a resource",
|
||||||
"mediaDropZone": "Drop your files here or click to browse",
|
"mediaDropZone": "Drop your files here or click to browse",
|
||||||
"mediaDropFormats": "JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON",
|
"mediaDropFormats": "JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON, GLB",
|
||||||
"mediaOrPasteUrl": "or paste a URL",
|
"mediaOrPasteUrl": "or paste a URL",
|
||||||
"mediaUrlHint": "https://… image, YouTube video or JSON file",
|
"mediaUrlHint": "https://… image, YouTube video or JSON file",
|
||||||
"mediaUrlInvalid": "This URL is not valid",
|
"mediaUrlInvalid": "This URL is not valid",
|
||||||
@ -1052,10 +1052,59 @@
|
|||||||
"menuKiosk": "Kiosk",
|
"menuKiosk": "Kiosk",
|
||||||
"menuWeb": "Web",
|
"menuWeb": "Web",
|
||||||
"menuVr": "VR",
|
"menuVr": "VR",
|
||||||
|
"channelsSectionTitle": "Channels",
|
||||||
|
"channelVrHint": "Also creates the VR application if missing",
|
||||||
|
"addonsSectionTitle": "Add-ons",
|
||||||
|
"addonAssistant": "AI assistant",
|
||||||
|
"backgroundTitle": "Immersive background",
|
||||||
|
"backgroundHintConfiguration": "The setting this visit takes place in, inside the headset. Without it, content floats in the dark.",
|
||||||
|
"backgroundHintMenu": "The setting of the headset's home menu, before the visitor has chosen anything.",
|
||||||
|
"backgroundResourceLabel": "Background media",
|
||||||
|
"backgroundKindPano": "360° panorama: shown all around the visitor, in the headset.",
|
||||||
|
"backgroundKindVideo360": "360° video: looped and muted. ⚠️ On a kiosk, the decoder runs all day.",
|
||||||
|
"backgroundKindScene3D": "3D setting: in the contract, not yet shown as a background by the headset.",
|
||||||
|
"backgroundFallbackLabel": "Fallback image",
|
||||||
|
"backgroundFallbackHint": "Shown by web, mobile and tablets, which cannot render a 360. Without it, those channels show no background at all.",
|
||||||
|
"backgroundRemove": "Remove background",
|
||||||
|
"addonImmersive": "Immersive content",
|
||||||
|
"addonImmersiveHint": "360° images, 360° video and 3D models. Turning it on adds 100 GB to the plan's storage quota, and turning it off removes them — a 5-minute 360° video weighs gigabytes.",
|
||||||
|
"channelApplicationCreated": "Channel enabled and application created",
|
||||||
"noAppConfigured": "No application configured on this instance",
|
"noAppConfigured": "No application configured on this instance",
|
||||||
"mobileAppNotConfigured": "Mobile application not configured",
|
"mobileAppNotConfigured": "Mobile application not configured",
|
||||||
"kioskAppNotConfigured": "Kiosk application not configured",
|
"kioskAppNotConfigured": "Kiosk application not configured",
|
||||||
"vrAppNotConfigured": "VR application not configured",
|
"vrAppNotConfigured": "VR application not configured",
|
||||||
|
"vrTabConfig": "Configuration",
|
||||||
|
"vrTabHeadsets": "Headsets",
|
||||||
|
"headsets": "Headsets",
|
||||||
|
"noHeadsetPaired": "No headset paired",
|
||||||
|
"headsetBattery": "Battery",
|
||||||
|
"headsetAppVersion": "Version",
|
||||||
|
"headsetLastSeen": "Seen on {date}",
|
||||||
|
"@headsetLastSeen": {
|
||||||
|
"placeholders": {
|
||||||
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headsetNeverSeen": "Never seen",
|
||||||
|
"headsetSilentHours": "Silent for {count} h",
|
||||||
|
"@headsetSilentHours": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headsetSilentDays": "Silent for {count} d",
|
||||||
|
"@headsetSilentDays": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updateHeadsetBtn": "Update headset",
|
||||||
"titleColonLabel": "Title:",
|
"titleColonLabel": "Title:",
|
||||||
"titleModalLabel": "Title",
|
"titleModalLabel": "Title",
|
||||||
"descriptionColonLabel": "Description:",
|
"descriptionColonLabel": "Description:",
|
||||||
@ -1386,6 +1435,34 @@
|
|||||||
"resourceTypePdf": "PDF",
|
"resourceTypePdf": "PDF",
|
||||||
"resourceTypeJson": "JSON",
|
"resourceTypeJson": "JSON",
|
||||||
"resourceTypeJsonUrl": "JSON (URL)",
|
"resourceTypeJsonUrl": "JSON (URL)",
|
||||||
|
"resourceTypeImage360": "360° image",
|
||||||
|
"resourceTypeVideo360": "360° video",
|
||||||
|
"resourceTypeModel3D": "3D model",
|
||||||
|
"model3DResourceLabel": "3D model",
|
||||||
|
"model3DPickModelFirst": "Pick a 3D model from the media library first.",
|
||||||
|
"model3DPlacePoints": "Drag the points of interest onto the model. Their position is saved with the section.",
|
||||||
|
"scene3DModeTitle": "What the visitor does with the scene",
|
||||||
|
"scene3DModeAsset": "Object",
|
||||||
|
"scene3DModeScene": "Place",
|
||||||
|
"scene3DModeAssetHint": "An object placed in front of the visitor, examined from every side — a sword, a collection piece. Points turn with the object.",
|
||||||
|
"scene3DModeSceneHint": "A place the visitor stands in and looks around — a reconstructed room. Points stay where they are.",
|
||||||
|
"model3DPointsTitle": "Points of interest ({count})",
|
||||||
|
"@model3DPointsTitle": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model3DAddPoint": "Add a point",
|
||||||
|
"model3DNoPoint": "No point on this model yet.",
|
||||||
|
"model3DNewPoint": "New point",
|
||||||
|
"model3DPointNotPlaced": "Not placed yet",
|
||||||
|
"model3DPointSaveError": "The position could not be saved",
|
||||||
|
"sectionTypeScene3D": "3D scene",
|
||||||
|
"sectionTypeDescScene3D": "A 3D object or setting the visitor explores, with points of interest placed on it. Shown in the VR headset.",
|
||||||
|
"mediaModel3DNoPreview": "No preview here: a 3D model is viewed in a 3D scene section, and in the headset.",
|
||||||
|
"mediaToggle360Hint": "Tap to switch between flat and 360° media — a 360° image is shown immersively in the headset and is never compressed",
|
||||||
"minutesAbbr": "min",
|
"minutesAbbr": "min",
|
||||||
"secondsAbbr": "sec",
|
"secondsAbbr": "sec",
|
||||||
"metersAbbr": "m",
|
"metersAbbr": "m",
|
||||||
|
|||||||
@ -1005,7 +1005,7 @@
|
|||||||
"@_mediatheque_dialogue": {},
|
"@_mediatheque_dialogue": {},
|
||||||
"mediaAddTitle": "Ajouter une ressource",
|
"mediaAddTitle": "Ajouter une ressource",
|
||||||
"mediaDropZone": "Glissez vos fichiers ici ou cliquez pour parcourir",
|
"mediaDropZone": "Glissez vos fichiers ici ou cliquez pour parcourir",
|
||||||
"mediaDropFormats": "JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON",
|
"mediaDropFormats": "JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON, GLB",
|
||||||
"mediaOrPasteUrl": "ou coller une URL",
|
"mediaOrPasteUrl": "ou coller une URL",
|
||||||
"mediaUrlHint": "https://… image, vidéo YouTube ou fichier JSON",
|
"mediaUrlHint": "https://… image, vidéo YouTube ou fichier JSON",
|
||||||
"mediaUrlInvalid": "Cette URL n'est pas valide",
|
"mediaUrlInvalid": "Cette URL n'est pas valide",
|
||||||
@ -1052,10 +1052,59 @@
|
|||||||
"menuKiosk": "Kiosk",
|
"menuKiosk": "Kiosk",
|
||||||
"menuWeb": "Web",
|
"menuWeb": "Web",
|
||||||
"menuVr": "VR",
|
"menuVr": "VR",
|
||||||
|
"channelsSectionTitle": "Canaux",
|
||||||
|
"channelVrHint": "Crée aussi l'application VR si elle n'existe pas",
|
||||||
|
"addonsSectionTitle": "Add-ons",
|
||||||
|
"addonAssistant": "Assistant IA",
|
||||||
|
"backgroundTitle": "Fond immersif",
|
||||||
|
"backgroundHintConfiguration": "Le décor dans lequel cette visite se déroule dans le casque. Sans lui, les contenus flottent dans le noir.",
|
||||||
|
"backgroundHintMenu": "Le décor du menu d'accueil du casque, avant que le visiteur ait choisi quoi que ce soit.",
|
||||||
|
"backgroundResourceLabel": "Média du fond",
|
||||||
|
"backgroundKindPano": "Panorama 360° : affiché tout autour du visiteur, dans le casque.",
|
||||||
|
"backgroundKindVideo360": "Vidéo 360° : jouée en boucle et sans son. ⚠️ Sur une borne, le décodeur tourne toute la journée.",
|
||||||
|
"backgroundKindScene3D": "Décor 3D : prévu par le contrat, pas encore affiché en fond par le casque.",
|
||||||
|
"backgroundFallbackLabel": "Image de repli",
|
||||||
|
"backgroundFallbackHint": "Affichée par le web, le mobile et les tablettes, qui ne savent pas rendre une 360. Sans elle, ces canaux n'affichent aucun fond.",
|
||||||
|
"backgroundRemove": "Retirer le fond",
|
||||||
|
"addonImmersive": "Contenu immersif",
|
||||||
|
"addonImmersiveHint": "Image 360, vidéo 360 et modèles 3D. L'activation ajoute 100 Go au quota de stockage du plan, et les retire à la désactivation — une vidéo 360 de 5 min pèse des Go.",
|
||||||
|
"channelApplicationCreated": "Canal activé et application créée",
|
||||||
"noAppConfigured": "Aucune application configurée sur cette instance",
|
"noAppConfigured": "Aucune application configurée sur cette instance",
|
||||||
"mobileAppNotConfigured": "Application mobile non configurée",
|
"mobileAppNotConfigured": "Application mobile non configurée",
|
||||||
"kioskAppNotConfigured": "Application kiosk non configurée",
|
"kioskAppNotConfigured": "Application kiosk non configurée",
|
||||||
"vrAppNotConfigured": "Application VR non configurée",
|
"vrAppNotConfigured": "Application VR non configurée",
|
||||||
|
"vrTabConfig": "Configuration",
|
||||||
|
"vrTabHeadsets": "Casques",
|
||||||
|
"headsets": "Casques",
|
||||||
|
"noHeadsetPaired": "Aucun casque appairé",
|
||||||
|
"headsetBattery": "Batterie",
|
||||||
|
"headsetAppVersion": "Version",
|
||||||
|
"headsetLastSeen": "Vu le {date}",
|
||||||
|
"@headsetLastSeen": {
|
||||||
|
"placeholders": {
|
||||||
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headsetNeverSeen": "Jamais vu",
|
||||||
|
"headsetSilentHours": "Muet depuis {count} h",
|
||||||
|
"@headsetSilentHours": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headsetSilentDays": "Muet depuis {count} j",
|
||||||
|
"@headsetSilentDays": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updateHeadsetBtn": "Mettre à jour le casque",
|
||||||
"titleColonLabel": "Titre :",
|
"titleColonLabel": "Titre :",
|
||||||
"titleModalLabel": "Titre",
|
"titleModalLabel": "Titre",
|
||||||
"descriptionColonLabel": "Description :",
|
"descriptionColonLabel": "Description :",
|
||||||
@ -1386,6 +1435,34 @@
|
|||||||
"resourceTypePdf": "PDF",
|
"resourceTypePdf": "PDF",
|
||||||
"resourceTypeJson": "JSON",
|
"resourceTypeJson": "JSON",
|
||||||
"resourceTypeJsonUrl": "JSON (URL)",
|
"resourceTypeJsonUrl": "JSON (URL)",
|
||||||
|
"resourceTypeImage360": "Image 360°",
|
||||||
|
"resourceTypeVideo360": "Vidéo 360°",
|
||||||
|
"resourceTypeModel3D": "Modèle 3D",
|
||||||
|
"model3DResourceLabel": "Modèle 3D",
|
||||||
|
"model3DPickModelFirst": "Choisissez d'abord un modèle 3D dans la médiathèque.",
|
||||||
|
"model3DPlacePoints": "Faites glisser les points d'intérêt sur le modèle. Leur position est enregistrée avec la section.",
|
||||||
|
"scene3DModeTitle": "Ce que le visiteur fait de la scène",
|
||||||
|
"scene3DModeAsset": "Objet",
|
||||||
|
"scene3DModeScene": "Décor",
|
||||||
|
"scene3DModeAssetHint": "Un objet posé devant le visiteur, qu'il regarde sous toutes ses faces — une épée, une pièce de collection. Les points tournent avec l'objet.",
|
||||||
|
"scene3DModeSceneHint": "Un lieu dans lequel le visiteur se trouve et regarde autour — une salle reconstituée. Les points restent où ils sont.",
|
||||||
|
"model3DPointsTitle": "Points d'intérêt ({count})",
|
||||||
|
"@model3DPointsTitle": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model3DAddPoint": "Ajouter un point",
|
||||||
|
"model3DNoPoint": "Aucun point sur ce modèle 3D.",
|
||||||
|
"model3DNewPoint": "Nouveau point",
|
||||||
|
"model3DPointNotPlaced": "Pas encore placé",
|
||||||
|
"model3DPointSaveError": "La position n'a pas pu être enregistrée",
|
||||||
|
"sectionTypeScene3D": "Scène 3D",
|
||||||
|
"sectionTypeDescScene3D": "Un objet ou un décor en 3D que le visiteur explore, avec des points d'intérêt posés dessus. Affiché dans le casque VR.",
|
||||||
|
"mediaModel3DNoPreview": "Pas d'aperçu ici : le modèle 3D se visualise dans une section Scène 3D, et dans le casque.",
|
||||||
|
"mediaToggle360Hint": "Toucher pour basculer entre média plat et 360° — une image 360° est affichée en immersif dans le casque et n'est pas compressée",
|
||||||
"minutesAbbr": "min",
|
"minutesAbbr": "min",
|
||||||
"secondsAbbr": "sec",
|
"secondsAbbr": "sec",
|
||||||
"metersAbbr": "m",
|
"metersAbbr": "m",
|
||||||
|
|||||||
@ -4147,7 +4147,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @mediaDropFormats.
|
/// No description provided for @mediaDropFormats.
|
||||||
///
|
///
|
||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
/// **'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON'**
|
/// **'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON, GLB'**
|
||||||
String get mediaDropFormats;
|
String get mediaDropFormats;
|
||||||
|
|
||||||
/// No description provided for @mediaOrPasteUrl.
|
/// No description provided for @mediaOrPasteUrl.
|
||||||
@ -4240,6 +4240,108 @@ abstract class AppLocalizations {
|
|||||||
/// **'VR'**
|
/// **'VR'**
|
||||||
String get menuVr;
|
String get menuVr;
|
||||||
|
|
||||||
|
/// No description provided for @channelsSectionTitle.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Canaux'**
|
||||||
|
String get channelsSectionTitle;
|
||||||
|
|
||||||
|
/// No description provided for @channelVrHint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Crée aussi l\'application VR si elle n\'existe pas'**
|
||||||
|
String get channelVrHint;
|
||||||
|
|
||||||
|
/// No description provided for @addonsSectionTitle.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Add-ons'**
|
||||||
|
String get addonsSectionTitle;
|
||||||
|
|
||||||
|
/// No description provided for @addonAssistant.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Assistant IA'**
|
||||||
|
String get addonAssistant;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundTitle.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Fond immersif'**
|
||||||
|
String get backgroundTitle;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundHintConfiguration.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Le décor dans lequel cette visite se déroule dans le casque. Sans lui, les contenus flottent dans le noir.'**
|
||||||
|
String get backgroundHintConfiguration;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundHintMenu.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Le décor du menu d\'accueil du casque, avant que le visiteur ait choisi quoi que ce soit.'**
|
||||||
|
String get backgroundHintMenu;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundResourceLabel.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Média du fond'**
|
||||||
|
String get backgroundResourceLabel;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundKindPano.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Panorama 360° : affiché tout autour du visiteur, dans le casque.'**
|
||||||
|
String get backgroundKindPano;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundKindVideo360.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Vidéo 360° : jouée en boucle et sans son. ⚠️ Sur une borne, le décodeur tourne toute la journée.'**
|
||||||
|
String get backgroundKindVideo360;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundKindScene3D.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Décor 3D : prévu par le contrat, pas encore affiché en fond par le casque.'**
|
||||||
|
String get backgroundKindScene3D;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundFallbackLabel.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Image de repli'**
|
||||||
|
String get backgroundFallbackLabel;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundFallbackHint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Affichée par le web, le mobile et les tablettes, qui ne savent pas rendre une 360. Sans elle, ces canaux n\'affichent aucun fond.'**
|
||||||
|
String get backgroundFallbackHint;
|
||||||
|
|
||||||
|
/// No description provided for @backgroundRemove.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Retirer le fond'**
|
||||||
|
String get backgroundRemove;
|
||||||
|
|
||||||
|
/// No description provided for @addonImmersive.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Contenu immersif'**
|
||||||
|
String get addonImmersive;
|
||||||
|
|
||||||
|
/// No description provided for @addonImmersiveHint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Image 360, vidéo 360 et modèles 3D. L\'activation ajoute 100 Go au quota de stockage du plan, et les retire à la désactivation — une vidéo 360 de 5 min pèse des Go.'**
|
||||||
|
String get addonImmersiveHint;
|
||||||
|
|
||||||
|
/// No description provided for @channelApplicationCreated.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Canal activé et application créée'**
|
||||||
|
String get channelApplicationCreated;
|
||||||
|
|
||||||
/// No description provided for @noAppConfigured.
|
/// No description provided for @noAppConfigured.
|
||||||
///
|
///
|
||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
@ -4264,6 +4366,72 @@ abstract class AppLocalizations {
|
|||||||
/// **'Application VR non configurée'**
|
/// **'Application VR non configurée'**
|
||||||
String get vrAppNotConfigured;
|
String get vrAppNotConfigured;
|
||||||
|
|
||||||
|
/// No description provided for @vrTabConfig.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Configuration'**
|
||||||
|
String get vrTabConfig;
|
||||||
|
|
||||||
|
/// No description provided for @vrTabHeadsets.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Casques'**
|
||||||
|
String get vrTabHeadsets;
|
||||||
|
|
||||||
|
/// No description provided for @headsets.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Casques'**
|
||||||
|
String get headsets;
|
||||||
|
|
||||||
|
/// No description provided for @noHeadsetPaired.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Aucun casque appairé'**
|
||||||
|
String get noHeadsetPaired;
|
||||||
|
|
||||||
|
/// No description provided for @headsetBattery.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Batterie'**
|
||||||
|
String get headsetBattery;
|
||||||
|
|
||||||
|
/// No description provided for @headsetAppVersion.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Version'**
|
||||||
|
String get headsetAppVersion;
|
||||||
|
|
||||||
|
/// No description provided for @headsetLastSeen.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Vu le {date}'**
|
||||||
|
String headsetLastSeen(String date);
|
||||||
|
|
||||||
|
/// No description provided for @headsetNeverSeen.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Jamais vu'**
|
||||||
|
String get headsetNeverSeen;
|
||||||
|
|
||||||
|
/// No description provided for @headsetSilentHours.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Muet depuis {count} h'**
|
||||||
|
String headsetSilentHours(String count);
|
||||||
|
|
||||||
|
/// No description provided for @headsetSilentDays.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Muet depuis {count} j'**
|
||||||
|
String headsetSilentDays(String count);
|
||||||
|
|
||||||
|
/// No description provided for @updateHeadsetBtn.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Mettre à jour le casque'**
|
||||||
|
String get updateHeadsetBtn;
|
||||||
|
|
||||||
/// No description provided for @titleColonLabel.
|
/// No description provided for @titleColonLabel.
|
||||||
///
|
///
|
||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
@ -5818,6 +5986,132 @@ abstract class AppLocalizations {
|
|||||||
/// **'JSON (URL)'**
|
/// **'JSON (URL)'**
|
||||||
String get resourceTypeJsonUrl;
|
String get resourceTypeJsonUrl;
|
||||||
|
|
||||||
|
/// No description provided for @resourceTypeImage360.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Image 360°'**
|
||||||
|
String get resourceTypeImage360;
|
||||||
|
|
||||||
|
/// No description provided for @resourceTypeVideo360.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Vidéo 360°'**
|
||||||
|
String get resourceTypeVideo360;
|
||||||
|
|
||||||
|
/// No description provided for @resourceTypeModel3D.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Modèle 3D'**
|
||||||
|
String get resourceTypeModel3D;
|
||||||
|
|
||||||
|
/// No description provided for @model3DResourceLabel.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Modèle 3D'**
|
||||||
|
String get model3DResourceLabel;
|
||||||
|
|
||||||
|
/// No description provided for @model3DPickModelFirst.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Choisissez d\'abord un modèle 3D dans la médiathèque.'**
|
||||||
|
String get model3DPickModelFirst;
|
||||||
|
|
||||||
|
/// No description provided for @model3DPlacePoints.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Faites glisser les points d\'intérêt sur le modèle. Leur position est enregistrée avec la section.'**
|
||||||
|
String get model3DPlacePoints;
|
||||||
|
|
||||||
|
/// No description provided for @scene3DModeTitle.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Ce que le visiteur fait de la scène'**
|
||||||
|
String get scene3DModeTitle;
|
||||||
|
|
||||||
|
/// No description provided for @scene3DModeAsset.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Objet'**
|
||||||
|
String get scene3DModeAsset;
|
||||||
|
|
||||||
|
/// No description provided for @scene3DModeScene.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Décor'**
|
||||||
|
String get scene3DModeScene;
|
||||||
|
|
||||||
|
/// No description provided for @scene3DModeAssetHint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Un objet posé devant le visiteur, qu\'il regarde sous toutes ses faces — une épée, une pièce de collection. Les points tournent avec l\'objet.'**
|
||||||
|
String get scene3DModeAssetHint;
|
||||||
|
|
||||||
|
/// No description provided for @scene3DModeSceneHint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Un lieu dans lequel le visiteur se trouve et regarde autour — une salle reconstituée. Les points restent où ils sont.'**
|
||||||
|
String get scene3DModeSceneHint;
|
||||||
|
|
||||||
|
/// No description provided for @model3DPointsTitle.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Points d\'intérêt ({count})'**
|
||||||
|
String model3DPointsTitle(String count);
|
||||||
|
|
||||||
|
/// No description provided for @model3DAddPoint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Ajouter un point'**
|
||||||
|
String get model3DAddPoint;
|
||||||
|
|
||||||
|
/// No description provided for @model3DNoPoint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Aucun point sur ce modèle 3D.'**
|
||||||
|
String get model3DNoPoint;
|
||||||
|
|
||||||
|
/// No description provided for @model3DNewPoint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Nouveau point'**
|
||||||
|
String get model3DNewPoint;
|
||||||
|
|
||||||
|
/// No description provided for @model3DPointNotPlaced.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Pas encore placé'**
|
||||||
|
String get model3DPointNotPlaced;
|
||||||
|
|
||||||
|
/// No description provided for @model3DPointSaveError.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'La position n\'a pas pu être enregistrée'**
|
||||||
|
String get model3DPointSaveError;
|
||||||
|
|
||||||
|
/// No description provided for @sectionTypeScene3D.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Scène 3D'**
|
||||||
|
String get sectionTypeScene3D;
|
||||||
|
|
||||||
|
/// No description provided for @sectionTypeDescScene3D.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Un objet ou un décor en 3D que le visiteur explore, avec des points d\'intérêt posés dessus. Affiché dans le casque VR.'**
|
||||||
|
String get sectionTypeDescScene3D;
|
||||||
|
|
||||||
|
/// No description provided for @mediaModel3DNoPreview.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Pas d\'aperçu ici : le modèle 3D se visualise dans une section Scène 3D, et dans le casque.'**
|
||||||
|
String get mediaModel3DNoPreview;
|
||||||
|
|
||||||
|
/// No description provided for @mediaToggle360Hint.
|
||||||
|
///
|
||||||
|
/// In fr, this message translates to:
|
||||||
|
/// **'Toucher pour basculer entre média plat et 360° — une image 360° est affichée en immersif dans le casque et n\'est pas compressée'**
|
||||||
|
String get mediaToggle360Hint;
|
||||||
|
|
||||||
/// No description provided for @minutesAbbr.
|
/// No description provided for @minutesAbbr.
|
||||||
///
|
///
|
||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
|
|||||||
@ -2248,7 +2248,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get mediaDropZone => 'Drop your files here or click to browse';
|
String get mediaDropZone => 'Drop your files here or click to browse';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get mediaDropFormats => 'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON';
|
String get mediaDropFormats =>
|
||||||
|
'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON, GLB';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get mediaOrPasteUrl => 'or paste a URL';
|
String get mediaOrPasteUrl => 'or paste a URL';
|
||||||
@ -2310,6 +2311,65 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get menuVr => 'VR';
|
String get menuVr => 'VR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelsSectionTitle => 'Channels';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelVrHint => 'Also creates the VR application if missing';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonsSectionTitle => 'Add-ons';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonAssistant => 'AI assistant';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundTitle => 'Immersive background';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundHintConfiguration =>
|
||||||
|
'The setting this visit takes place in, inside the headset. Without it, content floats in the dark.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundHintMenu =>
|
||||||
|
'The setting of the headset\'s home menu, before the visitor has chosen anything.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundResourceLabel => 'Background media';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindPano =>
|
||||||
|
'360° panorama: shown all around the visitor, in the headset.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindVideo360 =>
|
||||||
|
'360° video: looped and muted. ⚠️ On a kiosk, the decoder runs all day.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindScene3D =>
|
||||||
|
'3D setting: in the contract, not yet shown as a background by the headset.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundFallbackLabel => 'Fallback image';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundFallbackHint =>
|
||||||
|
'Shown by web, mobile and tablets, which cannot render a 360. Without it, those channels show no background at all.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundRemove => 'Remove background';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonImmersive => 'Immersive content';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonImmersiveHint =>
|
||||||
|
'360° images, 360° video and 3D models. Turning it on adds 100 GB to the plan\'s storage quota, and turning it off removes them — a 5-minute 360° video weighs gigabytes.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelApplicationCreated =>
|
||||||
|
'Channel enabled and application created';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noAppConfigured => 'No application configured on this instance';
|
String get noAppConfigured => 'No application configured on this instance';
|
||||||
|
|
||||||
@ -2322,6 +2382,45 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get vrAppNotConfigured => 'VR application not configured';
|
String get vrAppNotConfigured => 'VR application not configured';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vrTabConfig => 'Configuration';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vrTabHeadsets => 'Headsets';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsets => 'Headsets';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noHeadsetPaired => 'No headset paired';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetBattery => 'Battery';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetAppVersion => 'Version';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetLastSeen(String date) {
|
||||||
|
return 'Seen on $date';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetNeverSeen => 'Never seen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetSilentHours(String count) {
|
||||||
|
return 'Silent for $count h';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetSilentDays(String count) {
|
||||||
|
return 'Silent for $count d';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateHeadsetBtn => 'Update headset';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleColonLabel => 'Title:';
|
String get titleColonLabel => 'Title:';
|
||||||
|
|
||||||
@ -3148,6 +3247,78 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get resourceTypeJsonUrl => 'JSON (URL)';
|
String get resourceTypeJsonUrl => 'JSON (URL)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeImage360 => '360° image';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeVideo360 => '360° video';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeModel3D => '3D model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DResourceLabel => '3D model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPickModelFirst =>
|
||||||
|
'Pick a 3D model from the media library first.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPlacePoints =>
|
||||||
|
'Drag the points of interest onto the model. Their position is saved with the section.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeTitle => 'What the visitor does with the scene';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeAsset => 'Object';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeScene => 'Place';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeAssetHint =>
|
||||||
|
'An object placed in front of the visitor, examined from every side — a sword, a collection piece. Points turn with the object.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeSceneHint =>
|
||||||
|
'A place the visitor stands in and looks around — a reconstructed room. Points stay where they are.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String model3DPointsTitle(String count) {
|
||||||
|
return 'Points of interest ($count)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DAddPoint => 'Add a point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DNoPoint => 'No point on this model yet.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DNewPoint => 'New point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPointNotPlaced => 'Not placed yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPointSaveError => 'The position could not be saved';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sectionTypeScene3D => '3D scene';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sectionTypeDescScene3D =>
|
||||||
|
'A 3D object or setting the visitor explores, with points of interest placed on it. Shown in the VR headset.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get mediaModel3DNoPreview =>
|
||||||
|
'No preview here: a 3D model is viewed in a 3D scene section, and in the headset.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get mediaToggle360Hint =>
|
||||||
|
'Tap to switch between flat and 360° media — a 360° image is shown immersively in the headset and is never compressed';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get minutesAbbr => 'min';
|
String get minutesAbbr => 'min';
|
||||||
|
|
||||||
|
|||||||
@ -2292,7 +2292,8 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
'Glissez vos fichiers ici ou cliquez pour parcourir';
|
'Glissez vos fichiers ici ou cliquez pour parcourir';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get mediaDropFormats => 'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON';
|
String get mediaDropFormats =>
|
||||||
|
'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON, GLB';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get mediaOrPasteUrl => 'ou coller une URL';
|
String get mediaOrPasteUrl => 'ou coller une URL';
|
||||||
@ -2354,6 +2355,65 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get menuVr => 'VR';
|
String get menuVr => 'VR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelsSectionTitle => 'Canaux';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelVrHint =>
|
||||||
|
'Crée aussi l\'application VR si elle n\'existe pas';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonsSectionTitle => 'Add-ons';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonAssistant => 'Assistant IA';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundTitle => 'Fond immersif';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundHintConfiguration =>
|
||||||
|
'Le décor dans lequel cette visite se déroule dans le casque. Sans lui, les contenus flottent dans le noir.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundHintMenu =>
|
||||||
|
'Le décor du menu d\'accueil du casque, avant que le visiteur ait choisi quoi que ce soit.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundResourceLabel => 'Média du fond';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindPano =>
|
||||||
|
'Panorama 360° : affiché tout autour du visiteur, dans le casque.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindVideo360 =>
|
||||||
|
'Vidéo 360° : jouée en boucle et sans son. ⚠️ Sur une borne, le décodeur tourne toute la journée.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindScene3D =>
|
||||||
|
'Décor 3D : prévu par le contrat, pas encore affiché en fond par le casque.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundFallbackLabel => 'Image de repli';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundFallbackHint =>
|
||||||
|
'Affichée par le web, le mobile et les tablettes, qui ne savent pas rendre une 360. Sans elle, ces canaux n\'affichent aucun fond.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundRemove => 'Retirer le fond';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonImmersive => 'Contenu immersif';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonImmersiveHint =>
|
||||||
|
'Image 360, vidéo 360 et modèles 3D. L\'activation ajoute 100 Go au quota de stockage du plan, et les retire à la désactivation — une vidéo 360 de 5 min pèse des Go.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelApplicationCreated => 'Canal activé et application créée';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noAppConfigured =>
|
String get noAppConfigured =>
|
||||||
'Aucune application configurée sur cette instance';
|
'Aucune application configurée sur cette instance';
|
||||||
@ -2367,6 +2427,45 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get vrAppNotConfigured => 'Application VR non configurée';
|
String get vrAppNotConfigured => 'Application VR non configurée';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vrTabConfig => 'Configuration';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vrTabHeadsets => 'Casques';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsets => 'Casques';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noHeadsetPaired => 'Aucun casque appairé';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetBattery => 'Batterie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetAppVersion => 'Version';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetLastSeen(String date) {
|
||||||
|
return 'Vu le $date';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetNeverSeen => 'Jamais vu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetSilentHours(String count) {
|
||||||
|
return 'Muet depuis $count h';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetSilentDays(String count) {
|
||||||
|
return 'Muet depuis $count j';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateHeadsetBtn => 'Mettre à jour le casque';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleColonLabel => 'Titre :';
|
String get titleColonLabel => 'Titre :';
|
||||||
|
|
||||||
@ -3199,6 +3298,79 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get resourceTypeJsonUrl => 'JSON (URL)';
|
String get resourceTypeJsonUrl => 'JSON (URL)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeImage360 => 'Image 360°';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeVideo360 => 'Vidéo 360°';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeModel3D => 'Modèle 3D';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DResourceLabel => 'Modèle 3D';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPickModelFirst =>
|
||||||
|
'Choisissez d\'abord un modèle 3D dans la médiathèque.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPlacePoints =>
|
||||||
|
'Faites glisser les points d\'intérêt sur le modèle. Leur position est enregistrée avec la section.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeTitle => 'Ce que le visiteur fait de la scène';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeAsset => 'Objet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeScene => 'Décor';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeAssetHint =>
|
||||||
|
'Un objet posé devant le visiteur, qu\'il regarde sous toutes ses faces — une épée, une pièce de collection. Les points tournent avec l\'objet.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeSceneHint =>
|
||||||
|
'Un lieu dans lequel le visiteur se trouve et regarde autour — une salle reconstituée. Les points restent où ils sont.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String model3DPointsTitle(String count) {
|
||||||
|
return 'Points d\'intérêt ($count)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DAddPoint => 'Ajouter un point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DNoPoint => 'Aucun point sur ce modèle 3D.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DNewPoint => 'Nouveau point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPointNotPlaced => 'Pas encore placé';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPointSaveError =>
|
||||||
|
'La position n\'a pas pu être enregistrée';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sectionTypeScene3D => 'Scène 3D';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sectionTypeDescScene3D =>
|
||||||
|
'Un objet ou un décor en 3D que le visiteur explore, avec des points d\'intérêt posés dessus. Affiché dans le casque VR.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get mediaModel3DNoPreview =>
|
||||||
|
'Pas d\'aperçu ici : le modèle 3D se visualise dans une section Scène 3D, et dans le casque.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get mediaToggle360Hint =>
|
||||||
|
'Toucher pour basculer entre média plat et 360° — une image 360° est affichée en immersif dans le casque et n\'est pas compressée';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get minutesAbbr => 'min';
|
String get minutesAbbr => 'min';
|
||||||
|
|
||||||
|
|||||||
@ -2272,7 +2272,8 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
'Sleep uw bestanden hierheen of klik om te bladeren';
|
'Sleep uw bestanden hierheen of klik om te bladeren';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get mediaDropFormats => 'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON';
|
String get mediaDropFormats =>
|
||||||
|
'JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON, GLB';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get mediaOrPasteUrl => 'of plak een URL';
|
String get mediaOrPasteUrl => 'of plak een URL';
|
||||||
@ -2335,6 +2336,66 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get menuVr => 'VR';
|
String get menuVr => 'VR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelsSectionTitle => 'Kanalen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelVrHint =>
|
||||||
|
'Maakt ook de VR-applicatie aan als die ontbreekt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonsSectionTitle => 'Add-ons';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonAssistant => 'AI-assistent';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundTitle => 'Immersieve achtergrond';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundHintConfiguration =>
|
||||||
|
'Het decor waarin dit bezoek zich in de headset afspeelt. Zonder decor zweeft de inhoud in het donker.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundHintMenu =>
|
||||||
|
'Het decor van het hoofdmenu van de headset, voordat de bezoeker iets gekozen heeft.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundResourceLabel => 'Media van de achtergrond';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindPano =>
|
||||||
|
'360°-panorama: rondom de bezoeker getoond, in de headset.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindVideo360 =>
|
||||||
|
'360°-video: in lus en zonder geluid. ⚠️ Op een kiosk draait de decoder de hele dag.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundKindScene3D =>
|
||||||
|
'3D-decor: voorzien in het contract, nog niet als achtergrond getoond door de headset.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundFallbackLabel => 'Terugvalafbeelding';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundFallbackHint =>
|
||||||
|
'Getoond door web, mobiel en tablets, die geen 360 kunnen renderen. Zonder deze tonen die kanalen geen achtergrond.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get backgroundRemove => 'Achtergrond verwijderen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonImmersive => 'Immersieve content';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addonImmersiveHint =>
|
||||||
|
'360°-afbeeldingen, 360°-video en 3D-modellen. Inschakelen voegt 100 GB toe aan het opslagquotum van het plan, uitschakelen haalt ze weg — een 360°-video van 5 min weegt gigabytes.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get channelApplicationCreated =>
|
||||||
|
'Kanaal ingeschakeld en applicatie aangemaakt';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get noAppConfigured =>
|
String get noAppConfigured =>
|
||||||
'Geen applicatie geconfigureerd op deze instantie';
|
'Geen applicatie geconfigureerd op deze instantie';
|
||||||
@ -2348,6 +2409,45 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get vrAppNotConfigured => 'VR-applicatie niet geconfigureerd';
|
String get vrAppNotConfigured => 'VR-applicatie niet geconfigureerd';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vrTabConfig => 'Configuratie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get vrTabHeadsets => 'Headsets';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsets => 'Headsets';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noHeadsetPaired => 'Geen headset gekoppeld';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetBattery => 'Batterij';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetAppVersion => 'Versie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetLastSeen(String date) {
|
||||||
|
return 'Gezien op $date';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get headsetNeverSeen => 'Nooit gezien';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetSilentHours(String count) {
|
||||||
|
return '$count u zonder signaal';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String headsetSilentDays(String count) {
|
||||||
|
return '$count d zonder signaal';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateHeadsetBtn => 'Headset bijwerken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleColonLabel => 'Titel:';
|
String get titleColonLabel => 'Titel:';
|
||||||
|
|
||||||
@ -3175,6 +3275,78 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get resourceTypeJsonUrl => 'JSON (URL)';
|
String get resourceTypeJsonUrl => 'JSON (URL)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeImage360 => '360°-afbeelding';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeVideo360 => '360°-video';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resourceTypeModel3D => '3D-model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DResourceLabel => '3D-model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPickModelFirst =>
|
||||||
|
'Kies eerst een 3D-model uit de mediatheek.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPlacePoints =>
|
||||||
|
'Sleep de interessepunten op het model. Hun positie wordt met de sectie opgeslagen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeTitle => 'Wat de bezoeker met de scène doet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeAsset => 'Object';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeScene => 'Ruimte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeAssetHint =>
|
||||||
|
'Een object voor de bezoeker, van alle kanten te bekijken — een zwaard, een collectiestuk. Punten draaien mee met het object.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get scene3DModeSceneHint =>
|
||||||
|
'Een plek waar de bezoeker in staat en rondkijkt — een gereconstrueerde zaal. Punten blijven waar ze zijn.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String model3DPointsTitle(String count) {
|
||||||
|
return 'Interessepunten ($count)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DAddPoint => 'Punt toevoegen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DNoPoint => 'Nog geen punt op dit model.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DNewPoint => 'Nieuw punt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPointNotPlaced => 'Nog niet geplaatst';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get model3DPointSaveError => 'De positie kon niet worden opgeslagen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sectionTypeScene3D => '3D-scène';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sectionTypeDescScene3D =>
|
||||||
|
'Een 3D-object of decor dat de bezoeker verkent, met interessepunten erop. Wordt in de VR-headset getoond.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get mediaModel3DNoPreview =>
|
||||||
|
'Geen voorbeeld hier: een 3D-model bekijk je in een 3D-scènesectie en in de headset.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get mediaToggle360Hint =>
|
||||||
|
'Tik om te wisselen tussen plat en 360° — een 360°-afbeelding wordt immersief in de headset getoond en wordt niet gecomprimeerd';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get minutesAbbr => 'min';
|
String get minutesAbbr => 'min';
|
||||||
|
|
||||||
|
|||||||
@ -1005,7 +1005,7 @@
|
|||||||
"@_mediatheque_dialogue": {},
|
"@_mediatheque_dialogue": {},
|
||||||
"mediaAddTitle": "Een bron toevoegen",
|
"mediaAddTitle": "Een bron toevoegen",
|
||||||
"mediaDropZone": "Sleep uw bestanden hierheen of klik om te bladeren",
|
"mediaDropZone": "Sleep uw bestanden hierheen of klik om te bladeren",
|
||||||
"mediaDropFormats": "JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON",
|
"mediaDropFormats": "JPG, PNG, GIF, MP3, MP4, WEBM, PDF, JSON, GLB",
|
||||||
"mediaOrPasteUrl": "of plak een URL",
|
"mediaOrPasteUrl": "of plak een URL",
|
||||||
"mediaUrlHint": "https://… afbeelding, YouTube-video of JSON-bestand",
|
"mediaUrlHint": "https://… afbeelding, YouTube-video of JSON-bestand",
|
||||||
"mediaUrlInvalid": "Deze URL is niet geldig",
|
"mediaUrlInvalid": "Deze URL is niet geldig",
|
||||||
@ -1052,10 +1052,59 @@
|
|||||||
"menuKiosk": "Kiosk",
|
"menuKiosk": "Kiosk",
|
||||||
"menuWeb": "Web",
|
"menuWeb": "Web",
|
||||||
"menuVr": "VR",
|
"menuVr": "VR",
|
||||||
|
"channelsSectionTitle": "Kanalen",
|
||||||
|
"channelVrHint": "Maakt ook de VR-applicatie aan als die ontbreekt",
|
||||||
|
"addonsSectionTitle": "Add-ons",
|
||||||
|
"addonAssistant": "AI-assistent",
|
||||||
|
"backgroundTitle": "Immersieve achtergrond",
|
||||||
|
"backgroundHintConfiguration": "Het decor waarin dit bezoek zich in de headset afspeelt. Zonder decor zweeft de inhoud in het donker.",
|
||||||
|
"backgroundHintMenu": "Het decor van het hoofdmenu van de headset, voordat de bezoeker iets gekozen heeft.",
|
||||||
|
"backgroundResourceLabel": "Media van de achtergrond",
|
||||||
|
"backgroundKindPano": "360°-panorama: rondom de bezoeker getoond, in de headset.",
|
||||||
|
"backgroundKindVideo360": "360°-video: in lus en zonder geluid. ⚠️ Op een kiosk draait de decoder de hele dag.",
|
||||||
|
"backgroundKindScene3D": "3D-decor: voorzien in het contract, nog niet als achtergrond getoond door de headset.",
|
||||||
|
"backgroundFallbackLabel": "Terugvalafbeelding",
|
||||||
|
"backgroundFallbackHint": "Getoond door web, mobiel en tablets, die geen 360 kunnen renderen. Zonder deze tonen die kanalen geen achtergrond.",
|
||||||
|
"backgroundRemove": "Achtergrond verwijderen",
|
||||||
|
"addonImmersive": "Immersieve content",
|
||||||
|
"addonImmersiveHint": "360°-afbeeldingen, 360°-video en 3D-modellen. Inschakelen voegt 100 GB toe aan het opslagquotum van het plan, uitschakelen haalt ze weg — een 360°-video van 5 min weegt gigabytes.",
|
||||||
|
"channelApplicationCreated": "Kanaal ingeschakeld en applicatie aangemaakt",
|
||||||
"noAppConfigured": "Geen applicatie geconfigureerd op deze instantie",
|
"noAppConfigured": "Geen applicatie geconfigureerd op deze instantie",
|
||||||
"mobileAppNotConfigured": "Mobiele applicatie niet geconfigureerd",
|
"mobileAppNotConfigured": "Mobiele applicatie niet geconfigureerd",
|
||||||
"kioskAppNotConfigured": "Kioskapplicatie niet geconfigureerd",
|
"kioskAppNotConfigured": "Kioskapplicatie niet geconfigureerd",
|
||||||
"vrAppNotConfigured": "VR-applicatie niet geconfigureerd",
|
"vrAppNotConfigured": "VR-applicatie niet geconfigureerd",
|
||||||
|
"vrTabConfig": "Configuratie",
|
||||||
|
"vrTabHeadsets": "Headsets",
|
||||||
|
"headsets": "Headsets",
|
||||||
|
"noHeadsetPaired": "Geen headset gekoppeld",
|
||||||
|
"headsetBattery": "Batterij",
|
||||||
|
"headsetAppVersion": "Versie",
|
||||||
|
"headsetLastSeen": "Gezien op {date}",
|
||||||
|
"@headsetLastSeen": {
|
||||||
|
"placeholders": {
|
||||||
|
"date": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headsetNeverSeen": "Nooit gezien",
|
||||||
|
"headsetSilentHours": "{count} u zonder signaal",
|
||||||
|
"@headsetSilentHours": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"headsetSilentDays": "{count} d zonder signaal",
|
||||||
|
"@headsetSilentDays": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updateHeadsetBtn": "Headset bijwerken",
|
||||||
"titleColonLabel": "Titel:",
|
"titleColonLabel": "Titel:",
|
||||||
"titleModalLabel": "Titel",
|
"titleModalLabel": "Titel",
|
||||||
"descriptionColonLabel": "Beschrijving:",
|
"descriptionColonLabel": "Beschrijving:",
|
||||||
@ -1386,6 +1435,34 @@
|
|||||||
"resourceTypePdf": "PDF",
|
"resourceTypePdf": "PDF",
|
||||||
"resourceTypeJson": "JSON",
|
"resourceTypeJson": "JSON",
|
||||||
"resourceTypeJsonUrl": "JSON (URL)",
|
"resourceTypeJsonUrl": "JSON (URL)",
|
||||||
|
"resourceTypeImage360": "360°-afbeelding",
|
||||||
|
"resourceTypeVideo360": "360°-video",
|
||||||
|
"resourceTypeModel3D": "3D-model",
|
||||||
|
"model3DResourceLabel": "3D-model",
|
||||||
|
"model3DPickModelFirst": "Kies eerst een 3D-model uit de mediatheek.",
|
||||||
|
"model3DPlacePoints": "Sleep de interessepunten op het model. Hun positie wordt met de sectie opgeslagen.",
|
||||||
|
"scene3DModeTitle": "Wat de bezoeker met de scène doet",
|
||||||
|
"scene3DModeAsset": "Object",
|
||||||
|
"scene3DModeScene": "Ruimte",
|
||||||
|
"scene3DModeAssetHint": "Een object voor de bezoeker, van alle kanten te bekijken — een zwaard, een collectiestuk. Punten draaien mee met het object.",
|
||||||
|
"scene3DModeSceneHint": "Een plek waar de bezoeker in staat en rondkijkt — een gereconstrueerde zaal. Punten blijven waar ze zijn.",
|
||||||
|
"model3DPointsTitle": "Interessepunten ({count})",
|
||||||
|
"@model3DPointsTitle": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model3DAddPoint": "Punt toevoegen",
|
||||||
|
"model3DNoPoint": "Nog geen punt op dit model.",
|
||||||
|
"model3DNewPoint": "Nieuw punt",
|
||||||
|
"model3DPointNotPlaced": "Nog niet geplaatst",
|
||||||
|
"model3DPointSaveError": "De positie kon niet worden opgeslagen",
|
||||||
|
"sectionTypeScene3D": "3D-scène",
|
||||||
|
"sectionTypeDescScene3D": "Een 3D-object of decor dat de bezoeker verkent, met interessepunten erop. Wordt in de VR-headset getoond.",
|
||||||
|
"mediaModel3DNoPreview": "Geen voorbeeld hier: een 3D-model bekijk je in een 3D-scènesectie en in de headset.",
|
||||||
|
"mediaToggle360Hint": "Tik om te wisselen tussen plat en 360° — een 360°-afbeelding wordt immersief in de headset getoond en wordt niet gecomprimeerd",
|
||||||
"minutesAbbr": "min",
|
"minutesAbbr": "min",
|
||||||
"secondsAbbr": "sec",
|
"secondsAbbr": "sec",
|
||||||
"metersAbbr": "m",
|
"metersAbbr": "m",
|
||||||
|
|||||||
@ -42,6 +42,7 @@ part 'api/section_api.dart';
|
|||||||
part 'api/section_agenda_api.dart';
|
part 'api/section_agenda_api.dart';
|
||||||
part 'api/section_event_api.dart';
|
part 'api/section_event_api.dart';
|
||||||
part 'api/section_map_api.dart';
|
part 'api/section_map_api.dart';
|
||||||
|
part 'api/section_scene3_d_api.dart';
|
||||||
part 'api/section_parcours_api.dart';
|
part 'api/section_parcours_api.dart';
|
||||||
part 'api/section_quiz_api.dart';
|
part 'api/section_quiz_api.dart';
|
||||||
part 'api/notification_api.dart';
|
part 'api/notification_api.dart';
|
||||||
@ -170,6 +171,11 @@ part 'model/section_event.dart';
|
|||||||
part 'model/section_event_dto.dart';
|
part 'model/section_event_dto.dart';
|
||||||
part 'model/section_game.dart';
|
part 'model/section_game.dart';
|
||||||
part 'model/parcours_dto.dart';
|
part 'model/parcours_dto.dart';
|
||||||
|
part 'model/immersive_background_dto.dart';
|
||||||
|
part 'model/immersive_background_kind.dart';
|
||||||
|
part 'model/scene3_d_dto.dart';
|
||||||
|
part 'model/scene3_d_mode.dart';
|
||||||
|
part 'model/position3_d.dart';
|
||||||
part 'model/section_game_all_of_game_puzzle_image.dart';
|
part 'model/section_game_all_of_game_puzzle_image.dart';
|
||||||
part 'model/section_map.dart';
|
part 'model/section_map.dart';
|
||||||
part 'model/section_map_all_of_map_map_provider.dart';
|
part 'model/section_map_all_of_map_map_provider.dart';
|
||||||
|
|||||||
@ -129,8 +129,11 @@ class DeviceApi {
|
|||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [String] instanceId:
|
/// * [String] instanceId:
|
||||||
|
///
|
||||||
|
/// * [AppType] appType:
|
||||||
Future<Response> deviceGetWithHttpInfo({
|
Future<Response> deviceGetWithHttpInfo({
|
||||||
String? instanceId,
|
String? instanceId,
|
||||||
|
AppType? appType,
|
||||||
}) async {
|
}) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/api/Device';
|
final path = r'/api/Device';
|
||||||
@ -145,6 +148,9 @@ class DeviceApi {
|
|||||||
if (instanceId != null) {
|
if (instanceId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'instanceId', instanceId));
|
queryParams.addAll(_queryParams('', 'instanceId', instanceId));
|
||||||
}
|
}
|
||||||
|
if (appType != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'appType', appType));
|
||||||
|
}
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
@ -162,11 +168,15 @@ class DeviceApi {
|
|||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [String] instanceId:
|
/// * [String] instanceId:
|
||||||
|
///
|
||||||
|
/// * [AppType] appType:
|
||||||
Future<List<DeviceDTO>?> deviceGet({
|
Future<List<DeviceDTO>?> deviceGet({
|
||||||
String? instanceId,
|
String? instanceId,
|
||||||
|
AppType? appType,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await deviceGetWithHttpInfo(
|
final response = await deviceGetWithHttpInfo(
|
||||||
instanceId: instanceId,
|
instanceId: instanceId,
|
||||||
|
appType: appType,
|
||||||
);
|
);
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
|||||||
167
manager_api_new/lib/api/section_scene3_d_api.dart
Normal file
167
manager_api_new/lib/api/section_scene3_d_api.dart
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// Les points d'interet d'une section maquette 3D (item E7 du lot XR-4).
|
||||||
|
///
|
||||||
|
/// Meme objet que les points d'une carte — un `GeoPointDTO` — sur une autre
|
||||||
|
/// collection : ce qui change, c'est `localTransform` au lieu de `geometry`.
|
||||||
|
class SectionScene3DApi {
|
||||||
|
SectionScene3DApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||||
|
|
||||||
|
final ApiClient apiClient;
|
||||||
|
|
||||||
|
/// Performs an HTTP 'GET /api/SectionScene3D/{sectionId}/points' operation.
|
||||||
|
Future<Response> sectionScene3DGetPointsWithHttpInfo(String sectionId,) async {
|
||||||
|
final path = r'/api/SectionScene3D/{sectionId}/points'
|
||||||
|
.replaceAll('{sectionId}', sectionId);
|
||||||
|
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'GET',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<GeoPointDTO>?> sectionScene3DGetPoints(String sectionId,) async {
|
||||||
|
final response = await sectionScene3DGetPointsWithHttpInfo(sectionId,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
|
final responseBody = await _decodeBodyBytes(response);
|
||||||
|
return (await apiClient.deserializeAsync(responseBody, 'List<GeoPointDTO>') as List)
|
||||||
|
.cast<GeoPointDTO>()
|
||||||
|
.toList(growable: false);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'POST /api/SectionScene3D/{sectionId}/points' operation.
|
||||||
|
Future<Response> sectionScene3DCreatePointWithHttpInfo(
|
||||||
|
String sectionId,
|
||||||
|
GeoPointDTO geoPointDTO,
|
||||||
|
) async {
|
||||||
|
final path = r'/api/SectionScene3D/{sectionId}/points'
|
||||||
|
.replaceAll('{sectionId}', sectionId);
|
||||||
|
|
||||||
|
Object? postBody = geoPointDTO;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'POST',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GeoPointDTO?> sectionScene3DCreatePoint(
|
||||||
|
String sectionId,
|
||||||
|
GeoPointDTO geoPointDTO,
|
||||||
|
) async {
|
||||||
|
final response = await sectionScene3DCreatePointWithHttpInfo(sectionId, geoPointDTO,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
|
return await apiClient.deserializeAsync(
|
||||||
|
await _decodeBodyBytes(response), 'GeoPointDTO',) as GeoPointDTO;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'PUT /api/SectionScene3D/points' operation.
|
||||||
|
Future<Response> sectionScene3DUpdatePointWithHttpInfo(GeoPointDTO geoPointDTO,) async {
|
||||||
|
final path = r'/api/SectionScene3D/points';
|
||||||
|
|
||||||
|
Object? postBody = geoPointDTO;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'PUT',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GeoPointDTO?> sectionScene3DUpdatePoint(GeoPointDTO geoPointDTO,) async {
|
||||||
|
final response = await sectionScene3DUpdatePointWithHttpInfo(geoPointDTO,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
|
return await apiClient.deserializeAsync(
|
||||||
|
await _decodeBodyBytes(response), 'GeoPointDTO',) as GeoPointDTO;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'DELETE /api/SectionScene3D/points/{id}' operation.
|
||||||
|
Future<Response> sectionScene3DDeletePointWithHttpInfo(int id,) async {
|
||||||
|
final path = r'/api/SectionScene3D/points/{id}'
|
||||||
|
.replaceAll('{id}', id.toString());
|
||||||
|
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'DELETE',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sectionScene3DDeletePoint(int id,) async {
|
||||||
|
final response = await sectionScene3DDeletePointWithHttpInfo(id,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -325,6 +325,16 @@ class ApiClient {
|
|||||||
return GeoPointDTO.fromJson(value);
|
return GeoPointDTO.fromJson(value);
|
||||||
case 'Geometry':
|
case 'Geometry':
|
||||||
return Geometry.fromJson(value);
|
return Geometry.fromJson(value);
|
||||||
|
case 'ImmersiveBackgroundDTO':
|
||||||
|
return ImmersiveBackgroundDTO.fromJson(value);
|
||||||
|
case 'ImmersiveBackgroundKind':
|
||||||
|
return ImmersiveBackgroundKindTypeTransformer().decode(value);
|
||||||
|
case 'Scene3DMode':
|
||||||
|
return Scene3DModeTypeTransformer().decode(value);
|
||||||
|
case 'Scene3DDTO':
|
||||||
|
return Scene3DDTO.fromJson(value);
|
||||||
|
case 'Position3D':
|
||||||
|
return Position3D.fromJson(value);
|
||||||
case 'GeometryCentroid':
|
case 'GeometryCentroid':
|
||||||
return GeometryCentroid.fromJson(value);
|
return GeometryCentroid.fromJson(value);
|
||||||
case 'GeometryDTO':
|
case 'GeometryDTO':
|
||||||
|
|||||||
@ -68,6 +68,9 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is ApiKeyAppType) {
|
if (value is ApiKeyAppType) {
|
||||||
return ApiKeyAppTypeTypeTransformer().encode(value).toString();
|
return ApiKeyAppTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is Scene3DMode) {
|
||||||
|
return Scene3DModeTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
if (value is AppType) {
|
if (value is AppType) {
|
||||||
return AppTypeTypeTransformer().encode(value).toString();
|
return AppTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,12 +26,14 @@ class ApiKeyAppType {
|
|||||||
static const number0 = ApiKeyAppType._(0);
|
static const number0 = ApiKeyAppType._(0);
|
||||||
static const number1 = ApiKeyAppType._(1);
|
static const number1 = ApiKeyAppType._(1);
|
||||||
static const number2 = ApiKeyAppType._(2);
|
static const number2 = ApiKeyAppType._(2);
|
||||||
|
static const number3 = ApiKeyAppType._(3); // VrApp
|
||||||
|
|
||||||
/// List of all possible values in this [enum][ApiKeyAppType].
|
/// List of all possible values in this [enum][ApiKeyAppType].
|
||||||
static const values = <ApiKeyAppType>[
|
static const values = <ApiKeyAppType>[
|
||||||
number0,
|
number0,
|
||||||
number1,
|
number1,
|
||||||
number2,
|
number2,
|
||||||
|
number3,
|
||||||
];
|
];
|
||||||
|
|
||||||
static ApiKeyAppType? fromJson(dynamic value) =>
|
static ApiKeyAppType? fromJson(dynamic value) =>
|
||||||
|
|||||||
@ -32,6 +32,7 @@ class ApplicationInstanceDTO {
|
|||||||
this.appName = const [],
|
this.appName = const [],
|
||||||
this.appStoreUrl,
|
this.appStoreUrl,
|
||||||
this.playStoreUrl,
|
this.playStoreUrl,
|
||||||
|
this.immersiveBackground,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? id;
|
String? id;
|
||||||
@ -78,6 +79,9 @@ class ApplicationInstanceDTO {
|
|||||||
|
|
||||||
String? playStoreUrl;
|
String? playStoreUrl;
|
||||||
|
|
||||||
|
/// Fond du menu general, specifique VR.
|
||||||
|
ImmersiveBackgroundDTO? immersiveBackground;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@ -219,6 +223,11 @@ class ApplicationInstanceDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'appStoreUrl'] = null;
|
json[r'appStoreUrl'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.immersiveBackground != null) {
|
||||||
|
json[r'immersiveBackground'] = this.immersiveBackground;
|
||||||
|
} else {
|
||||||
|
json[r'immersiveBackground'] = null;
|
||||||
|
}
|
||||||
if (this.playStoreUrl != null) {
|
if (this.playStoreUrl != null) {
|
||||||
json[r'playStoreUrl'] = this.playStoreUrl;
|
json[r'playStoreUrl'] = this.playStoreUrl;
|
||||||
} else {
|
} else {
|
||||||
@ -272,6 +281,7 @@ class ApplicationInstanceDTO {
|
|||||||
appName: TranslationDTO.listFromJson(json[r'appName']),
|
appName: TranslationDTO.listFromJson(json[r'appName']),
|
||||||
appStoreUrl: mapValueOfType<String>(json, r'appStoreUrl'),
|
appStoreUrl: mapValueOfType<String>(json, r'appStoreUrl'),
|
||||||
playStoreUrl: mapValueOfType<String>(json, r'playStoreUrl'),
|
playStoreUrl: mapValueOfType<String>(json, r'playStoreUrl'),
|
||||||
|
immersiveBackground: ImmersiveBackgroundDTO.fromJson(json[r'immersiveBackground']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class ConfigurationDTO {
|
|||||||
this.sectionIds = const [],
|
this.sectionIds = const [],
|
||||||
this.loaderImageId,
|
this.loaderImageId,
|
||||||
this.loaderImageUrl,
|
this.loaderImageUrl,
|
||||||
|
this.immersiveBackground,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? id;
|
String? id;
|
||||||
@ -69,6 +70,9 @@ class ConfigurationDTO {
|
|||||||
|
|
||||||
String? loaderImageUrl;
|
String? loaderImageUrl;
|
||||||
|
|
||||||
|
/// Fond immersif de la visite, null si elle n'en a pas.
|
||||||
|
ImmersiveBackgroundDTO? immersiveBackground;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@ -177,6 +181,11 @@ class ConfigurationDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'loaderImageId'] = null;
|
json[r'loaderImageId'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.immersiveBackground != null) {
|
||||||
|
json[r'immersiveBackground'] = this.immersiveBackground;
|
||||||
|
} else {
|
||||||
|
json[r'immersiveBackground'] = null;
|
||||||
|
}
|
||||||
if (this.loaderImageUrl != null) {
|
if (this.loaderImageUrl != null) {
|
||||||
json[r'loaderImageUrl'] = this.loaderImageUrl;
|
json[r'loaderImageUrl'] = this.loaderImageUrl;
|
||||||
} else {
|
} else {
|
||||||
@ -228,6 +237,7 @@ class ConfigurationDTO {
|
|||||||
: const [],
|
: const [],
|
||||||
loaderImageId: mapValueOfType<String>(json, r'loaderImageId'),
|
loaderImageId: mapValueOfType<String>(json, r'loaderImageId'),
|
||||||
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
loaderImageUrl: mapValueOfType<String>(json, r'loaderImageUrl'),
|
||||||
|
immersiveBackground: ImmersiveBackgroundDTO.fromJson(json[r'immersiveBackground']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -28,6 +28,9 @@ class DeviceDetailDTO {
|
|||||||
this.lastConnectionLevel,
|
this.lastConnectionLevel,
|
||||||
this.batteryLevel,
|
this.batteryLevel,
|
||||||
this.lastBatteryLevel,
|
this.lastBatteryLevel,
|
||||||
|
this.appType,
|
||||||
|
this.appVersion,
|
||||||
|
this.lastSeen,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? id;
|
String? id;
|
||||||
@ -78,6 +81,13 @@ class DeviceDetailDTO {
|
|||||||
///
|
///
|
||||||
DateTime? lastBatteryLevel;
|
DateTime? lastBatteryLevel;
|
||||||
|
|
||||||
|
/// Canal de l'appareil. Absent de la requête = tablette, côté serveur.
|
||||||
|
AppType? appType;
|
||||||
|
|
||||||
|
String? appVersion;
|
||||||
|
|
||||||
|
DateTime? lastSeen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@ -96,7 +106,10 @@ class DeviceDetailDTO {
|
|||||||
other.connectionLevel == connectionLevel &&
|
other.connectionLevel == connectionLevel &&
|
||||||
other.lastConnectionLevel == lastConnectionLevel &&
|
other.lastConnectionLevel == lastConnectionLevel &&
|
||||||
other.batteryLevel == batteryLevel &&
|
other.batteryLevel == batteryLevel &&
|
||||||
other.lastBatteryLevel == lastBatteryLevel;
|
other.lastBatteryLevel == lastBatteryLevel &&
|
||||||
|
other.appType == appType &&
|
||||||
|
other.appVersion == appVersion &&
|
||||||
|
other.lastSeen == lastSeen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
@ -115,11 +128,14 @@ class DeviceDetailDTO {
|
|||||||
(connectionLevel == null ? 0 : connectionLevel!.hashCode) +
|
(connectionLevel == null ? 0 : connectionLevel!.hashCode) +
|
||||||
(lastConnectionLevel == null ? 0 : lastConnectionLevel!.hashCode) +
|
(lastConnectionLevel == null ? 0 : lastConnectionLevel!.hashCode) +
|
||||||
(batteryLevel == null ? 0 : batteryLevel!.hashCode) +
|
(batteryLevel == null ? 0 : batteryLevel!.hashCode) +
|
||||||
(lastBatteryLevel == null ? 0 : lastBatteryLevel!.hashCode);
|
(lastBatteryLevel == null ? 0 : lastBatteryLevel!.hashCode) +
|
||||||
|
(appType == null ? 0 : appType!.hashCode) +
|
||||||
|
(appVersion == null ? 0 : appVersion!.hashCode) +
|
||||||
|
(lastSeen == null ? 0 : lastSeen!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'DeviceDetailDTO[id=$id, identifier=$identifier, name=$name, ipAddressWLAN=$ipAddressWLAN, ipAddressETH=$ipAddressETH, configurationId=$configurationId, configuration=$configuration, connected=$connected, dateCreation=$dateCreation, dateUpdate=$dateUpdate, instanceId=$instanceId, connectionLevel=$connectionLevel, lastConnectionLevel=$lastConnectionLevel, batteryLevel=$batteryLevel, lastBatteryLevel=$lastBatteryLevel]';
|
'DeviceDetailDTO[id=$id, identifier=$identifier, name=$name, ipAddressWLAN=$ipAddressWLAN, ipAddressETH=$ipAddressETH, configurationId=$configurationId, configuration=$configuration, connected=$connected, dateCreation=$dateCreation, dateUpdate=$dateUpdate, instanceId=$instanceId, connectionLevel=$connectionLevel, lastConnectionLevel=$lastConnectionLevel, batteryLevel=$batteryLevel, lastBatteryLevel=$lastBatteryLevel, appType=$appType, appVersion=$appVersion, lastSeen=$lastSeen]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -200,6 +216,21 @@ class DeviceDetailDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'lastBatteryLevel'] = null;
|
json[r'lastBatteryLevel'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.appType != null) {
|
||||||
|
json[r'appType'] = this.appType;
|
||||||
|
} else {
|
||||||
|
json[r'appType'] = null;
|
||||||
|
}
|
||||||
|
if (this.appVersion != null) {
|
||||||
|
json[r'appVersion'] = this.appVersion;
|
||||||
|
} else {
|
||||||
|
json[r'appVersion'] = null;
|
||||||
|
}
|
||||||
|
if (this.lastSeen != null) {
|
||||||
|
json[r'lastSeen'] = this.lastSeen!.toUtc().toIso8601String();
|
||||||
|
} else {
|
||||||
|
json[r'lastSeen'] = null;
|
||||||
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +270,9 @@ class DeviceDetailDTO {
|
|||||||
lastConnectionLevel: mapDateTime(json, r'lastConnectionLevel', r''),
|
lastConnectionLevel: mapDateTime(json, r'lastConnectionLevel', r''),
|
||||||
batteryLevel: mapValueOfType<String>(json, r'batteryLevel'),
|
batteryLevel: mapValueOfType<String>(json, r'batteryLevel'),
|
||||||
lastBatteryLevel: mapDateTime(json, r'lastBatteryLevel', r''),
|
lastBatteryLevel: mapDateTime(json, r'lastBatteryLevel', r''),
|
||||||
|
appType: AppType.fromJson(json[r'appType']),
|
||||||
|
appVersion: mapValueOfType<String>(json, r'appVersion'),
|
||||||
|
lastSeen: mapDateTime(json, r'lastSeen', r''),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class DeviceDTO {
|
|||||||
this.dateCreation,
|
this.dateCreation,
|
||||||
this.dateUpdate,
|
this.dateUpdate,
|
||||||
this.instanceId,
|
this.instanceId,
|
||||||
|
this.appType,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? id;
|
String? id;
|
||||||
@ -54,6 +55,9 @@ class DeviceDTO {
|
|||||||
|
|
||||||
String? instanceId;
|
String? instanceId;
|
||||||
|
|
||||||
|
/// Canal de l'appareil. Absent de la requête = tablette, côté serveur.
|
||||||
|
AppType? appType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@ -68,7 +72,8 @@ class DeviceDTO {
|
|||||||
other.connected == connected &&
|
other.connected == connected &&
|
||||||
other.dateCreation == dateCreation &&
|
other.dateCreation == dateCreation &&
|
||||||
other.dateUpdate == dateUpdate &&
|
other.dateUpdate == dateUpdate &&
|
||||||
other.instanceId == instanceId;
|
other.instanceId == instanceId &&
|
||||||
|
other.appType == appType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
@ -83,11 +88,12 @@ class DeviceDTO {
|
|||||||
(connected == null ? 0 : connected!.hashCode) +
|
(connected == null ? 0 : connected!.hashCode) +
|
||||||
(dateCreation == null ? 0 : dateCreation!.hashCode) +
|
(dateCreation == null ? 0 : dateCreation!.hashCode) +
|
||||||
(dateUpdate == null ? 0 : dateUpdate!.hashCode) +
|
(dateUpdate == null ? 0 : dateUpdate!.hashCode) +
|
||||||
(instanceId == null ? 0 : instanceId!.hashCode);
|
(instanceId == null ? 0 : instanceId!.hashCode) +
|
||||||
|
(appType == null ? 0 : appType!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'DeviceDTO[id=$id, identifier=$identifier, name=$name, ipAddressWLAN=$ipAddressWLAN, ipAddressETH=$ipAddressETH, configurationId=$configurationId, configuration=$configuration, connected=$connected, dateCreation=$dateCreation, dateUpdate=$dateUpdate, instanceId=$instanceId]';
|
'DeviceDTO[id=$id, identifier=$identifier, name=$name, ipAddressWLAN=$ipAddressWLAN, ipAddressETH=$ipAddressETH, configurationId=$configurationId, configuration=$configuration, connected=$connected, dateCreation=$dateCreation, dateUpdate=$dateUpdate, instanceId=$instanceId, appType=$appType]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -146,6 +152,11 @@ class DeviceDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'instanceId'] = null;
|
json[r'instanceId'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.appType != null) {
|
||||||
|
json[r'appType'] = this.appType;
|
||||||
|
} else {
|
||||||
|
json[r'appType'] = null;
|
||||||
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +192,7 @@ class DeviceDTO {
|
|||||||
dateCreation: mapDateTime(json, r'dateCreation', r''),
|
dateCreation: mapDateTime(json, r'dateCreation', r''),
|
||||||
dateUpdate: mapDateTime(json, r'dateUpdate', r''),
|
dateUpdate: mapDateTime(json, r'dateUpdate', r''),
|
||||||
instanceId: mapValueOfType<String>(json, r'instanceId'),
|
instanceId: mapValueOfType<String>(json, r'instanceId'),
|
||||||
|
appType: AppType.fromJson(json[r'appType']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -29,6 +29,8 @@ class GeoPointDTO {
|
|||||||
this.polyColor,
|
this.polyColor,
|
||||||
this.sectionMapId,
|
this.sectionMapId,
|
||||||
this.sectionEventId,
|
this.sectionEventId,
|
||||||
|
this.sectionScene3DId,
|
||||||
|
this.localTransform,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? id;
|
int? id;
|
||||||
@ -63,6 +65,13 @@ class GeoPointDTO {
|
|||||||
|
|
||||||
String? sectionEventId;
|
String? sectionEventId;
|
||||||
|
|
||||||
|
/// Section maquette 3D a laquelle ce point appartient, le cas echeant.
|
||||||
|
String? sectionScene3DId;
|
||||||
|
|
||||||
|
/// Position du point sur la maquette. Nulle sur un point de carte : les deux
|
||||||
|
/// positions coexistent, geometry pour le terrain et celle-ci pour le modele.
|
||||||
|
Position3D? localTransform;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
@ -82,7 +91,9 @@ class GeoPointDTO {
|
|||||||
other.geometry == geometry &&
|
other.geometry == geometry &&
|
||||||
other.polyColor == polyColor &&
|
other.polyColor == polyColor &&
|
||||||
other.sectionMapId == sectionMapId &&
|
other.sectionMapId == sectionMapId &&
|
||||||
other.sectionEventId == sectionEventId;
|
other.sectionEventId == sectionEventId &&
|
||||||
|
other.sectionScene3DId == sectionScene3DId &&
|
||||||
|
other.localTransform == localTransform;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
@ -102,11 +113,13 @@ class GeoPointDTO {
|
|||||||
(geometry == null ? 0 : geometry!.hashCode) +
|
(geometry == null ? 0 : geometry!.hashCode) +
|
||||||
(polyColor == null ? 0 : polyColor!.hashCode) +
|
(polyColor == null ? 0 : polyColor!.hashCode) +
|
||||||
(sectionMapId == null ? 0 : sectionMapId!.hashCode) +
|
(sectionMapId == null ? 0 : sectionMapId!.hashCode) +
|
||||||
(sectionEventId == null ? 0 : sectionEventId!.hashCode);
|
(sectionEventId == null ? 0 : sectionEventId!.hashCode) +
|
||||||
|
(sectionScene3DId == null ? 0 : sectionScene3DId!.hashCode) +
|
||||||
|
(localTransform == null ? 0 : localTransform!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'GeoPointDTO[id=$id, title=$title, description=$description, contents=$contents, categorieId=$categorieId, imageResourceId=$imageResourceId, imageUrl=$imageUrl, schedules=$schedules, prices=$prices, phone=$phone, email=$email, site=$site, geometry=$geometry, polyColor=$polyColor, sectionMapId=$sectionMapId, sectionEventId=$sectionEventId]';
|
'GeoPointDTO[id=$id, title=$title, description=$description, contents=$contents, categorieId=$categorieId, imageResourceId=$imageResourceId, imageUrl=$imageUrl, schedules=$schedules, prices=$prices, phone=$phone, email=$email, site=$site, geometry=$geometry, polyColor=$polyColor, sectionMapId=$sectionMapId, sectionEventId=$sectionEventId, sectionScene3DId=$sectionScene3DId, localTransform=$localTransform]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -230,6 +243,8 @@ class GeoPointDTO {
|
|||||||
polyColor: mapValueOfType<String>(json, r'polyColor'),
|
polyColor: mapValueOfType<String>(json, r'polyColor'),
|
||||||
sectionMapId: mapValueOfType<String>(json, r'sectionMapId'),
|
sectionMapId: mapValueOfType<String>(json, r'sectionMapId'),
|
||||||
sectionEventId: mapValueOfType<String>(json, r'sectionEventId'),
|
sectionEventId: mapValueOfType<String>(json, r'sectionEventId'),
|
||||||
|
sectionScene3DId: mapValueOfType<String>(json, r'sectionScene3DId'),
|
||||||
|
localTransform: Position3D.fromJson(json[r'localTransform']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
130
manager_api_new/lib/model/immersive_background_dto.dart
Normal file
130
manager_api_new/lib/model/immersive_background_dto.dart
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// Le fond d'un lieu. Un seul type, deux porteurs : une `Configuration` le porte
|
||||||
|
/// pour le fond d'une visite, l'`ApplicationInstance` VR pour le fond du menu.
|
||||||
|
///
|
||||||
|
/// ⚠️ Le repli n'est pas decoratif : trois canaux sur quatre ne rendent pas un
|
||||||
|
/// panorama. Sans `fallbackResourceId`, activer un fond immersif noircirait
|
||||||
|
/// l'ecran partout ailleurs que dans le casque.
|
||||||
|
class ImmersiveBackgroundDTO {
|
||||||
|
ImmersiveBackgroundDTO({
|
||||||
|
this.resourceId,
|
||||||
|
this.kind,
|
||||||
|
this.fallbackResourceId,
|
||||||
|
this.resourceUrl,
|
||||||
|
this.fallbackUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? resourceId;
|
||||||
|
|
||||||
|
ImmersiveBackgroundKind? kind;
|
||||||
|
|
||||||
|
String? fallbackResourceId;
|
||||||
|
|
||||||
|
/// URL de la ressource, posee par le serveur a la lecture.
|
||||||
|
String? resourceUrl;
|
||||||
|
|
||||||
|
String? fallbackUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is ImmersiveBackgroundDTO &&
|
||||||
|
other.resourceId == resourceId &&
|
||||||
|
other.kind == kind &&
|
||||||
|
other.fallbackResourceId == fallbackResourceId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
(resourceId == null ? 0 : resourceId!.hashCode) +
|
||||||
|
(kind == null ? 0 : kind!.hashCode) +
|
||||||
|
(fallbackResourceId == null ? 0 : fallbackResourceId!.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'ImmersiveBackgroundDTO[resourceId=$resourceId, kind=$kind, fallbackResourceId=$fallbackResourceId]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
if (this.resourceId != null) {
|
||||||
|
json[r'resourceId'] = this.resourceId;
|
||||||
|
} else {
|
||||||
|
json[r'resourceId'] = null;
|
||||||
|
}
|
||||||
|
if (this.kind != null) {
|
||||||
|
json[r'kind'] = this.kind;
|
||||||
|
} else {
|
||||||
|
json[r'kind'] = null;
|
||||||
|
}
|
||||||
|
if (this.fallbackResourceId != null) {
|
||||||
|
json[r'fallbackResourceId'] = this.fallbackResourceId;
|
||||||
|
} else {
|
||||||
|
json[r'fallbackResourceId'] = null;
|
||||||
|
}
|
||||||
|
if (this.resourceUrl != null) {
|
||||||
|
json[r'resourceUrl'] = this.resourceUrl;
|
||||||
|
} else {
|
||||||
|
json[r'resourceUrl'] = null;
|
||||||
|
}
|
||||||
|
if (this.fallbackUrl != null) {
|
||||||
|
json[r'fallbackUrl'] = this.fallbackUrl;
|
||||||
|
} else {
|
||||||
|
json[r'fallbackUrl'] = null;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImmersiveBackgroundDTO? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return ImmersiveBackgroundDTO(
|
||||||
|
resourceId: mapValueOfType<String>(json, r'resourceId'),
|
||||||
|
kind: ImmersiveBackgroundKind.fromJson(json[r'kind']),
|
||||||
|
fallbackResourceId: mapValueOfType<String>(json, r'fallbackResourceId'),
|
||||||
|
resourceUrl: mapValueOfType<String>(json, r'resourceUrl'),
|
||||||
|
fallbackUrl: mapValueOfType<String>(json, r'fallbackUrl'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<ImmersiveBackgroundDTO> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <ImmersiveBackgroundDTO>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = ImmersiveBackgroundDTO.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, ImmersiveBackgroundDTO> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, ImmersiveBackgroundDTO>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = ImmersiveBackgroundDTO.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const requiredKeys = <String>{};
|
||||||
|
}
|
||||||
90
manager_api_new/lib/model/immersive_background_kind.dart
Normal file
90
manager_api_new/lib/model/immersive_background_kind.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// Ce qu'est le fond d'une visite : un panorama fixe, une video 360 qui tourne en
|
||||||
|
/// boucle, ou une scene 3D.
|
||||||
|
///
|
||||||
|
/// 0 = Pano 1 = Video360 2 = Scene3D
|
||||||
|
class ImmersiveBackgroundKind {
|
||||||
|
const ImmersiveBackgroundKind._(this.value);
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value.toString();
|
||||||
|
|
||||||
|
int toJson() => value;
|
||||||
|
|
||||||
|
/// Une image equirectangulaire : le cas le plus courant et le moins couteux.
|
||||||
|
static const Pano = ImmersiveBackgroundKind._(0);
|
||||||
|
|
||||||
|
/// Une video equirectangulaire, jouee en boucle et sans son.
|
||||||
|
static const Video360 = ImmersiveBackgroundKind._(1);
|
||||||
|
|
||||||
|
/// Un decor 3D. Prevu par le contrat, pas encore rendu en fond par le casque.
|
||||||
|
static const Scene3D = ImmersiveBackgroundKind._(2);
|
||||||
|
|
||||||
|
static const values = <ImmersiveBackgroundKind>[Pano, Video360, Scene3D];
|
||||||
|
|
||||||
|
static ImmersiveBackgroundKind? fromJson(dynamic value) =>
|
||||||
|
ImmersiveBackgroundKindTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<ImmersiveBackgroundKind> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <ImmersiveBackgroundKind>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = ImmersiveBackgroundKind.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImmersiveBackgroundKindTypeTransformer {
|
||||||
|
factory ImmersiveBackgroundKindTypeTransformer() => _instance ??= const ImmersiveBackgroundKindTypeTransformer._();
|
||||||
|
|
||||||
|
const ImmersiveBackgroundKindTypeTransformer._();
|
||||||
|
|
||||||
|
int encode(ImmersiveBackgroundKind data) => data.value;
|
||||||
|
|
||||||
|
ImmersiveBackgroundKind? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
if (data.runtimeType == String) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'Pano': return ImmersiveBackgroundKind.Pano;
|
||||||
|
case r'Video360': return ImmersiveBackgroundKind.Video360;
|
||||||
|
case r'Scene3D': return ImmersiveBackgroundKind.Scene3D;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data.runtimeType == int) {
|
||||||
|
switch (data) {
|
||||||
|
case 0: return ImmersiveBackgroundKind.Pano;
|
||||||
|
case 1: return ImmersiveBackgroundKind.Video360;
|
||||||
|
case 2: return ImmersiveBackgroundKind.Scene3D;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImmersiveBackgroundKindTypeTransformer? _instance;
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ class InstanceDTO {
|
|||||||
this.isWeb,
|
this.isWeb,
|
||||||
this.isVR,
|
this.isVR,
|
||||||
this.isAssistant,
|
this.isAssistant,
|
||||||
|
this.hasImmersiveContent,
|
||||||
this.guideName,
|
this.guideName,
|
||||||
this.guidePersonaPrompt,
|
this.guidePersonaPrompt,
|
||||||
this.guideVoiceId,
|
this.guideVoiceId,
|
||||||
@ -101,6 +102,9 @@ class InstanceDTO {
|
|||||||
|
|
||||||
bool? isAssistant;
|
bool? isAssistant;
|
||||||
|
|
||||||
|
/// Add-on « Contenu immersif » : image 360, video 360 et modeles 3D.
|
||||||
|
bool? hasImmersiveContent;
|
||||||
|
|
||||||
/// Nom du guide affiché au visiteur, libre.
|
/// Nom du guide affiché au visiteur, libre.
|
||||||
String? guideName;
|
String? guideName;
|
||||||
|
|
||||||
@ -159,6 +163,7 @@ class InstanceDTO {
|
|||||||
other.isWeb == isWeb &&
|
other.isWeb == isWeb &&
|
||||||
other.isVR == isVR &&
|
other.isVR == isVR &&
|
||||||
other.isAssistant == isAssistant &&
|
other.isAssistant == isAssistant &&
|
||||||
|
other.hasImmersiveContent == hasImmersiveContent &&
|
||||||
other.guideName == guideName &&
|
other.guideName == guideName &&
|
||||||
other.guidePersonaPrompt == guidePersonaPrompt &&
|
other.guidePersonaPrompt == guidePersonaPrompt &&
|
||||||
other.guideVoiceId == guideVoiceId &&
|
other.guideVoiceId == guideVoiceId &&
|
||||||
@ -188,6 +193,7 @@ class InstanceDTO {
|
|||||||
(isWeb == null ? 0 : isWeb!.hashCode) +
|
(isWeb == null ? 0 : isWeb!.hashCode) +
|
||||||
(isVR == null ? 0 : isVR!.hashCode) +
|
(isVR == null ? 0 : isVR!.hashCode) +
|
||||||
(isAssistant == null ? 0 : isAssistant!.hashCode) +
|
(isAssistant == null ? 0 : isAssistant!.hashCode) +
|
||||||
|
(hasImmersiveContent == null ? 0 : hasImmersiveContent!.hashCode) +
|
||||||
(guideName == null ? 0 : guideName!.hashCode) +
|
(guideName == null ? 0 : guideName!.hashCode) +
|
||||||
(guidePersonaPrompt == null ? 0 : guidePersonaPrompt!.hashCode) +
|
(guidePersonaPrompt == null ? 0 : guidePersonaPrompt!.hashCode) +
|
||||||
(guideVoiceId == null ? 0 : guideVoiceId!.hashCode) +
|
(guideVoiceId == null ? 0 : guideVoiceId!.hashCode) +
|
||||||
@ -249,6 +255,11 @@ class InstanceDTO {
|
|||||||
} else {
|
} else {
|
||||||
json[r'isVR'] = null;
|
json[r'isVR'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.hasImmersiveContent != null) {
|
||||||
|
json[r'hasImmersiveContent'] = this.hasImmersiveContent;
|
||||||
|
} else {
|
||||||
|
json[r'hasImmersiveContent'] = null;
|
||||||
|
}
|
||||||
if (this.isAssistant != null) {
|
if (this.isAssistant != null) {
|
||||||
json[r'isAssistant'] = this.isAssistant;
|
json[r'isAssistant'] = this.isAssistant;
|
||||||
} else {
|
} else {
|
||||||
@ -379,6 +390,7 @@ class InstanceDTO {
|
|||||||
isWeb: mapValueOfType<bool>(json, r'isWeb'),
|
isWeb: mapValueOfType<bool>(json, r'isWeb'),
|
||||||
isVR: mapValueOfType<bool>(json, r'isVR'),
|
isVR: mapValueOfType<bool>(json, r'isVR'),
|
||||||
isAssistant: mapValueOfType<bool>(json, r'isAssistant'),
|
isAssistant: mapValueOfType<bool>(json, r'isAssistant'),
|
||||||
|
hasImmersiveContent: mapValueOfType<bool>(json, r'hasImmersiveContent'),
|
||||||
guideName: mapValueOfType<String>(json, r'guideName'),
|
guideName: mapValueOfType<String>(json, r'guideName'),
|
||||||
guidePersonaPrompt: mapValueOfType<String>(json, r'guidePersonaPrompt'),
|
guidePersonaPrompt: mapValueOfType<String>(json, r'guidePersonaPrompt'),
|
||||||
guideVoiceId: mapValueOfType<String>(json, r'guideVoiceId'),
|
guideVoiceId: mapValueOfType<String>(json, r'guideVoiceId'),
|
||||||
|
|||||||
106
manager_api_new/lib/model/position3_d.dart
Normal file
106
manager_api_new/lib/model/position3_d.dart
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// Position d'un point d'interet sur une maquette 3D, dans le repere du modele.
|
||||||
|
///
|
||||||
|
/// Convention glTF — Y vers le haut, Z vers l'arriere, main droite — et non celle
|
||||||
|
/// d'Unity. La conversion vit a un seul endroit cote casque : un miroir d'axes est
|
||||||
|
/// invisible sur une maquette symetrique et tres couteux decouvert tard.
|
||||||
|
class Position3D {
|
||||||
|
Position3D({
|
||||||
|
this.x = 0,
|
||||||
|
this.y = 0,
|
||||||
|
this.z = 0,
|
||||||
|
this.rotationY,
|
||||||
|
});
|
||||||
|
|
||||||
|
double x;
|
||||||
|
|
||||||
|
double y;
|
||||||
|
|
||||||
|
double z;
|
||||||
|
|
||||||
|
/// Rotation en degres autour de Y. Sert a orienter un panneau vers l'allee.
|
||||||
|
double? rotationY;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is Position3D &&
|
||||||
|
other.x == x &&
|
||||||
|
other.y == y &&
|
||||||
|
other.z == z &&
|
||||||
|
other.rotationY == rotationY;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
x.hashCode + y.hashCode + z.hashCode + (rotationY == null ? 0 : rotationY!.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Position3D[x=$x, y=$y, z=$z, rotationY=$rotationY]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'x'] = this.x;
|
||||||
|
json[r'y'] = this.y;
|
||||||
|
json[r'z'] = this.z;
|
||||||
|
if (this.rotationY != null) {
|
||||||
|
json[r'rotationY'] = this.rotationY;
|
||||||
|
} else {
|
||||||
|
json[r'rotationY'] = null;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Position3D? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return Position3D(
|
||||||
|
x: mapValueOfType<num>(json, r'x')?.toDouble() ?? 0,
|
||||||
|
y: mapValueOfType<num>(json, r'y')?.toDouble() ?? 0,
|
||||||
|
z: mapValueOfType<num>(json, r'z')?.toDouble() ?? 0,
|
||||||
|
rotationY: mapValueOfType<num>(json, r'rotationY')?.toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Position3D> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <Position3D>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = Position3D.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, Position3D> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, Position3D>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = Position3D.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const requiredKeys = <String>{};
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@
|
|||||||
part of openapi.api;
|
part of openapi.api;
|
||||||
|
|
||||||
/// 0 = Image 1 = Video 2 = ImageUrl 3 = VideoUrl 4 = Audio 5 = PDF 6 = JSON 7 = JSONUrl
|
/// 0 = Image 1 = Video 2 = ImageUrl 3 = VideoUrl 4 = Audio 5 = PDF 6 = JSON 7 = JSONUrl
|
||||||
|
/// 8 = Word 9 = PowerPoint 10 = Text 11 = Image360 12 = Video360 13 = Model3D
|
||||||
class ResourceType {
|
class ResourceType {
|
||||||
/// Instantiate a new enum with the provided [value].
|
/// Instantiate a new enum with the provided [value].
|
||||||
const ResourceType._(this.value);
|
const ResourceType._(this.value);
|
||||||
@ -31,6 +32,19 @@ class ResourceType {
|
|||||||
static const Pdf = ResourceType._(5);
|
static const Pdf = ResourceType._(5);
|
||||||
static const Json = ResourceType._(6);
|
static const Json = ResourceType._(6);
|
||||||
static const JsonUrl = ResourceType._(7);
|
static const JsonUrl = ResourceType._(7);
|
||||||
|
static const Word = ResourceType._(8);
|
||||||
|
static const PowerPoint = ResourceType._(9);
|
||||||
|
static const Text = ResourceType._(10);
|
||||||
|
|
||||||
|
/// Photo equirectangulaire, affichee en skybox dans un casque.
|
||||||
|
static const Image360 = ResourceType._(11);
|
||||||
|
|
||||||
|
/// Video equirectangulaire, meme usage.
|
||||||
|
static const Video360 = ResourceType._(12);
|
||||||
|
|
||||||
|
/// Modele 3D glTF binaire. La valeur existe ; son exploitation demande encore un
|
||||||
|
/// type de section dedie et un editeur de placement de points d'interet.
|
||||||
|
static const Model3D = ResourceType._(13);
|
||||||
|
|
||||||
/// List of all possible values in this [enum][ResourceType].
|
/// List of all possible values in this [enum][ResourceType].
|
||||||
static const values = <ResourceType>[
|
static const values = <ResourceType>[
|
||||||
@ -41,7 +55,13 @@ class ResourceType {
|
|||||||
Audio,
|
Audio,
|
||||||
Pdf,
|
Pdf,
|
||||||
Json,
|
Json,
|
||||||
JsonUrl
|
JsonUrl,
|
||||||
|
Word,
|
||||||
|
PowerPoint,
|
||||||
|
Text,
|
||||||
|
Image360,
|
||||||
|
Video360,
|
||||||
|
Model3D
|
||||||
];
|
];
|
||||||
|
|
||||||
static ResourceType? fromJson(dynamic value) => ResourceTypeTypeTransformer().decode(value);
|
static ResourceType? fromJson(dynamic value) => ResourceTypeTypeTransformer().decode(value);
|
||||||
@ -89,6 +109,12 @@ class ResourceTypeTypeTransformer {
|
|||||||
case "PDF": return ResourceType.Pdf;
|
case "PDF": return ResourceType.Pdf;
|
||||||
case "JSON": return ResourceType.Json;
|
case "JSON": return ResourceType.Json;
|
||||||
case "JSONUrl": return ResourceType.JsonUrl;
|
case "JSONUrl": return ResourceType.JsonUrl;
|
||||||
|
case "Word": return ResourceType.Word;
|
||||||
|
case "PowerPoint": return ResourceType.PowerPoint;
|
||||||
|
case "Text": return ResourceType.Text;
|
||||||
|
case "Image360": return ResourceType.Image360;
|
||||||
|
case "Video360": return ResourceType.Video360;
|
||||||
|
case "Model3D": return ResourceType.Model3D;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
@ -105,6 +131,12 @@ class ResourceTypeTypeTransformer {
|
|||||||
case 5: return ResourceType.Pdf;
|
case 5: return ResourceType.Pdf;
|
||||||
case 6: return ResourceType.Json;
|
case 6: return ResourceType.Json;
|
||||||
case 7: return ResourceType.JsonUrl;
|
case 7: return ResourceType.JsonUrl;
|
||||||
|
case 8: return ResourceType.Word;
|
||||||
|
case 9: return ResourceType.PowerPoint;
|
||||||
|
case 10: return ResourceType.Text;
|
||||||
|
case 11: return ResourceType.Image360;
|
||||||
|
case 12: return ResourceType.Video360;
|
||||||
|
case 13: return ResourceType.Model3D;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
|||||||
299
manager_api_new/lib/model/scene3_d_dto.dart
Normal file
299
manager_api_new/lib/model/scene3_d_dto.dart
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// Une scene 3D — un objet ou un decor — et ses points d'interet (item E7 du lot XR-4).
|
||||||
|
///
|
||||||
|
/// Le modele arrive par un id de ressource `Model3D`, comme une image ou une video :
|
||||||
|
/// rien de particulier dans la mediatheque, et le telechargement hors ligne l'emporte
|
||||||
|
/// avec le reste de la visite.
|
||||||
|
class Scene3DDTO {
|
||||||
|
Scene3DDTO({
|
||||||
|
this.id,
|
||||||
|
this.label,
|
||||||
|
this.title = const [],
|
||||||
|
this.description = const [],
|
||||||
|
this.isActive,
|
||||||
|
this.imageId,
|
||||||
|
this.imageSource,
|
||||||
|
this.configurationId,
|
||||||
|
this.isSubSection,
|
||||||
|
this.parentId,
|
||||||
|
this.type,
|
||||||
|
this.dateCreation,
|
||||||
|
this.order,
|
||||||
|
this.instanceId,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
|
this.meterZoneGPS,
|
||||||
|
this.isBeacon,
|
||||||
|
this.beaconId,
|
||||||
|
this.model3DResourceId,
|
||||||
|
this.model3DSource,
|
||||||
|
this.mode,
|
||||||
|
this.points = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
String? id;
|
||||||
|
|
||||||
|
String? label;
|
||||||
|
|
||||||
|
List<TranslationDTO>? title;
|
||||||
|
|
||||||
|
List<TranslationDTO>? description;
|
||||||
|
|
||||||
|
bool? isActive;
|
||||||
|
|
||||||
|
String? imageId;
|
||||||
|
|
||||||
|
String? imageSource;
|
||||||
|
|
||||||
|
String? configurationId;
|
||||||
|
|
||||||
|
bool? isSubSection;
|
||||||
|
|
||||||
|
String? parentId;
|
||||||
|
|
||||||
|
SectionType? type;
|
||||||
|
|
||||||
|
DateTime? dateCreation;
|
||||||
|
|
||||||
|
int? order;
|
||||||
|
|
||||||
|
String? instanceId;
|
||||||
|
|
||||||
|
String? latitude;
|
||||||
|
|
||||||
|
String? longitude;
|
||||||
|
|
||||||
|
int? meterZoneGPS;
|
||||||
|
|
||||||
|
bool? isBeacon;
|
||||||
|
|
||||||
|
int? beaconId;
|
||||||
|
|
||||||
|
/// Ressource `Model3D` (glTF binaire) affichee par cette section.
|
||||||
|
String? model3DResourceId;
|
||||||
|
|
||||||
|
/// URL du modele, remplie a la lecture comme `imageSource`.
|
||||||
|
String? model3DSource;
|
||||||
|
|
||||||
|
/// Objet manipule (0) ou decor habite (1).
|
||||||
|
Scene3DMode? mode;
|
||||||
|
|
||||||
|
/// Points poses sur le modele. Meme objet que ceux d'une carte : seule leur
|
||||||
|
/// position change de nature.
|
||||||
|
List<GeoPointDTO>? points;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is Scene3DDTO &&
|
||||||
|
other.id == id &&
|
||||||
|
other.label == label &&
|
||||||
|
other.model3DResourceId == model3DResourceId &&
|
||||||
|
other.model3DSource == model3DSource &&
|
||||||
|
_deepEquality.equals(other.points, points);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
(id == null ? 0 : id!.hashCode) +
|
||||||
|
(label == null ? 0 : label!.hashCode) +
|
||||||
|
(model3DResourceId == null ? 0 : model3DResourceId!.hashCode) +
|
||||||
|
(model3DSource == null ? 0 : model3DSource!.hashCode) +
|
||||||
|
(points == null ? 0 : points!.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'Scene3DDTO[id=$id, label=$label, model3DResourceId=$model3DResourceId, points=$points]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
if (this.id != null) {
|
||||||
|
json[r'id'] = this.id;
|
||||||
|
} else {
|
||||||
|
json[r'id'] = null;
|
||||||
|
}
|
||||||
|
if (this.label != null) {
|
||||||
|
json[r'label'] = this.label;
|
||||||
|
} else {
|
||||||
|
json[r'label'] = null;
|
||||||
|
}
|
||||||
|
if (this.title != null) {
|
||||||
|
json[r'title'] = this.title;
|
||||||
|
} else {
|
||||||
|
json[r'title'] = null;
|
||||||
|
}
|
||||||
|
if (this.description != null) {
|
||||||
|
json[r'description'] = this.description;
|
||||||
|
} else {
|
||||||
|
json[r'description'] = null;
|
||||||
|
}
|
||||||
|
if (this.isActive != null) {
|
||||||
|
json[r'isActive'] = this.isActive;
|
||||||
|
} else {
|
||||||
|
json[r'isActive'] = null;
|
||||||
|
}
|
||||||
|
if (this.imageId != null) {
|
||||||
|
json[r'imageId'] = this.imageId;
|
||||||
|
} else {
|
||||||
|
json[r'imageId'] = null;
|
||||||
|
}
|
||||||
|
if (this.imageSource != null) {
|
||||||
|
json[r'imageSource'] = this.imageSource;
|
||||||
|
} else {
|
||||||
|
json[r'imageSource'] = null;
|
||||||
|
}
|
||||||
|
if (this.configurationId != null) {
|
||||||
|
json[r'configurationId'] = this.configurationId;
|
||||||
|
} else {
|
||||||
|
json[r'configurationId'] = null;
|
||||||
|
}
|
||||||
|
if (this.isSubSection != null) {
|
||||||
|
json[r'isSubSection'] = this.isSubSection;
|
||||||
|
} else {
|
||||||
|
json[r'isSubSection'] = null;
|
||||||
|
}
|
||||||
|
if (this.parentId != null) {
|
||||||
|
json[r'parentId'] = this.parentId;
|
||||||
|
} else {
|
||||||
|
json[r'parentId'] = null;
|
||||||
|
}
|
||||||
|
if (this.type != null) {
|
||||||
|
json[r'type'] = this.type;
|
||||||
|
} else {
|
||||||
|
json[r'type'] = null;
|
||||||
|
}
|
||||||
|
if (this.dateCreation != null) {
|
||||||
|
json[r'dateCreation'] = this.dateCreation!.toUtc().toIso8601String();
|
||||||
|
} else {
|
||||||
|
json[r'dateCreation'] = null;
|
||||||
|
}
|
||||||
|
if (this.order != null) {
|
||||||
|
json[r'order'] = this.order;
|
||||||
|
} else {
|
||||||
|
json[r'order'] = null;
|
||||||
|
}
|
||||||
|
if (this.instanceId != null) {
|
||||||
|
json[r'instanceId'] = this.instanceId;
|
||||||
|
} else {
|
||||||
|
json[r'instanceId'] = null;
|
||||||
|
}
|
||||||
|
if (this.latitude != null) {
|
||||||
|
json[r'latitude'] = this.latitude;
|
||||||
|
} else {
|
||||||
|
json[r'latitude'] = null;
|
||||||
|
}
|
||||||
|
if (this.longitude != null) {
|
||||||
|
json[r'longitude'] = this.longitude;
|
||||||
|
} else {
|
||||||
|
json[r'longitude'] = null;
|
||||||
|
}
|
||||||
|
if (this.meterZoneGPS != null) {
|
||||||
|
json[r'meterZoneGPS'] = this.meterZoneGPS;
|
||||||
|
} else {
|
||||||
|
json[r'meterZoneGPS'] = null;
|
||||||
|
}
|
||||||
|
if (this.isBeacon != null) {
|
||||||
|
json[r'isBeacon'] = this.isBeacon;
|
||||||
|
} else {
|
||||||
|
json[r'isBeacon'] = null;
|
||||||
|
}
|
||||||
|
if (this.beaconId != null) {
|
||||||
|
json[r'beaconId'] = this.beaconId;
|
||||||
|
} else {
|
||||||
|
json[r'beaconId'] = null;
|
||||||
|
}
|
||||||
|
if (this.model3DResourceId != null) {
|
||||||
|
json[r'model3DResourceId'] = this.model3DResourceId;
|
||||||
|
} else {
|
||||||
|
json[r'model3DResourceId'] = null;
|
||||||
|
}
|
||||||
|
if (this.model3DSource != null) {
|
||||||
|
json[r'model3DSource'] = this.model3DSource;
|
||||||
|
} else {
|
||||||
|
json[r'model3DSource'] = null;
|
||||||
|
}
|
||||||
|
if (this.mode != null) {
|
||||||
|
json[r'mode'] = this.mode;
|
||||||
|
} else {
|
||||||
|
json[r'mode'] = null;
|
||||||
|
}
|
||||||
|
if (this.points != null) {
|
||||||
|
json[r'points'] = this.points;
|
||||||
|
} else {
|
||||||
|
json[r'points'] = null;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Scene3DDTO? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return Scene3DDTO(
|
||||||
|
id: mapValueOfType<String>(json, r'id'),
|
||||||
|
label: mapValueOfType<String>(json, r'label'),
|
||||||
|
title: TranslationDTO.listFromJson(json[r'title']),
|
||||||
|
description: TranslationDTO.listFromJson(json[r'description']),
|
||||||
|
isActive: mapValueOfType<bool>(json, r'isActive'),
|
||||||
|
imageId: mapValueOfType<String>(json, r'imageId'),
|
||||||
|
imageSource: mapValueOfType<String>(json, r'imageSource'),
|
||||||
|
configurationId: mapValueOfType<String>(json, r'configurationId'),
|
||||||
|
isSubSection: mapValueOfType<bool>(json, r'isSubSection'),
|
||||||
|
parentId: mapValueOfType<String>(json, r'parentId'),
|
||||||
|
type: SectionType.fromJson(json[r'type']),
|
||||||
|
dateCreation: mapDateTime(json, r'dateCreation', r''),
|
||||||
|
order: mapValueOfType<int>(json, r'order'),
|
||||||
|
instanceId: mapValueOfType<String>(json, r'instanceId'),
|
||||||
|
latitude: mapValueOfType<String>(json, r'latitude'),
|
||||||
|
longitude: mapValueOfType<String>(json, r'longitude'),
|
||||||
|
meterZoneGPS: mapValueOfType<int>(json, r'meterZoneGPS'),
|
||||||
|
isBeacon: mapValueOfType<bool>(json, r'isBeacon'),
|
||||||
|
beaconId: mapValueOfType<int>(json, r'beaconId'),
|
||||||
|
model3DResourceId: mapValueOfType<String>(json, r'model3DResourceId'),
|
||||||
|
model3DSource: mapValueOfType<String>(json, r'model3DSource'),
|
||||||
|
mode: Scene3DMode.fromJson(json[r'mode']),
|
||||||
|
points: GeoPointDTO.listFromJson(json[r'points']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Scene3DDTO> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <Scene3DDTO>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = Scene3DDTO.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, Scene3DDTO> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, Scene3DDTO>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = Scene3DDTO.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const requiredKeys = <String>{};
|
||||||
|
}
|
||||||
90
manager_api_new/lib/model/scene3_d_mode.dart
Normal file
90
manager_api_new/lib/model/scene3_d_mode.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
/// Ce que le visiteur fait d'une scene 3D, et c'est une opposition franche :
|
||||||
|
/// soit il **manipule un objet** pose devant lui (camera orbitale, les points
|
||||||
|
/// tournent avec l'objet), soit il **est dedans** et regarde autour (camera fixe,
|
||||||
|
/// les points restent ou ils sont).
|
||||||
|
///
|
||||||
|
/// Meme GLB, meme modele de point, meme editeur. Ce qui change, c'est ou l'on met
|
||||||
|
/// le visiteur — et aucun fichier ne peut le deviner.
|
||||||
|
///
|
||||||
|
/// 0 = Asset 1 = Scene
|
||||||
|
class Scene3DMode {
|
||||||
|
const Scene3DMode._(this.value);
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value.toString();
|
||||||
|
|
||||||
|
int toJson() => value;
|
||||||
|
|
||||||
|
/// Un objet pose devant le visiteur : l'epee du roi, une piece de collection.
|
||||||
|
static const Asset = Scene3DMode._(0);
|
||||||
|
|
||||||
|
/// Un decor dans lequel le visiteur se trouve : une salle reconstituee.
|
||||||
|
static const Scene = Scene3DMode._(1);
|
||||||
|
|
||||||
|
static const values = <Scene3DMode>[Asset, Scene];
|
||||||
|
|
||||||
|
static Scene3DMode? fromJson(dynamic value) =>
|
||||||
|
Scene3DModeTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<Scene3DMode> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <Scene3DMode>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = Scene3DMode.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Scene3DModeTypeTransformer {
|
||||||
|
factory Scene3DModeTypeTransformer() => _instance ??= const Scene3DModeTypeTransformer._();
|
||||||
|
|
||||||
|
const Scene3DModeTypeTransformer._();
|
||||||
|
|
||||||
|
int encode(Scene3DMode data) => data.value;
|
||||||
|
|
||||||
|
Scene3DMode? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
if (data.runtimeType == String) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'Asset': return Scene3DMode.Asset;
|
||||||
|
case r'Scene': return Scene3DMode.Scene;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data.runtimeType == int) {
|
||||||
|
switch (data) {
|
||||||
|
case 0: return Scene3DMode.Asset;
|
||||||
|
case 1: return Scene3DMode.Scene;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Scene3DModeTypeTransformer? _instance;
|
||||||
|
}
|
||||||
@ -37,6 +37,9 @@ class SectionType {
|
|||||||
static const Event = SectionType._(11);
|
static const Event = SectionType._(11);
|
||||||
static const Parcours = SectionType._(12);
|
static const Parcours = SectionType._(12);
|
||||||
|
|
||||||
|
/// Maquette 3D avec points d'interet (E7 du lot XR-4).
|
||||||
|
static const Scene3D = SectionType._(13);
|
||||||
|
|
||||||
/// List of all possible values in this [enum][SectionType].
|
/// List of all possible values in this [enum][SectionType].
|
||||||
static const values = <SectionType>[
|
static const values = <SectionType>[
|
||||||
Map,
|
Map,
|
||||||
@ -52,6 +55,7 @@ class SectionType {
|
|||||||
Weather,
|
Weather,
|
||||||
Event,
|
Event,
|
||||||
Parcours,
|
Parcours,
|
||||||
|
Scene3D,
|
||||||
];
|
];
|
||||||
|
|
||||||
static SectionType? fromJson(dynamic value) => SectionTypeTypeTransformer().decode(value);
|
static SectionType? fromJson(dynamic value) => SectionTypeTypeTransformer().decode(value);
|
||||||
@ -104,6 +108,7 @@ class SectionTypeTypeTransformer {
|
|||||||
case r'Weather': return SectionType.Weather;
|
case r'Weather': return SectionType.Weather;
|
||||||
case r'Event': return SectionType.Event;
|
case r'Event': return SectionType.Event;
|
||||||
case r'Parcours': return SectionType.Parcours;
|
case r'Parcours': return SectionType.Parcours;
|
||||||
|
case r'Scene3D': return SectionType.Scene3D;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
@ -125,6 +130,7 @@ class SectionTypeTypeTransformer {
|
|||||||
case 10: return SectionType.Weather;
|
case 10: return SectionType.Weather;
|
||||||
case 11: return SectionType.Event;
|
case 11: return SectionType.Event;
|
||||||
case 12: return SectionType.Parcours;
|
case 12: return SectionType.Parcours;
|
||||||
|
case 13: return SectionType.Scene3D;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user