Studio lot 8e : lecteur audio dans la fiche d'un point de carte

PointAudio résout la ressource et affiche le portrait du narrateur.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011VxSQeGQYUvPmSEoGdnidA
This commit is contained in:
Thomas Fransolet 2026-09-15 23:00:46 +02:00
parent 8979ab2e63
commit 296b7a7519
2 changed files with 72 additions and 0 deletions

View File

@ -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<PointAudio> createState() => _PointAudioState();
}
class _PointAudioState extends State<PointAudio> {
/// Gardé : la fiche du point se reconstruit souvent, et chaque reconstruction relancerait la requête.
late Future<ResourceDTO?> _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<ResourceDTO?>(
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,
],
),
);
},
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:tablet_app/Components/point_audio.dart';
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -58,6 +59,11 @@ class _MarkerInfoWidget extends State<MarkerViewWidget> {
ScrollController scrollDescription = new ScrollController(); ScrollController scrollDescription = new ScrollController();
ScrollController scrollPrice = 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; 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)); Color primaryColor = new Color(int.parse(tabletAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16));
@ -194,6 +200,12 @@ class _MarkerInfoWidget extends State<MarkerViewWidget> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ 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) 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( HtmlWidget(
selectedPoint.description!.firstWhere((d) => d.language == language).value!, selectedPoint.description!.firstWhere((d) => d.language == language).value!,