import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; import 'package:tablet_app/Components/cached_custom_resource.dart'; import 'package:tablet_app/Helpers/ImageCustomProvider.dart'; import 'package:tablet_app/Helpers/translationHelper.dart'; import 'package:tablet_app/Models/tabletContext.dart'; import 'package:tablet_app/app_context.dart'; import 'package:tablet_app/constants.dart'; /// Largeur de lecture. Une borne fait 1280 px de large : étirer le texte sur /// toute la dalle le rend illisible, l'œil perd la ligne au retour. const double kArticleMeasure = 720.0; const double kMediaRailFlex = 38.0; const double kArticleTextFlex = 62.0; class ArticleView extends StatefulWidget { final ArticleDTO section; const ArticleView({Key? key, required this.section}) : super(key: key); @override State createState() => _ArticleViewState(); } class _ArticleViewState extends State { String _audioId = ""; ResourceDTO? _audioResource; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _resolveAudio()); } /// L'audio est porté par langue : `audioIds` est une List. /// Pas d'entrée pour la langue choisie = pas de lecteur, sans repli sur une /// autre langue — servir l'audio français à un néerlandophone est pire que /// le silence. Future _resolveAudio() async { final tabletAppContext = Provider.of(context, listen: false).getContext() as TabletAppContext; final audioId = TranslationHelper.get(widget.section.audioIds, tabletAppContext); if (audioId.isEmpty) return; final embedded = widget.section.contents ?.where((content) => content.resourceId == audioId && content.resource != null) .firstOrNull; if (embedded != null) { if (mounted) { setState(() { _audioId = audioId; _audioResource = embedded.resource; }); } return; } try { final resource = await tabletAppContext.clientAPI!.resourceApi!.resourceGetDetail(audioId); if (resource != null && mounted) { setState(() { _audioId = audioId; _audioResource = resource; }); } } catch (e) { print("ERROR _resolveAudio ArticleView"); print(e); } } List _medias() { final contents = widget.section.contents ?.where((content) => content.resource != null && content.resourceId != _audioId) .toList() ?? []; contents.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0)); return contents; } @override Widget build(BuildContext context) { final appContext = Provider.of(context); final tabletAppContext = appContext.getContext() as TabletAppContext; final medias = _medias(); if (medias.isEmpty) { return _buildTextOnly(appContext, tabletAppContext); } final rail = Expanded( flex: kMediaRailFlex.toInt(), child: _buildMediaRail(appContext, tabletAppContext, medias), ); final text = Expanded( flex: kArticleTextFlex.toInt(), child: _buildTextColumn(tabletAppContext, showAudio: false), ); // isContentTop veut dire « le texte avant le média ». En portrait c'était // au-dessus ; en paysage il n'y a plus de dessus, le drapeau devient le côté. final contentFirst = widget.section.isContentTop ?? false; return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: contentFirst ? [text, rail] : [rail, text], ); } Widget _buildTextOnly(AppContext appContext, TabletAppContext tabletAppContext) { return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: kArticleMeasure), child: _buildTextColumn(tabletAppContext, showAudio: true), ), ); } Widget _buildMediaRail( AppContext appContext, TabletAppContext tabletAppContext, List medias, ) { final main = medias.first; final thumbs = medias.skip(1).toList(); return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: CachedCustomResource( resourceDTO: main.resource!, isAuto: false, webView: false, fit: BoxFit.cover, ), ), ), if (thumbs.isNotEmpty) ...[ const SizedBox(height: 12.0), SizedBox( height: 90.0, child: Row( children: thumbs .map((content) => Expanded( child: Padding( padding: const EdgeInsets.only(right: 8.0), child: ClipRRect( borderRadius: BorderRadius.circular(6.0), child: Image( image: ImageCustomProvider.getImageProvider( appContext, content.resourceId, content.resource!.url ?? "", ), fit: BoxFit.cover, ), ), ), )) .toList(), ), ), ], if (_audioResource != null) ...[ const SizedBox(height: 12.0), _buildAudio(), ], ], ), ); } Widget _buildTextColumn(TabletAppContext tabletAppContext, {required bool showAudio}) { final title = TranslationHelper.get(widget.section.title, tabletAppContext); final content = TranslationHelper.get(widget.section.content, tabletAppContext); return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 32.0), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: kArticleMeasure), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (title.isNotEmpty) Text( title, style: const TextStyle( fontSize: kSectionTitleDetailSize, fontWeight: FontWeight.bold, color: kMainGrey, ), ), if (showAudio && _audioResource != null) ...[ const SizedBox(height: 16.0), _buildAudio(), ], const SizedBox(height: 16.0), HtmlWidget( content, textStyle: const TextStyle( fontSize: kSectionDescriptionDetailSize, color: kSecondGrey, ), ), ], ), ), ); } Widget _buildAudio() { return CachedCustomResource( resourceDTO: _audioResource!, isAuto: widget.section.isReadAudioAuto ?? false, webView: false, ); } }