mymuseum-visitapp/lib/Services/downloadConfiguration.dart
Thomas Fransolet f013d33bba D5 et mention RGPD aux visiteurs
D5 — les échecs de téléchargement ne disparaissent plus dans un print : ils sont
collectés et la visite n'est déclarée téléchargée que si elle l'est vraiment.
Nouvelle clé downloadIncomplete dans les 10 langues, sans quoi getFromLocale
aurait rendu une chaîne vide.

Trouvé en câblant l'écran, et c'est pire que le bug d'origine : sans état
d'échec, une visite incomplète restait sur « téléchargement en cours »
indéfiniment. Le compteur d'avancement n'est incrémenté qu'en cas de succès, donc
il n'atteignait jamais le total et l'écran ne concluait jamais. Relancer ne
re-télécharge que ce qui manque.

J4 — mention d'information accessible depuis l'assistant lui-même, là où la
collecte a lieu (§8.3 des CGU). Texte repris mot pour mot de
DOCS/mention-information-visiteurs.md.
FR/NL/EN seulement, repli sur l'anglais : traduire une mention de protection des
données sans relecture humaine serait pire que la servir en anglais. Les 7 autres
langues attendent une traduction relue, à demander avec la relecture juridique.

flutter analyze lib sans erreur, flutter build apk --debug --flavor dev vert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 16:01:00 +02:00

