Thomas Fransolet 081d9a00d9 L'accueil de la borne passe en bento, réglable depuis le manager
Même grille à six colonnes que le menu du casque, même algorithme de
placement que le mobile et l'aperçu du manager. La borne envoie son
deviceId : elle reçoit son propre agencement, pas la configuration nue.

La hauteur de cellule se déduit de la hauteur disponible et non de la
largeur — une borne ne défile pas. L'orientation est concentrée en un point,
pour que le portrait de la V2 soit un branchement et pas une réécriture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:36:19 +02:00

731 lines
33 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:tablet_app/Components/casting_intro_dialog.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:myinfomate_layout/myinfomate_layout.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:tablet_app/Services/statisticsService.dart';
import 'package:tablet_app/Components/loading_common.dart';
import 'package:tablet_app/Helpers/DatabaseHelper.dart';
import 'package:tablet_app/Helpers/ImageCustomProvider.dart';
import 'package:tablet_app/Helpers/MQTTHelper.dart';
import 'package:tablet_app/Helpers/translationHelper.dart';
import 'package:tablet_app/Screens/Agenda/agenda_view.dart';
import 'package:tablet_app/Screens/Article/article_view.dart';
import 'package:tablet_app/Screens/Event/event_view.dart';
import 'package:tablet_app/Screens/Configuration/config_view.dart';
import 'package:tablet_app/Screens/MainView/section_page_detail.dart';
import 'package:tablet_app/Screens/Weather/weather_view.dart';
import 'package:tablet_app/Screens/Map/map_context.dart';
import 'package:tablet_app/Screens/Map/map_view.dart';
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/Screens/Menu/menu_view.dart';
import 'package:tablet_app/Screens/PDF/pdf_view.dart';
import 'package:tablet_app/Screens/Game/game_page.dart';
import 'package:tablet_app/Screens/Slider/slider_view.dart';
import 'package:tablet_app/Screens/Video/video_view.dart';
import 'package:tablet_app/Screens/Web/web_view.dart';
import 'package:tablet_app/Services/downloadService.dart';
import 'package:tablet_app/app_context.dart';
import 'package:tablet_app/constants.dart';
import 'package:intl/intl.dart';
import 'package:http/http.dart' as http;
import 'package:image/image.dart' as IMG;
import 'dart:ui' as ui;
import 'package:tablet_app/Components/assistant_chat_view.dart';
import '../Quizz/quizz_view.dart';
import 'language_selection.dart';
class MainViewWidget extends StatefulWidget {
MainViewWidget();
@override
_MainViewWidget createState() => _MainViewWidget();
}
class _MainViewWidget extends State<MainViewWidget> {
Size sizeScreen = new Size(1080.0, 1920.0); // Tablet resolution
SectionDTO? sectionSelected;
late ConfigurationDTO configurationDTO;
/// isDate, isHour, isSectionImageBackground et screenPercentageSectionsMainPage
/// ont quitté ConfigurationDTO avec Postgres v3 : ils vivent sur le lien
/// instance ↔ configuration.
AppConfigurationLink? appConfigurationLink;
ValueNotifier<DateTime?> currentHourDate = ValueNotifier<DateTime?>((DateTime.now().toUtc()).add(Duration(hours: 1)));
late Color backgroundColor;
bool isImageBackground = false;
List<SectionDTO>? sectionsLocal;
late List<dynamic> rawSectionsData;
bool isInit = true; // Use to make it faster and to load resource at init
bool isDialogOpen = false;
@override
void initState() {
final appContext = Provider.of<AppContext>(context, listen: false);
configurationDTO = appContext.getContext().configuration;
appConfigurationLink = appContext.getContext().currentAppConfigurationLink;
if(appConfigurationLink?.isHour == true) {
Timer.periodic(Duration(seconds: 1), (Timer t) => _getTime(currentHourDate));
}
isImageBackground = appConfigurationLink?.isSectionImageBackground ?? true;
super.initState();
}
void _getTime(ValueNotifier<DateTime?> valueNotifier) {
//final DateTime now = DateTime.now();
//final String formattedDateTime = _formatDateTime(now);
//setState(() {
//_timeString = formattedDateTime;
valueNotifier.value = (DateTime.now().toUtc()).add(Duration(hours: 2));
//});
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
Size size = MediaQuery.of(context).size;
backgroundColor = appContext.getContext().configuration != null ? new Color(int.parse(appContext.getContext().configuration.secondaryColor.split('(0x')[1].split(')')[0], radix: 16)) : Colors.white;
Color textColor = backgroundColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
TabletAppContext tabletAppContext = appContext.getContext() as TabletAppContext;
// TODO REMOVE
/*if (!MQTTHelper.instance.isInstantiated)
MQTTHelper.instance.connect(appContext);*/
return Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
height: size.height,
width: size.width,
color: configurationDTO.imageId == null ? configurationDTO.secondaryColor != null ? backgroundColor : kBackgroundGrey : null,
decoration: configurationDTO.imageId != null ? BoxDecoration(
image: new DecorationImage(
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(Colors.grey.withValues(alpha: 0.1), BlendMode.color),
image: ImageCustomProvider.getImageProvider(appContext, configurationDTO.imageId!, configurationDTO.imageSource!)
),
) : null,
child: Stack(
children: [
if(currentHourDate.value != null)
ValueListenableBuilder<DateTime?>(
valueListenable: currentHourDate,
builder: (context, value, _) {
return Positioned(
top: 0,
left: 0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if(value != null && appConfigurationLink?.isHour == true)
Text(DateFormat('HH:mm').format(value), style: TextStyle(fontSize: 20, color: textColor)),
if(value != null && appConfigurationLink?.isDate == true)
if(appContext.getContext().language.toString().toUpperCase() == "EN")
Text(DateFormat('MM/dd').format(value), style: TextStyle(fontSize: 15, color: textColor)),
if(value != null && appConfigurationLink?.isDate == true)
if(appContext.getContext().language.toString().toUpperCase() != "EN")
Text(DateFormat('dd/MM').format(value), style: TextStyle(fontSize: 15, color: textColor)),
],
),
)
);
},
),
LanguageSelection(size: size),
Center(
child: Container(
height: appConfigurationLink?.screenPercentageSectionsMainPage != null ? size.height * (appConfigurationLink!.screenPercentageSectionsMainPage! / 100) : size.height * 0.85,
width: size.width * 0.9,
child: isInit ? FutureBuilder(
future: getSections(size, appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
// Show select config
return Text("");
}
else {
return getGridSections(appContext, textColor);
}
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
} else {
return Center(
child: Container(
child: LoadingCommon()
)
);
}
}
) : getGridSections(appContext, textColor),
),
),
Positioned(
bottom: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: FutureBuilder(
future: getPlatformInfo(),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text(snapshot.data.version, style: TextStyle(fontSize: 8));
}
return SizedBox();
}
),
)
),
if (tabletAppContext.isAssistantEnabled)
Positioned(
bottom: 24,
right: 24,
child: FloatingActionButton.extended(
backgroundColor: kMainGrey,
icon: const Icon(Icons.chat_bubble_outline, color: Colors.white),
label: const Text("Assistant", style: TextStyle(color: Colors.white, fontSize: 16)),
onPressed: () {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'Assistant',
barrierColor: Colors.black45,
transitionDuration: const Duration(milliseconds: 250),
pageBuilder: (dialogContext, __, ___) => Align(
alignment: Alignment.centerRight,
child: AssistantChatView(
tabletAppContext: tabletAppContext,
configurationId: tabletAppContext.configuration?.id,
onNavigateToSection: (sectionId, sectionTitle) {
Navigator.of(dialogContext).pop();
final index = sectionsLocal?.indexWhere((s) => s.id == sectionId) ?? -1;
if (index >= 0) {
Navigator.push(context, MaterialPageRoute(
builder: (_) => SectionPageDetail(
configurationDTO: configurationDTO,
sectionDTO: sectionsLocal![index],
textColor: textColor,
isImageBackground: isImageBackground,
elementToShow: getContent(tabletAppContext, sectionsLocal![index], isImageBackground, rawSectionsData[index]),
isFromMenu: false,
),
));
}
},
),
),
);
},
),
)
/*if(configurationDTO.weatherCity != null && configurationDTO.weatherCity!.length > 2 && configurationDTO.weatherResult != null)
Positioned(
bottom: 0,
left: 0,
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Builder(
builder: (context) {
Map<String, dynamic> weatherResultInJson = jsonDecode(configurationDTO.weatherResult!);
WeatherData weatherDataResult = WeatherData.fromJson(weatherResultInJson);
return WeatherView(weatherData: weatherDataResult);
}
),
))*/
]),
),
);
}
Future<ConfigurationDTO?> getCurrentConfiguration(dynamic appContext) async {
TabletAppContext tabletAppContext = await appContext.getContext();
try {
ConfigurationDTO? configurationDTO = await tabletAppContext.clientAPI!.configurationApi!.configurationGetDetail(tabletAppContext.configuration!.id!); // tabletAppContext.configuration!.id! // 65c5f0ee4c030e63ce16bff5 TODO Remove
tabletAppContext.configuration = configurationDTO;
backgroundColor = tabletAppContext.configuration != null ? new Color(int.parse(appContext.getContext().configuration.secondaryColor.split('(0x')[1].split(')')[0], radix: 16)) : Colors.white;
TabletAppContext? localContext = await DatabaseHelper.instance.getData();
if (localContext != null) { // Check if sql DB exist
await DatabaseHelper.instance.update(tabletAppContext);
} else {
await DatabaseHelper.instance.insert(tabletAppContext);
}
/*
appContext.setContext(tabletAppContext);
// STORE IT LOCALLY (SQLite)
TabletAppContext? localContext = await DatabaseHelper.instance.getData();
if (localContext != null) { // Check if sql DB exist
await DatabaseHelper.instance.update(tabletAppContext);
} else {
await DatabaseHelper.instance.insert(tabletAppContext);
}*/
return configurationDTO;
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Une erreur est survenue lors de la récupération de la configuration'),
),
);
}
}
Future<List<SectionDTO>?> getSections(Size size, dynamic appContext) async {
TabletAppContext tabletAppContext = await appContext.getContext();
// DIALOG HERE
if(!isDialogOpen) {
isDialogOpen = true;
var result = await showDialog(
builder: (BuildContext dialogContext) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(tabletAppContext.currentAppConfigurationLink?.roundedValue?.toDouble() ?? 20.0))
),
content: Container(
width: 400,
height: 200,
child: DownloadConfigurationWidget(),
),
actions: <Widget>[],
), context: context
);
isDialogOpen = false;
} else {
print("ALREADY OPEN LAAAA");
}
await getCurrentConfiguration(appContext);
final loadedConfiguration = tabletAppContext.configuration;
if (mounted && loadedConfiguration != null) {
CastingIntroDialog.showIfNeeded(context, loadedConfiguration, tabletAppContext.language);
}
// Load applicationInstanceDTO to check if assistant/statistics are enabled
try {
if (tabletAppContext.instanceId != null) {
final instanceDTO = await tabletAppContext.clientAPI!.instanceApi!.instanceGetDetail(tabletAppContext.instanceId!);
if (instanceDTO != null && instanceDTO.applicationInstanceDTOs != null) {
final tabletInstance = instanceDTO.applicationInstanceDTOs!
.where((a) => a.appType == AppType.Tablet)
.firstOrNull;
if (tabletInstance != null) {
// Assigné inconditionnellement : ce DTO ne sert plus seulement de drapeau
// d'assistant, il porte aussi les AppConfigurationLink d'où viennent
// roundedValue, isDate, isHour, isSectionImageBackground et
// screenPercentageSectionsMainPage depuis Postgres v3. Le laisser null
// sur une instance sans IA renverrait tous ces réglages à leurs valeurs
// par défaut, sans erreur visible.
tabletAppContext.applicationInstanceDTO = tabletInstance;
tabletAppContext.isAssistantEnabled =
instanceDTO.isAssistant == true && tabletInstance.isAssistant == true;
if (tabletInstance.hasStats == true) {
tabletAppContext.statisticsService = StatisticsService(
clientAPI: tabletAppContext.clientAPI!,
instanceId: tabletAppContext.instanceId,
configurationId: tabletAppContext.configuration?.id,
appType: 'Tablet',
language: tabletAppContext.language,
);
}
}
}
appContext.setContext(tabletAppContext);
}
} catch (e) {
print('Failed to load applicationInstanceDTO: $e');
}
print(sectionsLocal);
if(isInit) {
try {
// Le deviceId désigne le lien de cette borne, donc son agencement : ordre,
// tailles bento et sections masquées pour le seul canal kiosk. Sans lui, le
// serveur rend la configuration nue et la borne perd sa mise en page.
final rawList = await tabletAppContext.clientAPI!.sectionApi!.sectionGetFromConfigurationDetail(
tabletAppContext.configuration!.id!,
deviceId: tabletAppContext.deviceId,
);
rawSectionsData = jsonDecode(jsonEncode(rawList));
List<SectionDTO> sectionList = jsonDecode(jsonEncode(rawSectionsData)).map((json) => SectionDTO.fromJson(json)).whereType<SectionDTO>().toList();
sectionsLocal = sectionList;
isInit = false;
return sectionList;
} catch (e) {
print(e);
print("IN CATCH");
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) {
return ConfigViewWidget();
},
),
(Route<dynamic> route) => false // For pushAndRemoveUntil
);
}
}
return sectionsLocal;
}
/// Le nombre de colonnes de la grille bento.
///
/// **6**, comme le menu du casque : c'est la même vue, sur un écran qui a la même
/// forme. Doit rester aligné sur `kKioskColumns` de `kiosk_screen.dart` côté
/// manager-app, sinon l'aperçu montre au client une grille que sa borne ne rend pas.
///
/// **Point de décision d'orientation** (lot K9) : la borne est en paysage en V1, le
/// portrait est un objectif V2. Tout ce qui dépend de l'orientation passe par ici
/// plutôt que par un `if portrait` semé dans l'écran — la V2 devient un branchement,
/// pas une réécriture.
int get bentoColumns => 6;
/// L'écran principal en bento : chaque section occupe colSpan × rowSpan cellules,
/// réglées par canal dans manager-app. Même placement dense que le mobile et l'aperçu
/// du manager (`myinfomate_layout`), que le web refait en CSS Grid et le casque en C#.
///
/// ⚠️ La hauteur de cellule se déduit de la **hauteur disponible**, pas de la largeur :
/// une borne ne défile pas, la grille doit tenir dans l'écran.
getGridSections(AppContext appContext, Color textColor) {
TabletAppContext tabletAppContext = appContext.getContext() as TabletAppContext;
if (sectionsLocal == null) {
return Center(
child: Text("Aucune section à afficher", style: TextStyle(fontSize: kNoneInfoOrIncorrect)),
);
}
var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
// Les index des sections affichées. On garde l'index d'origine : `rawSectionsData`
// est apparié à `sectionsLocal` position par position, et une section masquée
// décalerait les deux listes l'une par rapport à l'autre.
final visibleIndexes = <int>[];
for (var i = 0; i < sectionsLocal!.length; i++) {
if (sectionsLocal![i].isActive != false) visibleIndexes.add(i);
}
final layout = bentoLayout(
visibleIndexes
.map((i) => BentoItem(
id: sectionsLocal![i].id ?? '$i',
colSpan: (sectionsLocal![i].gridColSpan ?? 1).clamp(1, bentoColumns),
rowSpan: (sectionsLocal![i].gridRowSpan ?? 1).clamp(1, 2),
))
.toList(),
bentoColumns,
);
return LayoutBuilder(
builder: (context, constraints) {
const gap = 16.0;
final rows = layout.rowCount < 1 ? 1 : layout.rowCount;
final cellWidth = (constraints.maxWidth - (bentoColumns - 1) * gap) / bentoColumns;
final cellHeight = (constraints.maxHeight - (rows - 1) * gap) / rows;
// Un placement par item, dans l'ordre des items : `placements[i]` désigne la
// section `visibleIndexes[i]`.
return Stack(
children: [
for (var i = 0; i < layout.placements.length; i++)
Positioned(
left: layout.placements[i].col * (cellWidth + gap),
top: layout.placements[i].row * (cellHeight + gap),
width: layout.placements[i].colSpan * cellWidth +
(layout.placements[i].colSpan - 1) * gap,
height: layout.placements[i].rowSpan * cellHeight +
(layout.placements[i].rowSpan - 1) * gap,
child: sectionTile(
appContext, tabletAppContext, textColor, visibleIndexes[i], isPortrait),
),
],
);
},
);
}
Widget sectionTile(AppContext appContext, TabletAppContext tabletAppContext,
Color textColor, int index, bool isPortrait) {
return InkWell(
onTap: () {
setState(() {
//sectionSelected = sectionsLocal![index];
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return SectionPageDetail(configurationDTO: configurationDTO, sectionDTO: sectionsLocal![index], textColor: textColor, isImageBackground: isImageBackground, elementToShow: getContent(tabletAppContext, sectionsLocal![index], isImageBackground, rawSectionsData[index]), isFromMenu: false);
},
),// For pushAndRemoveUntil
);
});
},
child: Container(
decoration: isImageBackground ? boxDecoration(appContext, sectionsLocal![index], false, rawSectionsData[index]) : null,
//color: Colors.yellow,
padding: EdgeInsets.all(isImageBackground ? 18 : 5),
child: isImageBackground ? Align(
alignment: Alignment.bottomRight,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
//color: Colors.green,
child: SizedBox(
width: double.infinity,
child: Align(
alignment: Alignment.centerRight,
child: HtmlWidget(
sectionsLocal![index].title!.firstWhere((translation) => translation.language == appContext.getContext().language).value ?? "",
customStylesBuilder: (element) {
return {'text-align': 'right', 'font-family': "Roboto"};
},
textStyle: TextStyle(fontSize: isPortrait ? 10 : 25),//calculateFontSize(constraints.maxWidth, constraints.maxHeight, kIsWeb ? kWebMenuTitleDetailSize : kMenuTitleDetailSize)),
),
),
),
)
),
) : Column(
children: [
Expanded(
flex: 8,
child: Container(
decoration: BoxDecoration(
color: sectionsLocal![index].imageSource == null ? kBackgroundLight : null, // default color if no image
shape: BoxShape.rectangle,
image: sectionsLocal![index].imageSource != null || sectionsLocal![index].type == SectionType.Video ? new DecorationImage(
fit: BoxFit.cover,
image: ImageCustomProvider.getImageProvider(appContext, sectionsLocal![index].imageId, sectionsLocal![index].type == SectionType.Video ? getYoutubeThumbnailUrl(rawSectionsData[index]) : sectionsLocal![index].imageSource!),
): null,
),
)
),
Expanded(
flex: 2,
child: Center(
child: HtmlWidget(
sectionsLocal![index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "",
customStylesBuilder: (element) {
return {'text-align': 'center', 'font-family': "Roboto"};
},
textStyle: TextStyle(fontSize: isPortrait ? 10 : 25),//calculateFontSize(constraints.maxWidth, constraints.maxHeight, kIsWeb ? kWebMenuTitleDetailSize : kMenuTitleDetailSize)),
),
)
)
],
)
),
);
}
getPlatformInfo() async {
return await PackageInfo.fromPlatform();
}
}
boxDecoration(AppContext appContext, SectionDTO section, bool isSelected, Object rawSectionData) {
TabletAppContext tabletAppContext = appContext.getContext() as TabletAppContext;
return BoxDecoration(
color: !isSelected ? kBackgroundLight : section.imageSource == null ? kBackgroundLight : null,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(tabletAppContext.currentAppConfigurationLink?.roundedValue?.toDouble() ?? 30.0),
image: section.imageSource != null || section.type == SectionType.Video ? new DecorationImage(
fit: BoxFit.cover,
colorFilter: !isSelected? new ColorFilter.mode(kBackgroundLight.withValues(alpha: 0.2), BlendMode.dstATop) : null,
image: ImageCustomProvider.getImageProvider(appContext, section.imageId, section.type == SectionType.Video ? getYoutubeThumbnailUrl(rawSectionData) : section.imageSource!),
): null,
boxShadow: [
BoxShadow(
color: kBackgroundSecondGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1.5), // changes position of shadow
),
],
);
}
Future<List<Map<String, dynamic>>> getByteIcons(TabletAppContext tabletAppContext, MapDTO mapDTO) async {
//var mapDTO = MapDTO.fromJson(jsonDecode(section.data!));
var selectedMarkerIcon;
if (mapDTO.iconSource != null) {
if (kIsWeb) {
Uint8List fileData = await http.readBytes(Uri.parse(mapDTO.iconSource!));
selectedMarkerIcon = resizeImage(fileData, 40);
} else {
File? localIcon = await _checkIfLocalResourceExists(tabletAppContext, mapDTO.iconResourceId!);
if(localIcon == null) {
final ByteData imageData = await NetworkAssetBundle(Uri.parse(mapDTO.iconSource!)).load("");
selectedMarkerIcon = await getBytesFromAsset(imageData, 50);
} else {
Uint8List bytes = await localIcon.readAsBytes();
selectedMarkerIcon = await getBytesFromAsset(ByteData.view(bytes.buffer), 50);
}
}
} else {
// Icône par défaut
final ByteData bytes = await rootBundle.load('assets/icons/marker.png');
selectedMarkerIcon = await getBytesFromAsset(bytes, 25);
}
List<Map<String, dynamic>> icons = [];
icons.add({'id': null, 'icon': selectedMarkerIcon});
// Utiliser Future.forEach() pour itérer de manière asynchrone sur la liste des catégories
await Future.forEach(mapDTO!.categories!, (cat) async {
if (cat.resourceDTO != null && cat.resourceDTO!.url != null && cat.resourceDTO!.id != null) {
Uint8List categoryIcon;
if (kIsWeb) {
categoryIcon = await http.readBytes(Uri.parse(cat.resourceDTO!.url!));
} else {
File? localIcon = await _checkIfLocalResourceExists(tabletAppContext, cat.resourceDTO!.id!);
if(localIcon == null) {
final ByteData imageData = await NetworkAssetBundle(Uri.parse(cat.resourceDTO!.url!)).load("");
categoryIcon = await getBytesFromAsset(imageData, 50);
} else {
Uint8List bytes = await localIcon.readAsBytes();
categoryIcon = await getBytesFromAsset(ByteData.view(bytes.buffer), 50);
}
}
icons.add({'id': cat.id, 'icon': categoryIcon});
}
});
return icons;
}
Future<File?> _checkIfLocalResourceExists(TabletAppContext tabletAppContext, String iconId) 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(iconId))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(iconId)).path);
return file;
}
} catch(e) {
print("ERROR _checkIfLocalResourceExists CachedCustomResource");
print(e);
}
return null;
}
Future<Uint8List> getBytesFromAsset(ByteData data, int width) async {
//ByteData data = await rootBundle.load(path);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);
ui.FrameInfo fi = await codec.getNextFrame();
return (await fi.image.toByteData(format: ui.ImageByteFormat.png))!.buffer.asUint8List();
}
Uint8List resizeImage(Uint8List data, int width) {
Uint8List resizedData = data;
IMG.Image img = IMG.decodeImage(data)!;
IMG.Image resized = IMG.copyResize(img, width: width);
resizedData = Uint8List.fromList(IMG.encodeJpg(resized));
return resizedData;
}
Widget getContent(TabletAppContext tabletAppContext, SectionDTO sectionSelected, bool isImageBackground, Object rawSectionData) {
switch (sectionSelected.type) {
case SectionType.Map : // MAP
MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!;
return ChangeNotifierProvider<MapContext>(
create: (_) =>
MapContext(null),
child: FutureBuilder(
future: getByteIcons(tabletAppContext, mapDTO),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return MapView(section: mapDTO, icons: snapshot.data);
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
} else {
return Center(
child: Container(
child: LoadingCommon()
)
);
}
}
) /*FutureBuilder(
future: _url,
builder: (BuildContext context, AsyncSnapshot snapshot) => snapshot.hasData
? WebViewWidget(url: snapshot.data,)
: CircularProgressIndicator()),*/
);
case SectionType.Web : // WEB
WebDTO webDTO = WebDTO.fromJson(rawSectionData)!;
return WebView(section: webDTO);
case SectionType.Video : // Video
VideoDTO videoDTO = VideoDTO.fromJson(rawSectionData)!;
return VideoView(section: videoDTO);
case SectionType.Slider :
SliderDTO sliderDTO = SliderDTO.fromJson(rawSectionData)!;
return SliderView(section: sliderDTO);
case SectionType.Menu :
MenuDTO menuDTO = MenuDTO.fromJson(rawSectionData)!;
return MenuView(section: menuDTO, isImageBackground: isImageBackground);
case SectionType.Quiz :
QuizDTO quizDTO = QuizDTO.fromJson(rawSectionData)!;
return QuizzView(section: quizDTO);
case SectionType.Pdf :
PdfDTO pdfDTO = PdfDTO.fromJson(rawSectionData)!;
return PDFViewWidget(section: pdfDTO);
case SectionType.Game :
GameDTO gameDTO = GameDTO.fromJson(rawSectionData)!;
return GamePage(section: gameDTO);
case SectionType.Agenda :
AgendaDTO agendaDTO = AgendaDTO.fromJson(rawSectionData)!;
return AgendaView(section: agendaDTO);
case SectionType.Article :
ArticleDTO articleDTO = ArticleDTO.fromJson(rawSectionData)!;
return ArticleView(section: articleDTO);
case SectionType.Event :
SectionEventDTO eventDTO = SectionEventDTO.fromJson(rawSectionData)!;
return EventView(section: eventDTO);
case SectionType.Weather :
WeatherDTO weatherDTO = WeatherDTO.fromJson(rawSectionData)!;
return WeatherView(section: weatherDTO);
default:
return Center(child: Text("Ce type n'est pas supporté"));
};
}