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(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( 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 _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 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 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 = _download(); Future _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( 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); }, ); } }