import 'dart:convert'; import 'package:diacritic/diacritic.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:manager_api_new/api.dart'; import 'package:mymuseum_visitapp/Components/ScannerBouton.dart'; import 'package:mymuseum_visitapp/Components/SearchBox.dart'; import 'package:mymuseum_visitapp/Components/SearchNumberBox.dart'; import 'package:mymuseum_visitapp/Components/SlideFromRouteRight.dart'; import 'package:mymuseum_visitapp/Helpers/ImageCustomProvider.dart'; import 'package:mymuseum_visitapp/Helpers/translationHelper.dart'; import 'package:mymuseum_visitapp/Models/visitContext.dart'; import 'package:mymuseum_visitapp/Screens/section_page.dart'; import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/constants.dart'; import 'package:provider/provider.dart'; class MenuPage extends StatefulWidget { final MenuDTO section; final bool isImageBackground; MenuPage({required this.section, required this.isImageBackground}); @override _MenuPageState createState() => _MenuPageState(); } class _MenuPageState extends State { //MenuDTO menuDTO = MenuDTO(); SectionDTO? selectedSection; bool isImageBackground = false; late List rawSubSectionsData; late List subSections; String? searchValue; int? searchNumberValue; late List _allSections; final ValueNotifier> filteredSections = ValueNotifier([]); @override void initState() { /*print(widget.section.data); menuDTO = MenuDTO.fromJson(jsonDecode(widget.section.data!))!; print(menuDTO);*/ //menuDTO = widget.section; rawSubSectionsData = jsonDecode(jsonEncode(widget.section.sections)); //menuDTO.sections!.sort((a, b) => a.order!.compareTo(b.order!)); // useless, we get these after that subSections = jsonDecode(jsonEncode(rawSubSectionsData)).map((json) => SectionDTO.fromJson(json)).whereType().toList(); isImageBackground = widget.isImageBackground; WidgetsBinding.instance.addPostFrameCallback((_) { final appContext = Provider.of(context, listen: false); VisitAppContext visitAppContext = appContext.getContext(); _allSections = subSections; applyFilters(visitAppContext); }); super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { final appContext = Provider.of(context); Size size = MediaQuery.of(context).size; VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; ConfigurationDTO configurationDTO = appContext.getContext().configuration; Color backgroundColor = appContext.getContext().configuration != null ? 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; Color? primaryColor = configurationDTO.primaryColor != null ? Color(int.parse(configurationDTO.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : null; return SafeArea( bottom: false, top: false, child: Stack( children: [ Container( height: size.height * 0.28, decoration: BoxDecoration( boxShadow: const [ BoxShadow( color: kMainGrey, spreadRadius: 0.5, blurRadius: 5, offset: Offset(0, 1), // changes position of shadow ), ], gradient: const LinearGradient( begin: Alignment.centerRight, end: Alignment.centerLeft, colors: [ /*Color(0xFFDD79C2), Color(0xFFB65FBE), Color(0xFF9146BA), Color(0xFF7633B8), Color(0xFF6528B6), Color(0xFF6025B6)*/ kMainColor0, //Color(0xFFf6b3c4) kMainColor1, kMainColor2, ], ), image: widget.section.imageSource != null ? DecorationImage( fit: BoxFit.cover, opacity: 0.65, image: NetworkImage( widget.section.imageSource!, ), ): null, ), ), Column( children: [ Padding( padding: const EdgeInsets.only(top: 20.0), child: Row( children: [ Padding( padding: const EdgeInsets.only(left: 15), child: SizedBox( width: 50, height: 50, child: InkWell( onTap: () { //setState(() { /**/ Navigator.of(context).pop(); /*visitAppContext.configuration = null; visitAppContext.isScanningBeacons = false;*/ /*Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute( builder: (context) => const HomePage3(), ),(route) => false);*/ //}); }, child: Container( decoration: BoxDecoration( color: primaryColor, shape: BoxShape.circle, ), child: const Icon(Icons.arrow_back, size: 23, color: Colors.white) ), ) ), ), // TODO add a check if search used. SearchBox( width: size.width *0.55, onChanged: (value) { searchValue = value?.trim(); applyFilters(visitAppContext); } ), // TODO add a check if number used. Expanded( child: SearchNumberBox(onChanged: (value) { if (value != null && value.isNotEmpty) { searchNumberValue = int.tryParse(value); } else { searchNumberValue = null; } FocusScope.of(context).unfocus(); applyFilters(visitAppContext); } ), ), ], ), ), Expanded( child: Stack( children: [ // Our background Container( margin: const EdgeInsets.only(top: 0), decoration: const BoxDecoration( boxShadow: [ BoxShadow( color: kMainGrey, spreadRadius: 0.5, blurRadius: 2, offset: Offset(0, 1), // changes position of shadow ), ], color: kBackgroundColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), ), ValueListenableBuilder>( valueListenable: filteredSections, builder: (context, value, child) { return GridView.builder( shrinkWrap: true, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1, childAspectRatio: kIsWeb ? 1.7 : 1.95), itemCount: value.length, itemBuilder: (BuildContext context, int index) { return InkWell( onTap: () { //SectionDTO? section = await (appContext.getContext() as TabletAppContext).clientAPI!.sectionApi!.sectionGetDetail(menuDTO.sections![index].id!); SectionDTO section = value[index]; var rawSectionData = rawSubSectionsData[index]; Navigator.push( context, SlideFromRightRoute(page: SectionPage( configuration: configurationDTO, rawSection: rawSectionData, visitAppContextIn: appContext.getContext(), sectionId: section.id!, )), ); }, child: Container( decoration: isImageBackground ? boxDecoration(appContext, value[index], false, rawSubSectionsData[index]) : null, padding: const EdgeInsets.all(20), margin: const EdgeInsets.symmetric(vertical: 15, horizontal: 15), child: isImageBackground ? Align( alignment: Alignment.bottomRight, child: FractionallySizedBox( heightFactor: 0.5, child: Column( children: [ Align( alignment: Alignment.centerRight, child: HtmlWidget( value[index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "", customStylesBuilder: (element) { return {'text-align': 'right', 'font-family': "Roboto"}; }, textStyle: new TextStyle(fontSize: kMenuTitleDetailSize), ), ), /*Align( alignment: Alignment.centerRight, child: HtmlWidget( menuDTO.sections![index].description!.firstWhere((translation) => translation.language == appContext.getContext().language).value!, customStylesBuilder: (element) { return {'text-align': 'right'}; }, textStyle: new TextStyle(fontSize: kIsWeb? kWebSectionDescriptionDetailSize: kSectionDescriptionDetailSize, fontFamily: ""), ), ),*/ ], ) ), ) : Column( children: [ Expanded( flex: 7, child: Container( decoration: BoxDecoration( color: value[index].imageSource == null && value[index].type != SectionType.Video ? kBackgroundColor : null, // default color if no image shape: BoxShape.rectangle, image: value[index].imageSource != null || value[index].type == SectionType.Video ? new DecorationImage( fit: BoxFit.contain, // contain or cover ? image: ImageCustomProvider.getImageProvider(appContext, value[index].imageId, value[index].type == SectionType.Video ? getYoutubeThumbnailUrl(rawSubSectionsData[index]) : value[index].imageSource!), ): null, ), ) ), Expanded( flex: 3, child: Container( //color: Colors.yellow, constraints: BoxConstraints( maxWidth: size.width * 0.3, ), child: Center( child: HtmlWidget( value[index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "", customStylesBuilder: (element) { return {'text-align': 'center', 'font-family': "Roboto"}; }, textStyle: const TextStyle(fontSize: 20),//calculateFontSize(constraints.maxWidth, constraints.maxHeight, kIsWeb ? kWebMenuTitleDetailSize : kMenuTitleDetailSize)), ), ), ) ) ], ), ), ); } ); } ), ], ), ), ], ), Align( alignment: Alignment.bottomRight, child: Padding( padding: const EdgeInsets.all(15.0), child: ScannerBouton(appContext: appContext), ), ), ], ), ); } void applyFilters(VisitAppContext visitAppContext) { List result = _allSections; if (searchValue != null && searchValue!.isNotEmpty) { result = result.where((s) { final rawTitle = TranslationHelper.get(s.title, visitAppContext); final plainText = stripHtmlTags(rawTitle); final normalizedTitle = removeDiacritics(plainText.toLowerCase()); final normalizedSearch = removeDiacritics(searchValue!.toLowerCase()); return normalizedTitle.contains(normalizedSearch); }).toList(); } else if (searchNumberValue != null) { result = result.where((s) => s.order! + 1 == searchNumberValue).toList(); } filteredSections.value = result; } String stripHtmlTags(String htmlText) { final exp = RegExp(r'<[^>]*>', multiLine: true, caseSensitive: false); return htmlText.replaceAll(exp, ''); } } boxDecoration(AppContext appContext, SectionDTO section, bool isSelected, Object rawSubSectionData) { VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; return BoxDecoration( color: kBackgroundLight, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), image: section.imageSource != null || section.type == SectionType.Video ? DecorationImage( fit: BoxFit.cover, colorFilter: !isSelected? ColorFilter.mode(kBackgroundLight.withValues(alpha: 0.35), BlendMode.dstATop) : null, image: ImageCustomProvider.getImageProvider(appContext, section.imageId, section.type == SectionType.Video ? getYoutubeThumbnailUrl(rawSubSectionData) : section.imageSource!), ): null, boxShadow: const [ BoxShadow( color: kBackgroundSecondGrey, spreadRadius: 0.3, blurRadius: 5, offset: Offset(0, 1.5), // changes position of shadow ), ], ); } String getYoutubeThumbnailUrl(Object rawSectionData) { try{ VideoDTO videoDTO = VideoDTO.fromJson(rawSectionData)!; String thumbnailUrl = ""; if(videoDTO.source_ != null) { //VideoDTO? videoDTO = VideoDTO.fromJson(jsonDecode(sectionDTO.data!)); Uri uri = Uri.parse(videoDTO.source_!); String videoId = uri.queryParameters['v']!; // Construire l'URL du thumbnail en utilisant l'identifiant de la vidéo YouTube thumbnailUrl = 'https://img.youtube.com/vi/$videoId/0.jpg'; } return thumbnailUrl; } catch(e) { return ""; } }