Une reconstitution n'est pas une photographie d'archive, et rien dans l'image ne le dit : la mention est due au visiteur. Elle est posée au-dessus des trois branches de rendu (réseau, cache hors ligne, URL externe) plutôt qu'à chacune, pour qu'aucun point d'affichage ne puisse l'oublier. Traduite dans les onze langues publiées : l'app visiteur n'a pas d'infrastructure de traduction pour ses propres libellés. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
149 lines
5.8 KiB
Dart
149 lines
5.8 KiB
Dart
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:tablet_app/Components/ai_generated_badge.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:manager_api_new/api.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:tablet_app/Components/audio_player.dart';
|
|
import 'package:tablet_app/Components/video_viewer.dart';
|
|
import 'package:tablet_app/Components/video_viewer_youtube.dart';
|
|
import 'package:tablet_app/Models/tabletContext.dart';
|
|
import 'package:tablet_app/app_context.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
|
|
/// L'image porte sa mention quelle que soit la branche de rendu prise en dessous.
|
|
/// Même geste que dans l'app visiteur : la poser ici évite d'y penser à chaque écran.
|
|
Widget build(BuildContext context) {
|
|
final resource = _resource(context);
|
|
if (!AiGeneratedBadge.isGenerated(resourceDTO)) return resource;
|
|
|
|
final tabletAppContext =
|
|
Provider.of<AppContext>(context, listen: false).getContext() as TabletAppContext;
|
|
return Stack(
|
|
children: [
|
|
Positioned.fill(child: resource),
|
|
Positioned(
|
|
left: 6,
|
|
bottom: 6,
|
|
child: AiGeneratedBadge(language: tabletAppContext.language),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _resource(BuildContext context) {
|
|
final appContext = Provider.of<AppContext>(context);
|
|
TabletAppContext tabletAppContext = appContext.getContext();
|
|
Size size = MediaQuery.of(context).size;
|
|
|
|
Color primaryColor = new Color(int.parse(tabletAppContext.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 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(tabletAppContext),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
// Loader ou indicateur de chargement pendant la vérification
|
|
return 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) => CircularProgressIndicator(),
|
|
errorWidget: (context, url, error) => 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);
|
|
default:
|
|
return 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);
|
|
default:
|
|
return Text("Not supported type");
|
|
}
|
|
// Utilisation de l'image locale
|
|
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<File?> _checkIfLocalResourceExists(TabletAppContext tabletAppContext) async {
|
|
try {
|
|
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
|
|
String localPath = appDocumentsDirectory!.path;
|
|
Directory configurationDirectory = Directory('$localPath/${tabletAppContext.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;
|
|
}
|
|
}
|