From 296b7a751965bd794c72ccad8967fdfa59fbe5c9 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Tue, 15 Sep 2026 23:00:46 +0200 Subject: [PATCH] Studio lot 8e : lecteur audio dans la fiche d'un point de carte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PointAudio résout la ressource et affiche le portrait du narrateur. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011VxSQeGQYUvPmSEoGdnidA --- lib/Components/point_audio.dart | 60 ++++++++++++++++++++++++++++++++ lib/Screens/Map/marker_view.dart | 12 +++++++ 2 files changed, 72 insertions(+) create mode 100644 lib/Components/point_audio.dart diff --git a/lib/Components/point_audio.dart b/lib/Components/point_audio.dart new file mode 100644 index 0000000..5cb12e1 --- /dev/null +++ b/lib/Components/point_audio.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:tablet_app/Components/cached_custom_resource.dart'; +import 'package:tablet_app/Components/narrator_avatar.dart'; + +/// Audio d'un point de carte (plan Studio lot 8e) : le point range un identifiant de ressource, +/// résolu ici, avec le portrait du narrateur quand c'est une narration. +class PointAudio extends StatefulWidget { + const PointAudio({Key? key, required this.resourceId, required this.resourceApi}) : super(key: key); + + final String resourceId; + final ResourceApi resourceApi; + + @override + State createState() => _PointAudioState(); +} + +class _PointAudioState extends State { + /// Gardé : la fiche du point se reconstruit souvent, et chaque reconstruction relancerait la requête. + late Future _resource; + + @override + void initState() { + super.initState(); + _resource = widget.resourceApi.resourceGetDetail(widget.resourceId); + } + + @override + void didUpdateWidget(PointAudio oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.resourceId != widget.resourceId) { + _resource = widget.resourceApi.resourceGetDetail(widget.resourceId); + } + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _resource, + builder: (context, snapshot) { + final resource = snapshot.data; + if (resource?.url == null) return const SizedBox.shrink(); + + final player = CachedCustomResource(resourceDTO: resource!, isAuto: false, webView: false); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: resource.narratorName == null + ? Align(alignment: Alignment.centerLeft, child: player) + : Row( + children: [ + NarratorAvatar(name: resource.narratorName!, portraitUrl: resource.narratorPortraitUrl), + const SizedBox(width: 16), + player, + ], + ), + ); + }, + ); + } +} diff --git a/lib/Screens/Map/marker_view.dart b/lib/Screens/Map/marker_view.dart index 275c27b..84cfb94 100644 --- a/lib/Screens/Map/marker_view.dart +++ b/lib/Screens/Map/marker_view.dart @@ -1,3 +1,4 @@ +import 'package:tablet_app/Components/point_audio.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; @@ -58,6 +59,11 @@ class _MarkerInfoWidget extends State { ScrollController scrollDescription = new ScrollController(); ScrollController scrollPrice = new ScrollController(); + // Un audio par langue, sans repli sur une autre : l'audio français à un néerlandophone est pire que le silence. + final pointAudioId = selectedPoint?.audioIds + ?.where((a) => a.language == language && (a.value ?? '').isNotEmpty) + .firstOrNull + ?.value; var isPointPrice = selectedPoint != null && selectedPoint.prices != null && selectedPoint.prices!.isNotEmpty && selectedPoint.prices!.any((d) => d.language == language) && selectedPoint.prices!.firstWhere((d) => d.language == language).value != null && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.trim().length > 0; Color primaryColor = new Color(int.parse(tabletAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)); @@ -194,6 +200,12 @@ class _MarkerInfoWidget extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ + if (pointAudioId != null) + PointAudio( + key: ValueKey('point-audio-$pointAudioId'), + resourceId: pointAudioId, + resourceApi: tabletAppContext.clientAPI!.resourceApi!, + ), if(selectedPoint.description!.any((d) => d.language == language) && selectedPoint.description!.firstWhere((d) => d.language == language).value != null && selectedPoint.description!.firstWhere((d) => d.language == language).value!.trim().length > 0) HtmlWidget( selectedPoint.description!.firstWhere((d) => d.language == language).value!,