663 lines
30 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/modelsHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:http/http.dart' as http;
import 'package:fluttertoast/fluttertoast.dart';
class DownloadConfigurationWidget extends StatefulWidget {
DownloadConfigurationWidget({Key? key, required this.configuration}) : super(key: key);
final ConfigurationDTO configuration;
@override
State<DownloadConfigurationWidget> createState() => _DownloadConfigurationWidgetState();
}
class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidget> {
ValueNotifier<int> currentResourceIndex = ValueNotifier<int>(0);
ValueNotifier<int> currentResourceNbr = ValueNotifier<int>(-1);
/// D5 — nombre de ressources dont le téléchargement a échoué. Alimente le message
/// d'échec : « 3 fichiers n'ont pas pu être téléchargés » dit au visiteur ce qui
/// s'est passé, là où l'écran d'erreur générique le laissait supposer une panne.
ValueNotifier<int> downloadFailureCount = ValueNotifier<int>(0);
bool isAlreadyDownloading = false;
//OtaEvent? currentEvent;
Future<bool> download(BuildContext buildContext, VisitAppContext visitAppContext) async {
bool isAllLanguages = true;
if(visitAppContext.isAllLanguages != null) {
isAllLanguages = visitAppContext.isAllLanguages!;
if(isAllLanguages) {
print("Admin here and all download all audios !");
/*ScaffoldMessenger.of(buildContext).showSnackBar(
const SnackBar(
content: Text("Tous les audios vont être téléchargés"),
backgroundColor: kMainColor),
);*/
}
}
if(!isAlreadyDownloading) {
isAlreadyDownloading = true;
// HERE CHECK VERSION APK
if(true) {
Map<Permission, PermissionStatus> statuses = await [
Permission.requestInstallPackages,
].request();
//tryOtaUpdate();
}
ExportConfigurationDTO? exportConfigurationDTO;
try{
// Retrieve all url from resource to download (get all resource from configuration en somme)
exportConfigurationDTO = await visitAppContext.clientAPI.configurationApi!.configurationExport(widget.configuration.id!, language: isAllLanguages ? null : visitAppContext.language); // tabletAppContext.configuration!.id! // 65c5f0ee4c030e63ce16bff5 TODO Remove
} catch(e) {
print("Erreur lors du téléchargement de la configuration et de ses ressources !");
print(e);
return false;
}
exportConfigurationDTO.resources!.forEach((element) {
print(element.id);
print(element.label);
});
if(exportConfigurationDTO.resources != null && exportConfigurationDTO.resources!.isNotEmpty) {
Map<Permission, PermissionStatus> statuses = await [
Permission.storage,
].request();
//if(statuses[Permission.storage] == PermissionStatus.granted) {
try{
try {
Directory directory = Directory('${visitAppContext.localPath}');
List<FileSystemEntity> allConfigurations = directory.listSync();
Directory configurationDirectory = Directory('${visitAppContext.localPath}/${widget.configuration.id}');
if(!allConfigurations.any((configurationDirectory) => configurationDirectory.uri.pathSegments.any((element) => element == widget.configuration.id))) {
// create directory
print("Trying to create directory");
configurationDirectory.createSync(recursive: true);
print('Répertoire créé avec succès.');
}
} catch(e) {
print("Listing failed, so try to create directory");
Directory configurationDirectory = Directory('${visitAppContext.localPath}/${widget.configuration.id}');
configurationDirectory.createSync(recursive: true);
print('Répertoire créé avec succès.');
}
Directory configurationDirectory = Directory('${visitAppContext.localPath}/${widget.configuration.id}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
for (var file in fileList) {
print(file.uri.pathSegments.last);
}
Map<String, DateTime?> localResourceDates = await readLocalResourceDates();
// Ressources dont le téléchargement a échoué. Vide = visite complète.
final List<String> failedResourceIds = [];
var resourcesToDownload = exportConfigurationDTO.resources!.where((resource) => resource.type != ResourceType.ImageUrl && resource.type != ResourceType.VideoUrl && resource.type != ResourceType.JsonUrl && resource.url != null && isResourceOutdated(resource, fileList, localResourceDates[resource.id]));
currentResourceNbr.value = resourcesToDownload.length;
// foreach ou on va tout télécharger - avec un joli etape 0 / length - on peut rendre tout lent on s'en fou ça ne ce fait qu'une fois
for (var resource in resourcesToDownload) {
String? filePath = await downloadResource(visitAppContext, widget.configuration, resource, visitAppContext.localPath!);
if (filePath != null)
{
// Insert in database
ResourceModel resourceModel = ResourceModel(id: resource.id, source: resource.url, path: filePath, type: resource.type, dateUpdate: resource.dateUpdate);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
localResourceDates[resource.id!] = resource.dateUpdate;
} catch (e) {
print("We got an issue inserting image metadata ${resource.id}");
}
currentResourceIndex.value++;
} else {
// D5 — un fichier raté ne disparaît plus dans un `print`. Une visite dont
// des ressources manquent ne doit pas s'annoncer téléchargée : hors ligne,
// le visiteur trouverait des images vides ou un audio muet sans comprendre
// pourquoi, et sans rien pouvoir y faire.
failedResourceIds.add(resource.id ?? '?');
debugPrint("[download] échec de la ressource ${resource.id} (${resource.url})");
}
}
// Delete others that are no more used
var resourceToDelete = fileList.where((fileL) =>
!exportConfigurationDTO!.resources!.any((resource) =>
resource.id != null && fileL.uri.pathSegments.last.contains(resource.id!)));
for (var resource in resourceToDelete) {
print("resource to DELETE");
print(resource.path);
try {
resource.deleteSync();
} catch (e) {
print("We got an issue deleting local file ${resource.path}");
}
}
await DatabaseHelper.instance.insert(DatabaseTableType.configurations, ModelsHelper.configurationToMap(widget.configuration));
List<SectionDTO>? sections = exportConfigurationDTO.sections;
List<String> usedImageOrAudioIds = [];
if(sections!.isNotEmpty) {
List<SectionDTO> sectionsInDB = await DatabaseHelper.instance.queryWithConfigurationId(DatabaseTableType.sections, widget.configuration.id!);
List<SectionDTO> sectionsToKeep = sections.where((s) => s.type == SectionType.Article || s.type == SectionType.Quiz).toList(); // TODO: supporter tous les types de sections (Game, Menu, Map, PDF, Video, Slider, Web, Weather, Agenda) — actuellement limité à Article et Quiz
sectionsToKeep.sort((a,b) => a.order!.compareTo(b.order!));
int newOrder = 0;
// Update local DB - Sections
for(var section in sectionsToKeep) {
section.order = newOrder;
try {
await DatabaseHelper.instance.insert(DatabaseTableType.sections, ModelsHelper.sectionToMap(section));
} catch (e) {
print("We got an issue inserting section data ${section.id}");
}
// Download section image
if(section.imageId != null) {
usedImageOrAudioIds.add(section.imageId!);
var imageData = exportConfigurationDTO.resources!.where((element) => element.id == section.imageId);
if(imageData.isNotEmpty) {
// TODO get all resources from API + store it in download directory of the app
ResourceModel resourceModel = ResourceModel(id: imageData.first.id, source: section.imageSource, /*data: imageData.first.data,*/ type: imageData.first.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} catch (e) {
print("We got an issue inserting image data ${imageData.first.id}");
}
}
}
newOrder = newOrder + 1;
}
// Le bloc qui vivait ici parsait `section.data` type par type — et il était
// commenté, donc une visite téléchargée n'embarquait que l'image de chaque
// section : ni contenus d'articles, ni audios, ni icônes de carte.
//
// Il n'y a plus rien à redécouvrir : depuis le 2026-08-11 le serveur collecte
// les ressources via `GetReferencedResourceIds()`, implémentée sur les 13
// sous-types, et les met toutes dans `resources`. On enregistre la charge.
// Un nouveau type de section est couvert sans toucher à ce fichier.
//
// ⚠️ `insert` fait un UPDATE de toute la ligne quand l'id existe déjà : cette
// boucle repasse sur les ressources que la boucle de téléchargement vient
// d'enregistrer. Elle doit donc réécrire la date que celle-ci a posée, sinon
// elle l'efface et le fichier redevient « sans date connue » à chaque visite.
// Et pour une ressource dont le téléchargement a échoué, c'est l'ancienne date
// locale qui est conservée — surtout pas celle du serveur, qui la déclarerait
// à jour alors que le fichier est absent.
for (var resource in exportConfigurationDTO.resources ?? []) {
if (resource.id == null) continue;
usedImageOrAudioIds.add(resource.id!);
try {
await DatabaseHelper.instance.insert(
DatabaseTableType.resources,
ResourceModel(id: resource.id, source: resource.url, type: resource.type, dateUpdate: localResourceDates[resource.id]).toMap(),
);
} catch (e) {
print("We got an issue inserting resource ${resource.id}");
}
}
List<String?> sectionIdsToRemove = sectionsInDB.map((s) => s.id).where((sectionId) => !sectionsToKeep.map((sk) => sk.id).contains(sectionId)).toList();
for(var sectionIdToRemove in sectionIdsToRemove) {
print("section with id removed");
print(sectionIdToRemove);
try {
await DatabaseHelper.instance.delete(sectionIdToRemove!, DatabaseTableType.sections);
} catch (e) {
print("We got an issue deleting section id: ${sectionIdToRemove}");
}
}
// `cleanLocalResources` reste désactivée : la table `resources` n'a pas de
// colonne `configurationId`, elle est globale à toutes les visites. La purger
// avec les ids d'une seule configuration effacerait les lignes des autres.
// Les fichiers obsolètes, eux, sont bien supprimés plus haut — leur répertoire
// est propre à la configuration. Et rien ne lit la colonne `path` pour le rendu :
// `CachedCustomResource` retrouve le fichier en listant le répertoire.
}
// D5 — la visite n'est déclarée téléchargée que si elle l'est vraiment. Le
// contenu déjà récupéré reste sur le device : relancer ne re-télécharge que
// ce qui manque, `isResourceOutdated` voyant les fichiers présents.
if (failedResourceIds.isNotEmpty) {
debugPrint("[download] ${failedResourceIds.length} ressource(s) en échec : "
"${failedResourceIds.join(', ')}");
downloadFailureCount.value = failedResourceIds.length;
return false;
}
} catch(e) {
print("ERRORRRR");
print(e);
if(statuses[Permission.storage] != PermissionStatus.granted) {
Fluttertoast.showToast(
msg: "PermissionStatus not granted, issue may be linked to that. Please check os version (if less than 13, real issue).",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.redAccent,
textColor: Colors.white,
fontSize: 16.0
);
}
return false;
}
/*} else {
print("PermissionStatus.granted NOT GRANTED");
Fluttertoast.showToast(
msg: "PermissionStatus not granted",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.redAccent,
textColor: Colors.white,
fontSize: 16.0
);
return false;
}*/
}
}
return true;
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
return Center(
child: FutureBuilder(future: download(context, visitAppContext), builder: (context, snapshot) {
return ValueListenableBuilder<int>(
valueListenable: currentResourceNbr,
builder: (context, valueNbr, _) {
return ValueListenableBuilder<int>(
valueListenable: currentResourceIndex,
builder: (context, valueIndex, _) {
var valueInPercentage = valueNbr > 0 ? (valueIndex / valueNbr) * 100 : 100;
String formattedPercentage = valueInPercentage.toStringAsFixed(0);
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// D5 — l'état d'échec passe avant les autres. Sans lui, une visite
// incomplète restait sur « téléchargement en cours » indéfiniment :
// le compteur d'avancement n'est incrémenté qu'en cas de succès, donc
// il n'atteignait jamais le total et l'écran ne concluait jamais.
ValueListenableBuilder<int>(
valueListenable: downloadFailureCount,
builder: (context, failures, _) {
if (failures == 0) {
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
textAlign: TextAlign.center,
valueIndex == valueNbr && valueNbr != -1 ? valueNbr == 0 ?
TranslationHelper.getFromLocale(
"upToDate",
appContext.getContext()) : TranslationHelper.getFromLocale(
"downloadFinish",
appContext.getContext()) : TranslationHelper.getFromLocale(
"downloadInProgress",
appContext.getContext()),
style: const TextStyle(fontSize: 20),
),
),
);
}
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.cloud_off, size: 34, color: Colors.orangeAccent),
const SizedBox(height: 10),
Text(
TranslationHelper.getFromLocale(
"downloadIncomplete", appContext.getContext()),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16),
),
],
),
),
);
},
),
if(valueNbr != -1 && valueNbr != 0)
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: LinearProgressIndicator(
value: valueInPercentage / 100,
semanticsLabel: 'Linear progress indicator',
color: kMainColor0,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'$formattedPercentage%',
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w500),
),
),
],
),
)
],
);
}
);
}
);
}),
);
}
}
/*class DownloadConfiguration {
static Future<List<ResourceModel>> download(BuildContext buildContext, AppContext appContext, ConfigurationDTO configuration) async {
VisitAppContext visitAppContext = (appContext.getContext() as VisitAppContext);
bool isAllLanguages = false;
List<ResourceModel> audiosNotWorking = [];
if(visitAppContext.isAllLanguages != null) {
isAllLanguages = visitAppContext.isAllLanguages!;
if(isAllLanguages) {
print("Admin here and all download all audios !");
ScaffoldMessenger.of(buildContext).showSnackBar(
const SnackBar(
content: Text("Tous les audios vont être téléchargés"),
backgroundColor: kMainColor),
);
}
}
print("Start export configuration to store it locally");
ExportConfigurationDTO? exportConfigurationDTO;
try{
exportConfigurationDTO = await ApiService.exportConfiguration(visitAppContext.clientAPI, configuration.id!, isAllLanguages ? null : visitAppContext.language!);
} catch(e) {
print("Erreur lors du téléchargement de la visite");
print(e);
}
if(exportConfigurationDTO != null) {
// Delete all from local DB ref to this configurationId then add it all
await DatabaseHelper.instance.insert(DatabaseTableType.configurations, ModelsHelper.configurationToMap(configuration));
List<String> usedImageOrAudioIds = [];
var currentLanguage = (appContext.getContext() as VisitAppContext).language;
if(configuration.imageId != null) {
usedImageOrAudioIds.add(configuration.imageId!);
var imageData = exportConfigurationDTO.resources!.where((element) => element.id == configuration.imageId);
if(imageData.isNotEmpty) {
ResourceModel resourceModel = ResourceModel(id: imageData.first.id, source: configuration.imageSource, /*data: imageData.first.data,*/ type: imageData.first.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} catch (e) {
print("We got an issue inserting image data ${imageData.first.id}");
}
}
}
List<SectionDTO>? sections = exportConfigurationDTO.sections;
if(sections!.isNotEmpty) {
List<SectionDTO> sectionsInDB = await DatabaseHelper.instance.queryWithConfigurationId(DatabaseTableType.sections, configuration.id!);
List<SectionDTO> sectionsToKeep = sections.where((s) => s.type == SectionType.Article || s.type == SectionType.Quizz).toList(); // TODO: supporter tous les types de sections (Game, Menu, Map, PDF, Video, Slider, Web, Weather, Agenda) — actuellement limité à Article et Quiz
sectionsToKeep.sort((a,b) => a.order!.compareTo(b.order!));
int newOrder = 0;
// Update local DB - Sections
for(var section in sectionsToKeep) {
section.order = newOrder;
try {
await DatabaseHelper.instance.insert(DatabaseTableType.sections, ModelsHelper.sectionToMap(section));
} catch (e) {
print("We got an issue inserting section data ${section.id}");
}
// Download section image
if(section.imageId != null) {
usedImageOrAudioIds.add(section.imageId!);
var imageData = exportConfigurationDTO.resources!.where((element) => element.id == section.imageId);
if(imageData.isNotEmpty) {
// TODO get all resources from API + store it in download directory of the app
ResourceModel resourceModel = ResourceModel(id: imageData.first.id, source: section.imageSource, /*data: imageData.first.data,*/ type: imageData.first.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} catch (e) {
print("We got an issue inserting image data ${imageData.first.id}");
}
}
}
// Download all images..
ArticleDTO? articleDTO = ArticleDTO.fromJson(jsonDecode(section.data!));
if(articleDTO != null) {
for(var image in articleDTO.contents!) {
usedImageOrAudioIds.add(image.resourceId!);
var imageData = exportConfigurationDTO.resources!.where((element) => element.id == image.resourceId);
if(imageData.isNotEmpty) {
// TODO get all resources from API + store it in download directory of the app
ResourceModel resourceModel = ResourceModel(id: imageData.first.id, source: image.resourceUrl, /*data: imageData.first.data,*/ type: imageData.first.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} catch (e) {
print("We got an issue inserting image data ${imageData.first.id}");
}
}
}
var audioIdsArticle = isAllLanguages ? articleDTO.audioIds! : articleDTO.audioIds!.where((audioId) => audioId.language == currentLanguage);
for(var audioId in audioIdsArticle) {
if(audioId.value != null) {
usedImageOrAudioIds.add(audioId.value!);
audiosNotWorking = await importAudio(visitAppContext, exportConfigurationDTO, audioId.value!, audiosNotWorking);
}
}
}
newOrder = newOrder + 1;
}
List<String?> sectionIdsToRemove = sectionsInDB.map((s) => s.id).where((sectionId) => !sectionsToKeep.map((sk) => sk.id).contains(sectionId)).toList();
for(var sectionIdToRemove in sectionIdsToRemove) {
print("section with id removed");
print(sectionIdToRemove);
try {
await DatabaseHelper.instance.delete(sectionIdToRemove!, DatabaseTableType.sections);
} catch (e) {
print("We got an issue deleting section id: ${sectionIdToRemove}");
}
}
cleanLocalResources(usedImageOrAudioIds, configuration);
}
}
return audiosNotWorking;
}
}
*/
Future<String?> downloadResource(VisitAppContext visitAppContext, ConfigurationDTO configurationDTO, ResourceDTO resourceDTO, String localPath) async {
try {
// Téléchargement de la ressource depuis l'URL
http.Response response = await http.get(Uri.parse(resourceDTO.url!));
if (response.statusCode == 200) {
// Vérification de l'en-tête Content-Type
String contentType = response.headers["content-type"] ?? "";
// Déduction de l'extension en fonction du Content-Type
String extension = _getExtensionFromContentType(contentType);
print("LOCAL PATTH");
print(localPath);
File file = File('$localPath/${configurationDTO.id}/${resourceDTO.id}.$extension');
// Écriture du contenu téléchargé dans le fichier local
await file.writeAsBytes(response.bodyBytes);
return file.path;
} else {
print("Échec du téléchargement de la ressource - ${response.statusCode}");
return null;
}
} catch (e) {
print("Erreur lors du téléchargement de la ressource !");
print(e);
return null;
}
}
/// Date de dernière mise à jour connue pour chaque ressource déjà sur le device.
/// Une valeur nulle signifie « fichier téléchargé avant la v4 de la base locale,
/// date inconnue » — pas « périmé ».
Future<Map<String, DateTime?>> readLocalResourceDates() async {
List<Map<String, dynamic>> rows = await DatabaseHelper.instance.queryAllRows(DatabaseTableType.resources);
return {
for (var row in rows) row["id"] as String: DatabaseHelper.instance.getResourceFromDB(row).dateUpdate
};
}
bool isResourceOutdated(ResourceDTO resource, List<FileSystemEntity> fileList, DateTime? localDateUpdate) {
var localFile = fileList.where((fileL) => fileL.uri.pathSegments.last.contains(resource.id!));
if (localFile.isEmpty) return true;
// Fichier écrit avec une extension que la table ne savait pas déduire — c'était le
// cas de tous les MP3 avant D3. Il est illisible, et sa date ne le dira jamais :
// le serveur n'a pas changé, c'est le client qui l'avait mal enregistré.
//
// La date nulle borne la reprise à UN essai : sans elle, un type MIME réellement
// absent de la table se re-téléchargerait à chaque visite pour se réécrire en
// `.unknown`. On ne peut pas non plus déduire l'extension de l'URL en repli — une
// URL Firebase n'en porte pas.
if (localFile.first.uri.pathSegments.last.endsWith(".unknown") && localDateUpdate == null) return true;
if (localDateUpdate == null || resource.dateUpdate == null) return false;
return resource.dateUpdate!.isAfter(localDateUpdate);
}
String _getExtensionFromContentType(String contentType) {
Map<String, String> contentTypeToExtension = {
"image/jpeg": "jpg",
"image/jpg": "jpg",
"image/png": "png",
"image/gif": "gif",
"audio/mpeg": "mp3",
"audio/mp3": "mp3",
"video/mp4": "mp4",
"video/webm": "webm",
"video/avi": "avi",
"video/quicktime": "mov",
"application/pdf": "pdf",
"application/json": "json"
};
return contentTypeToExtension[contentType] ?? "unknown";
}
Future<List<ResourceModel>> importAudio(VisitAppContext visitAppContext, ExportConfigurationDTO exportConfigurationDTO, String audioId, List<ResourceModel> audiosNotWorking) async {
var audioData = exportConfigurationDTO.resources!.where((element) => element.id == audioId);
if(audioData.isNotEmpty) {
// TODO get all resources from API + store it in download directory of the app
ResourceModel resourceModel = ResourceModel(id: audioData.first.id, source: "", /*data: audioData.first.data,*/ type: audioData.first.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
return audiosNotWorking;
} catch (e) {
print("We got an issue inserting audio data ${audioData.first.id}");
resourceModel.label = audioData.first.label;
print(resourceModel.label);
// Update context with not working audios
//VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext;
audiosNotWorking.add(resourceModel);
audiosNotWorking = audiosNotWorking.toSet().toList(); // Remove duplicates
// appContext.setContext(visitAppContext);
return audiosNotWorking;
}
}
return audiosNotWorking;
}
void cleanLocalResources(List<String> usedImageIds, ConfigurationDTO configuration) async {
List<Map<String, dynamic>> resourcesInDB = await DatabaseHelper.instance.queryAllRows(DatabaseTableType.resources);
List<ResourceModel> resourcesModel = [];
for(var resource in resourcesInDB) {
resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(resource));
}
List<String?> resourceIdsToRemove = resourcesModel.map((r) => r.id).where((resourceId) => !usedImageIds.contains(resourceId)).toList();
for(var resourceIdToRemove in resourceIdsToRemove) {
print("resource with id removed ________________ !!!!!");
print(resourceIdToRemove);
try {
await DatabaseHelper.instance.delete(resourceIdToRemove!, DatabaseTableType.resources);
} catch (e) {
print("We got an issue deleting resource id: ${resourceIdToRemove}");
}
}
}