From 303e50a255a33bd77c1c8bdc5b9bd8b6475caee3 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Fri, 4 Jul 2025 17:21:17 +0200 Subject: [PATCH] Menu done + article refonte wip --- lib/Components/ScannerBouton.dart | 7 + lib/Components/SearchBox.dart | 4 +- lib/Components/SliderImages.dart | 9 +- .../Sections/Article/article_page.dart | 228 ++++++++--- lib/Screens/Sections/Menu/menu_page.dart | 375 ++++++++++++++++++ lib/Screens/Sections/Quiz/quizz_page.dart | 9 + lib/Screens/section_page.dart | 74 ++-- 7 files changed, 619 insertions(+), 87 deletions(-) create mode 100644 lib/Screens/Sections/Menu/menu_page.dart diff --git a/lib/Components/ScannerBouton.dart b/lib/Components/ScannerBouton.dart index af65621..bfcb129 100644 --- a/lib/Components/ScannerBouton.dart +++ b/lib/Components/ScannerBouton.dart @@ -29,6 +29,13 @@ class _ScannerBoutonState extends State { decoration: const BoxDecoration( shape: BoxShape.circle, color: kMainColor1, + boxShadow: [ + BoxShadow( + offset: Offset(0, 1.5), + blurRadius: 3.5, + color: kConfigurationColor, // Black color with 12% opacity + ) + ], ), height: 85.0, width: 85.0, diff --git a/lib/Components/SearchBox.dart b/lib/Components/SearchBox.dart index d81b3d4..e49ecb9 100644 --- a/lib/Components/SearchBox.dart +++ b/lib/Components/SearchBox.dart @@ -8,9 +8,11 @@ class SearchBox extends StatefulWidget { const SearchBox({ Key? key, this.onChanged, + this.width, }) : super(key: key); final ValueChanged? onChanged; + final double? width; @override State createState() => _SearchBoxState(); @@ -25,7 +27,7 @@ class _SearchBoxState extends State { final appContext = Provider.of(context); return Container( - width: size.width*0.65, + width: widget.width ?? size.width*0.65, margin: const EdgeInsets.all(kDefaultPadding), padding: const EdgeInsets.symmetric( horizontal: kDefaultPadding, diff --git a/lib/Components/SliderImages.dart b/lib/Components/SliderImages.dart index cdb01b7..708ce4e 100644 --- a/lib/Components/SliderImages.dart +++ b/lib/Components/SliderImages.dart @@ -5,6 +5,7 @@ import 'package:manager_api_new/api.dart'; import 'package:mymuseum_visitapp/Components/ShowImagePopup.dart'; import 'package:mymuseum_visitapp/Models/resourceModel.dart'; import 'package:mymuseum_visitapp/Models/visitContext.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/marker_view.dart'; import 'package:mymuseum_visitapp/Services/apiService.dart'; import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/constants.dart'; @@ -69,8 +70,12 @@ class _SliderImagesWidget extends State { items: resourcesInWidget.map((i) { return Builder( builder: (BuildContext context) { + AppContext appContext = Provider.of(context); + ContentDTO contentDTO = ContentDTO(resourceId: i!.id, resource: ResourceDTO(id: i.id, type: i.type, label: i.label, url: i.source)); + var resourcetoShow = getElementForResource(context, appContext, contentDTO, true); + return resourcetoShow; //print(widget.imagesDTO[currentIndex-1]); - return FutureBuilder( + /*return FutureBuilder( future: ApiService.getResource(appContext, visitAppContext.configuration!, i!.id!), builder: (context, AsyncSnapshot snapshot) { return Padding( @@ -118,7 +123,7 @@ class _SliderImagesWidget extends State { ), ); } - ); + );*/ }, ); }).toList(), diff --git a/lib/Screens/Sections/Article/article_page.dart b/lib/Screens/Sections/Article/article_page.dart index 7ccbfd1..a5bc046 100644 --- a/lib/Screens/Sections/Article/article_page.dart +++ b/lib/Screens/Sections/Article/article_page.dart @@ -24,11 +24,12 @@ import 'package:path_provider/path_provider.dart'; import 'audio_player_floating.dart'; class ArticlePage extends StatefulWidget { - const ArticlePage({Key? key, required this.visitAppContextIn, required this.articleDTO, required this.resourcesModel}) : super(key: key); + const ArticlePage({Key? key, required this.visitAppContextIn, required this.articleDTO, required this.resourcesModel, this.mainAudioId}) : super(key: key); final ArticleDTO articleDTO; final VisitAppContext visitAppContextIn; final List resourcesModel; + final String? mainAudioId; @override State createState() => _ArticlePageState(); @@ -45,9 +46,8 @@ class _ArticlePageState extends State { void initState() { widget.visitAppContextIn.isContentCurrentlyShown = true; - audioResourceModel = widget.resourcesModel.firstWhere((r) => r?.type == ResourceType.Audio, orElse: () => null); - - resourcesModelToShow = widget.resourcesModel.where((r) => r?.type != ResourceType.Audio).toList(); // TODO also handle audio in slider .. must differentiate the main audio and the rest + audioResourceModel = widget.resourcesModel.firstWhere((r) => r?.id == widget.mainAudioId, orElse: () => null); + resourcesModelToShow = widget.resourcesModel.where((r) => r?.id != widget.mainAudioId).toList(); super.initState(); } @@ -66,59 +66,175 @@ class _ArticlePageState extends State { visitAppContext = appContext.getContext(); + var title = TranslationHelper.get(widget.articleDTO.title, appContext.getContext()); + String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('
', ' '); + return Scaffold( key: _scaffoldKey, - appBar: CustomAppBar( - title: TranslationHelper.get(widget.articleDTO.title, visitAppContext), - isHomeButton: false, - isTextSizeButton: true, - ), - body: OrientationBuilder( - builder: (context, orientation) { - if(size.height > size.width) { - return Column( - children: [ - if(widget.articleDTO.isContentTop!) - getContent(size, appContext), - if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) - getImages(size, widget.articleDTO.isContentTop!), - - if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) - getImages(size, widget.articleDTO.isContentTop!), - if(!widget.articleDTO.isContentTop!) - getContent(size, appContext), - - /*if(audioResourceModel != null) - AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!),*/ + body: 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: [ + kMainColor0, + kMainColor1, + kMainColor2, ], - ); - } else { - return SizedBox( - height: size.height, + ), + image: widget.articleDTO.imageSource != null ? DecorationImage( + fit: BoxFit.cover, + opacity: 0.65, + image: NetworkImage( + widget.articleDTO.imageSource!, + ), + ): null, + ), + ), + Column( + children: [ + SizedBox( + height: size.height * 0.11, width: size.width, - child: Column( + child: Stack( + fit: StackFit.expand, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if(widget.articleDTO.isContentTop!) - getContent(size, appContext), - if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) - getImages(size, widget.articleDTO.isContentTop!), - - if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) - getImages(size, widget.articleDTO.isContentTop!), - if(!widget.articleDTO.isContentTop!) - getContent(size, appContext), - ], + Center( + child: Padding( + padding: const EdgeInsets.only(top: 22.0), + child: SizedBox( + width: size.width *0.7, + child: HtmlWidget( + cleanedTitle, + textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20), + customStylesBuilder: (element) + { + return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"}; + }, + ), + ), + ), + ), + Positioned( + right: 10, + top: 45, + child: InkWell( + onTap: () { + setState(() { + visitAppContext.isMaximizeTextSize = !visitAppContext.isMaximizeTextSize; + appContext.setContext(visitAppContext); + }); + }, + child: SizedBox( + width: 50, + child: visitAppContext.isMaximizeTextSize ? const Icon(Icons.text_fields, size: 30, color: Colors.white) : const Icon(Icons.format_size, size: 30, color: Colors.white) + ), + ), + ), + Positioned( + top: 35, + left: 10, + child: SizedBox( + width: 50, + height: 50, + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + decoration: const BoxDecoration( + color: kMainColor, + shape: BoxShape.circle, + ), + child: const Icon(Icons.arrow_back, size: 23, color: Colors.white) + ), + ) + ), ), - /*if(audioResourceModel != null) - AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!)*/ ], ), - ); - } - } + ), + Expanded( + child: 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(20), + topRight: Radius.circular(20), + ), + ), + child: OrientationBuilder( + builder: (context, orientation) { + if(size.height > size.width) { + return Column( + children: [ + if(widget.articleDTO.isContentTop!) + getContent(size, appContext), + if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) + getImages(size, widget.articleDTO.isContentTop!), + + if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) + getImages(size, widget.articleDTO.isContentTop!), + if(!widget.articleDTO.isContentTop!) + getContent(size, appContext), + + /*if(audioResourceModel != null) + AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!),*/ + ], + ); + } else { + return SizedBox( + height: size.height, + width: size.width, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if(widget.articleDTO.isContentTop!) + getContent(size, appContext), + if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) + getImages(size, widget.articleDTO.isContentTop!), + + if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) + getImages(size, widget.articleDTO.isContentTop!), + if(!widget.articleDTO.isContentTop!) + getContent(size, appContext), + ], + ), + /*if(audioResourceModel != null) + AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!)*/ + ], + ), + ); + } + } + ), + ), + ), + ], + ), + ], ), floatingActionButton: Padding( padding: const EdgeInsets.only(right: 0, top: 0), //size.height*0.1 @@ -143,7 +259,7 @@ class _ArticlePageState extends State { ), color: Colors.white, shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(5.0), + borderRadius: BorderRadius.circular(20.0), boxShadow: const [kDefaultShadow], ), child: SliderImagesWidget( @@ -168,7 +284,7 @@ class _ArticlePageState extends State { ), color: Colors.white, shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(5.0), + borderRadius: BorderRadius.circular(20.0), boxShadow: const [kDefaultShadow], ), child: SliderImagesWidget( @@ -190,7 +306,7 @@ class _ArticlePageState extends State { height: size.height * 0.76, //color: Colors.blueAccent, child: Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 8.0), + padding: const EdgeInsets.all(10.0), child: Container( decoration: BoxDecoration( border: Border.all( @@ -199,7 +315,7 @@ class _ArticlePageState extends State { ), color: Colors.white, shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(5.0), + borderRadius: BorderRadius.circular(20.0), boxShadow: const [kDefaultShadow], ), child: SingleChildScrollView( @@ -227,7 +343,7 @@ class _ArticlePageState extends State { //height: size.height * 0.65, //color: Colors.blueAccent, child: Padding( - padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 8.0), + padding: const EdgeInsets.all(10.0), child: Container( decoration: BoxDecoration( border: Border.all( @@ -236,12 +352,12 @@ class _ArticlePageState extends State { ), color: Colors.white, shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(5.0), + borderRadius: BorderRadius.circular(20.0), boxShadow: const [kDefaultShadow], ), child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(12.5), child: HtmlWidget( TranslationHelper.get(widget.articleDTO.content, appContext.getContext()), //textAlign: TextAlign.left, diff --git a/lib/Screens/Sections/Menu/menu_page.dart b/lib/Screens/Sections/Menu/menu_page.dart new file mode 100644 index 0000000..e5cdcd2 --- /dev/null +++ b/lib/Screens/Sections/Menu/menu_page.dart @@ -0,0 +1,375 @@ +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 ""; + } +} \ No newline at end of file diff --git a/lib/Screens/Sections/Quiz/quizz_page.dart b/lib/Screens/Sections/Quiz/quizz_page.dart index 2e66d51..56f1850 100644 --- a/lib/Screens/Sections/Quiz/quizz_page.dart +++ b/lib/Screens/Sections/Quiz/quizz_page.dart @@ -105,6 +105,15 @@ class _QuizPageState extends State { offset: Offset(0, 1), // changes position of shadow ), ], + gradient: const LinearGradient( + begin: Alignment.centerRight, + end: Alignment.centerLeft, + colors: [ + kMainColor0, + kMainColor1, + kMainColor2, + ], + ), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(25), bottomRight: Radius.circular(25), diff --git a/lib/Screens/section_page.dart b/lib/Screens/section_page.dart index 4ed841b..f7da8fa 100644 --- a/lib/Screens/section_page.dart +++ b/lib/Screens/section_page.dart @@ -18,6 +18,7 @@ import 'package:mymuseum_visitapp/Screens/Sections/Agenda/agenda_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Map/map_page.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Menu/menu_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/PDF/pdf_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Puzzle/puzzle_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Quiz/quizz_page.dart'; @@ -55,6 +56,8 @@ class _SectionPageState extends State { late final MapContext mapContext = MapContext(null); List>? icons; + String? mainAudioId; + @override void initState() { widget.visitAppContextIn.isContentCurrentlyShown = true; @@ -78,10 +81,6 @@ class _SectionPageState extends State { return Scaffold( key: _scaffoldKey, resizeToAvoidBottomInset: false, - appBar: test!.type == SectionType.Menu ? CustomAppBar( - title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "", - isHomeButton: false, - ) : null, body: MediaQuery.removeViewInsets( context: context, removeBottom: true, @@ -98,34 +97,46 @@ class _SectionPageState extends State { return AgendaPage(section: agendaDTO); case SectionType.Article: ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!; - return ArticlePage(visitAppContextIn: widget.visitAppContextIn, articleDTO: articleDTO, resourcesModel: resourcesModel); - case SectionType.Quiz: - QuizDTO quizDTO = QuizDTO.fromJson(sectionResult)!; - return QuizPage(visitAppContextIn: widget.visitAppContextIn, quizDTO: quizDTO, resourcesModel: resourcesModel); - case SectionType.Web: - WebDTO webDTO = WebDTO.fromJson(sectionResult)!; - return WebPage(section: webDTO); - case SectionType.Pdf: - PdfDTO pdfDTO = PdfDTO.fromJson(sectionResult)!; - return PDFPage(section: pdfDTO); - case SectionType.Video: - VideoDTO videoDTO = VideoDTO.fromJson(sectionResult)!; - return VideoPage(section: videoDTO); - case SectionType.Puzzle: - PuzzleDTO puzzleDTO = PuzzleDTO.fromJson(sectionResult)!; - return PuzzlePage(section: puzzleDTO); - case SectionType.Slider: - SliderDTO sliderDTO = SliderDTO.fromJson(sectionResult)!; - return SliderPage(section: sliderDTO); + return ArticlePage( + visitAppContextIn: widget.visitAppContextIn, + articleDTO: articleDTO, + resourcesModel: resourcesModel, + mainAudioId: mainAudioId, + ); case SectionType.Map: MapDTO mapDTO = MapDTO.fromJson(sectionResult)!; return ChangeNotifierProvider.value( value: mapContext, child: MapPage(section: mapDTO, icons: icons ?? []), ); + case SectionType.Menu: + MenuDTO menuDTO = MenuDTO.fromJson(sectionResult)!; + return MenuPage(section: menuDTO, isImageBackground: widget.configuration.isSectionImageBackground!); + case SectionType.Pdf: + PdfDTO pdfDTO = PdfDTO.fromJson(sectionResult)!; + return PDFPage(section: pdfDTO); + case SectionType.Puzzle: + PuzzleDTO puzzleDTO = PuzzleDTO.fromJson(sectionResult)!; + return PuzzlePage(section: puzzleDTO); + case SectionType.Quiz: + QuizDTO quizDTO = QuizDTO.fromJson(sectionResult)!; + return QuizPage( + visitAppContextIn: widget.visitAppContextIn, + quizDTO: quizDTO, + resourcesModel: resourcesModel, + ); + case SectionType.Slider: + SliderDTO sliderDTO = SliderDTO.fromJson(sectionResult)!; + return SliderPage(section: sliderDTO); + case SectionType.Video: + VideoDTO videoDTO = VideoDTO.fromJson(sectionResult)!; + return VideoPage(section: videoDTO); case SectionType.Weather: WeatherDTO weatherDTO = WeatherDTO.fromJson(sectionResult)!; return WeatherPage(section: weatherDTO); + case SectionType.Web: + WebDTO webDTO = WebDTO.fromJson(sectionResult)!; + return WebPage(section: webDTO); default: return const Center(child: Text("Unsupported type")); } @@ -213,12 +224,18 @@ class _SectionPageState extends State { break; case SectionType.Article: ArticleDTO articleDTO = ArticleDTO.fromJson(rawSectionData)!; - var audioToDownload = articleDTO.audioIds!.firstWhere((a) => a.language == visitAppContext.language!); - if(audioToDownload.value != null) { + var audioToDownload = articleDTO.audioIds!.firstWhere((a) => a.language == visitAppContext.language!).value; + var othersToDownload = articleDTO.contents!.map((c) => c.resourceId); + mainAudioId = audioToDownload; + var resourcesToDownload = []; + resourcesToDownload.add(audioToDownload); + resourcesToDownload.addAll(othersToDownload); + + for (var resourceToDownload in resourcesToDownload) { if(isConfigOffline) { // OFFLINE - List> ressourceArticle = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, audioToDownload.value!); + List> ressourceArticle = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, resourceToDownload); if(ressourceArticle.isNotEmpty) { resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(ressourceArticle.first)); } else { @@ -228,14 +245,14 @@ class _SectionPageState extends State { else { // ONLINE - ResourceDTO? resourceDTO = await client.resourceApi!.resourceGetDetail(audioToDownload.value!); + ResourceDTO? resourceDTO = await client.resourceApi!.resourceGetDetail(resourceToDownload); if(resourceDTO != null && resourceDTO.url != null) { // ONLINE //ResourceModel? resourceAudioOnline = await ApiService.downloadAudio(client, resourceDTO.url!, resourceDTO.id!); ResourceModel resourceAudioOnline = ResourceModel(); resourceAudioOnline.id = resourceDTO.id; resourceAudioOnline.source = resourceDTO.url; - resourceAudioOnline.type = ResourceType.Audio; + resourceAudioOnline.type = resourceDTO.type; resourcesModel.add(resourceAudioOnline); @@ -246,6 +263,7 @@ class _SectionPageState extends State { } } } + break; case SectionType.Map: MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!;