mymuseum-visitapp/lib/Components/cached_custom_resource.dart
Thomas Fransolet d237987889 Lot E : rendu PDF des médias d'étape, et QuestionType nommé
Le cas PDF n'était traité nulle part : un PDF tombait dans le `default` et
affichait "Not supported type".

⚠️ Le correctif ne va pas dans showElementForResource comme l'annonçait le
plan : cette fonction fait un `return CachedCustomResource(...)` avant son
switch, donc tout son switch est du code mort. Le vrai dispatcher est
CachedCustomResource, et il en a deux — ressource distante et fichier local
déjà téléchargé pour l'offline. Les deux sont traités. Le cas distant doit
télécharger avant d'afficher, PDFView n'acceptant qu'un chemin de fichier.

QuestionType : _kindOf comparait des entiers bruts, remplacé par les alias
nommés du client.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:31:07 +02:00

179 lines
7.1 KiB
Dart

import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/audio_player.dart';
import 'package:mymuseum_visitapp/Components/video_viewer.dart';
import 'package:mymuseum_visitapp/Components/video_viewer_youtube.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
class CachedCustomResource extends StatelessWidget {
final ResourceDTO resourceDTO;
final bool isAuto;
final bool webView;
final BoxFit fit;
CachedCustomResource({
required this.resourceDTO,
required this.isAuto,
required this.webView,
this.fit = BoxFit.cover,
});
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
Color primaryColor = Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16));
if(resourceDTO.type == ResourceType.ImageUrl || resourceDTO.type == ResourceType.VideoUrl)
{
// Image Url or Video Url don't care, just get resource
if(resourceDTO.type == ResourceType.ImageUrl) {
return CachedNetworkImage(
imageUrl: resourceDTO.url!,
fit: BoxFit.fill,
progressIndicatorBuilder: (context, url, downloadProgress) =>
CircularProgressIndicator(value: downloadProgress.progress, color: primaryColor),
errorWidget: (context, url, error) => Icon(Icons.error),
);
} else {
if(resourceDTO.url == null) {
return const Center(child: Text("Error loading video"));
} else {
return VideoViewerYoutube(videoUrl: resourceDTO.url!, isAuto: isAuto, webView: webView);
}
}
} else {
// Check if exist on local storage, if no, just show it via url
print("Check local storage in cached custom resource");
return FutureBuilder<File?>(
future: _checkIfLocalResourceExists(visitAppContext),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// Loader ou indicateur de chargement pendant la vérification
return const CircularProgressIndicator();
} else if (snapshot.hasError || snapshot.data == null) {
// Si la ressource locale n'existe pas ou s'il y a une erreur
switch(resourceDTO.type) {
case ResourceType.Image :
return CachedNetworkImage(
imageUrl: resourceDTO.url!,
fit: fit,
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.error),
);
case ResourceType.Video :
return VideoViewer(file: null, videoUrl: resourceDTO.url!);
case ResourceType.Audio :
return AudioPlayerFloatingContainer(file: null, audioBytes: null, resourceURl: resourceDTO.url!, isAuto: isAuto);
case ResourceType.Pdf :
// PDFView ne lit qu'un fichier local : il faut télécharger d'abord.
return _RemotePdfView(url: resourceDTO.url!, progressColor: primaryColor);
default:
return const Text("Not supported type");
}
} else {
switch(resourceDTO.type) {
case ResourceType.Image :
return Image.file(
snapshot.data!,
fit: fit,
);
case ResourceType.Video :
return VideoViewer(file: snapshot.data!, videoUrl: resourceDTO.url!);
case ResourceType.Audio :
return AudioPlayerFloatingContainer(file: snapshot.data!, audioBytes: null, resourceURl: resourceDTO.url!, isAuto: isAuto);
case ResourceType.Pdf :
// Déjà téléchargé par la configuration hors ligne : lecture directe.
return PDFView(filePath: snapshot.data!.path);
default:
return const Text("Not supported type");
}
// Utilisation de l'image locale
}
},
);
}
}
Future<File?> _checkIfLocalResourceExists(VisitAppContext visitAppContext) async {
try {
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
Directory configurationDirectory = Directory('$localPath/${visitAppContext.configuration!.id}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(resourceDTO.id!))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(resourceDTO.id!)).path);
return file;
}
} catch(e) {
print("ERROR _checkIfLocalResourceExists CachedCustomResource");
print(e);
}
return null;
}
Future<String> get localPath async {
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
return appDocumentsDirectory!.path;
}
}
/// Affiche un PDF distant. `PDFView` n'accepte qu'un chemin de fichier, donc le
/// document est d'abord écrit dans le dossier documents — même approche que
/// `PDFPage`, à laquelle ce widget évite de renvoyer pour un simple média d'étape.
class _RemotePdfView extends StatefulWidget {
final String url;
final Color progressColor;
const _RemotePdfView({required this.url, required this.progressColor});
@override
State<_RemotePdfView> createState() => _RemotePdfViewState();
}
class _RemotePdfViewState extends State<_RemotePdfView> {
late final Future<File> _file = _download();
Future<File> _download() async {
final request = await HttpClient().getUrl(Uri.parse(widget.url));
final response = await request.close();
final bytes = await consolidateHttpClientResponseBytes(response);
final directory = await getApplicationDocumentsDirectory();
final filename = widget.url.substring(widget.url.lastIndexOf("/") + 1);
final file = File("${directory.path}/$filename");
return file.writeAsBytes(bytes, flush: true);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<File>(
future: _file,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text("Error loading PDF"));
}
if (snapshot.data == null) {
return Center(
child: CircularProgressIndicator(color: widget.progressColor));
}
return PDFView(filePath: snapshot.data!.path);
},
);
}
}