flutter build apk --debug vert, APK produit. La borne couvre désormais
12 des 13 types ; seul SectionParcours reste dehors, et définitivement —
un parcours guidé fait marcher le visiteur avec géodéclenchement.
Les deux écrans suivent la maquette paysage validée le 12/08 : ils sont
dessinés pour une dalle large et fixe, pas pour un téléphone debout.
K7 — Article. article_view.dart remplace le stub Text("TODO Article") et
le case est décommenté (l'import d'ArticleView était déjà en tête de
main_view, laissé par celui qui avait écrit le TODO). Rail média à 38 %
avec image, vignettes à plat et audio docké ; colonne de lecture à 62 %
plafonnée à 720 px, parce qu'étirer le texte sur 1280 px le rend
illisible. isContentTop, qui voulait dire « texte au-dessus » en portrait,
devient le côté : à true, texte à gauche et rail à droite.
Trois états incomplets, tous possibles en configuration. Le rail n'existe
que s'il a quelque chose à ancrer : sans audio le dock tombe, sans photo
le rail disparaît et l'audio passe en tête de la colonne, sans rien la
colonne se centre.
L'audio est porté par langue (audioIds est une List<TranslationDTO>) :
pas d'entrée pour la langue choisie, pas de lecteur, sans repli sur une
autre langue. TranslationHelper.get renvoyant "" quand la langue manque,
la règle tombe juste.
K3 — Événement. event_view.dart : bande héros à 26 % au lieu des 52 % de
mymuseum (debout devant une borne, personne ne défile pour découvrir
qu'il y a un programme dessous), programme à gauche avec bloc « en
cours », carte flutter_map vive à droite. Le détail d'un bloc s'ouvre
sous la carte et non en showModalBottomSheet : ce dernier est une réponse
à l'étroitesse, or ici il y a la place à côté.
Quatre choses que le code a dictées :
- La barre de pied de la maquette n'appartient pas à ces écrans.
section_page_detail la dessine déjà pour les treize types, avec les
clés back/menu selon isFromMenu. La dessiner aurait affiché deux
boutons retour.
- Un seul constructeur de marqueurs au lieu des quatre de mymuseum.
MapAnnotationDTO et MapAnnotation portent des champs rigoureusement
identiques mais sont deux types Dart distincts. Normalisé par une
classe interne à deux fabriques — L5 appliqué au front, et il porte
d'autant plus que la convention [lng, lat] est l'endroit exact où une
divergence ne lève aucune erreur.
- L'audio d'article se résout dans contents avant l'API. ContentDTO
porte son resource complet (idiome de marker_view), donc l'audio est
trouvé sans appel réseau quand il est embarqué, donc hors ligne aussi.
resourceGetDetail n'est qu'un repli ; mymuseum appelle l'API
systématiquement en ligne.
- Nouvelle clé i18n event.live dans les 10 langues. Sans les 10,
getFromLocale renvoie "" et la pastille rendrait une boîte vide.
ATTENTION : PL, CN, UK et AR sont de ma main et demandent une
relecture humaine.
Le nouveau code emploie withValues plutôt que withOpacity, déprécié —
le repo est en pleine migration (13 contre 9).
flutter analyze sur Screens/Article, Screens/Event et Helpers : zéro
issue dans les fichiers touchés. Les 2 restantes sont dans MQTTHelper,
préexistantes.
Non prouvé : le rendu. Aucun des deux écrans n'a été vu à l'œil, et un
build vert ne dit rien d'une mise en page. Aucun SectionEvent n'existe
en base, le type étant né avec Postgres v3 — c'est le §19.13 cas E qui
en créera un.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
666 lines
30 KiB
Dart
666 lines
30 KiB
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: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;
|
|
int rowCount = 4;
|
|
|
|
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);
|
|
|
|
// 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 {
|
|
final rawList = await tabletAppContext.clientAPI!.sectionApi!.sectionGetFromConfigurationDetail(tabletAppContext.configuration!.id!);
|
|
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;
|
|
}
|
|
|
|
getGridSections(AppContext appContext, Color textColor) {
|
|
TabletAppContext tabletAppContext = appContext.getContext() as TabletAppContext;
|
|
|
|
if(sectionsLocal != null) {
|
|
var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
|
|
|
|
return Center(
|
|
child: Container(
|
|
//color: Colors.greenAccent,
|
|
child: GridView.builder(
|
|
shrinkWrap: true,
|
|
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: rowCount),
|
|
itemCount: sectionsLocal!.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
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),
|
|
margin: EdgeInsets.symmetric(vertical: isImageBackground ? 25 : 10, horizontal: 25),
|
|
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)),
|
|
),
|
|
)
|
|
)
|
|
],
|
|
)
|
|
),
|
|
);
|
|
}
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
Center(
|
|
child: Text("Aucune section à afficher", style: TextStyle(fontSize: kNoneInfoOrIncorrect)),
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
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é"));
|
|
};
|
|
} |