Menu done + article refonte wip

This commit is contained in:
Thomas Fransolet 2025-07-04 17:21:17 +02:00
parent c50083b19f
commit 303e50a255
7 changed files with 619 additions and 87 deletions

View File

@ -29,6 +29,13 @@ class _ScannerBoutonState extends State<ScannerBouton> {
decoration: const BoxDecoration( decoration: const BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: kMainColor1, color: kMainColor1,
boxShadow: [
BoxShadow(
offset: Offset(0, 1.5),
blurRadius: 3.5,
color: kConfigurationColor, // Black color with 12% opacity
)
],
), ),
height: 85.0, height: 85.0,
width: 85.0, width: 85.0,

View File

@ -8,9 +8,11 @@ class SearchBox extends StatefulWidget {
const SearchBox({ const SearchBox({
Key? key, Key? key,
this.onChanged, this.onChanged,
this.width,
}) : super(key: key); }) : super(key: key);
final ValueChanged? onChanged; final ValueChanged? onChanged;
final double? width;
@override @override
State<SearchBox> createState() => _SearchBoxState(); State<SearchBox> createState() => _SearchBoxState();
@ -25,7 +27,7 @@ class _SearchBoxState extends State<SearchBox> {
final appContext = Provider.of<AppContext>(context); final appContext = Provider.of<AppContext>(context);
return Container( return Container(
width: size.width*0.65, width: widget.width ?? size.width*0.65,
margin: const EdgeInsets.all(kDefaultPadding), margin: const EdgeInsets.all(kDefaultPadding),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: kDefaultPadding, horizontal: kDefaultPadding,

View File

@ -5,6 +5,7 @@ import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/ShowImagePopup.dart'; import 'package:mymuseum_visitapp/Components/ShowImagePopup.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart'; import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/Models/visitContext.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/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
@ -69,8 +70,12 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
items: resourcesInWidget.map<Widget>((i) { items: resourcesInWidget.map<Widget>((i) {
return Builder( return Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
AppContext appContext = Provider.of<AppContext>(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]); //print(widget.imagesDTO[currentIndex-1]);
return FutureBuilder( /*return FutureBuilder(
future: ApiService.getResource(appContext, visitAppContext.configuration!, i!.id!), future: ApiService.getResource(appContext, visitAppContext.configuration!, i!.id!),
builder: (context, AsyncSnapshot<dynamic> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
return Padding( return Padding(
@ -118,7 +123,7 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
), ),
); );
} }
); );*/
}, },
); );
}).toList(), }).toList(),

View File

@ -24,11 +24,12 @@ import 'package:path_provider/path_provider.dart';
import 'audio_player_floating.dart'; import 'audio_player_floating.dart';
class ArticlePage extends StatefulWidget { 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 ArticleDTO articleDTO;
final VisitAppContext visitAppContextIn; final VisitAppContext visitAppContextIn;
final List<ResourceModel?> resourcesModel; final List<ResourceModel?> resourcesModel;
final String? mainAudioId;
@override @override
State<ArticlePage> createState() => _ArticlePageState(); State<ArticlePage> createState() => _ArticlePageState();
@ -45,9 +46,8 @@ class _ArticlePageState extends State<ArticlePage> {
void initState() { void initState() {
widget.visitAppContextIn.isContentCurrentlyShown = true; widget.visitAppContextIn.isContentCurrentlyShown = true;
audioResourceModel = widget.resourcesModel.firstWhere((r) => r?.type == ResourceType.Audio, orElse: () => null); audioResourceModel = widget.resourcesModel.firstWhere((r) => r?.id == widget.mainAudioId, orElse: () => null);
resourcesModelToShow = widget.resourcesModel.where((r) => r?.id != widget.mainAudioId).toList();
resourcesModelToShow = widget.resourcesModel.where((r) => r?.type != ResourceType.Audio).toList(); // TODO also handle audio in slider .. must differentiate the main audio and the rest
super.initState(); super.initState();
} }
@ -66,59 +66,175 @@ class _ArticlePageState extends State<ArticlePage> {
visitAppContext = appContext.getContext(); visitAppContext = appContext.getContext();
var title = TranslationHelper.get(widget.articleDTO.title, appContext.getContext());
String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('<br>', ' ');
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
appBar: CustomAppBar( body: Stack(
title: TranslationHelper.get(widget.articleDTO.title, visitAppContext), children: [
isHomeButton: false, Container(
isTextSizeButton: true, height: size.height * 0.28,
), decoration: BoxDecoration(
body: OrientationBuilder( boxShadow: const [
builder: (context, orientation) { BoxShadow(
if(size.height > size.width) { color: kMainGrey,
return Column( spreadRadius: 0.5,
children: [ blurRadius: 5,
if(widget.articleDTO.isContentTop!) offset: Offset(0, 1), // changes position of shadow
getContent(size, appContext), ),
if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) ],
getImages(size, widget.articleDTO.isContentTop!), gradient: const LinearGradient(
begin: Alignment.centerRight,
if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) end: Alignment.centerLeft,
getImages(size, widget.articleDTO.isContentTop!), colors: [
if(!widget.articleDTO.isContentTop!) kMainColor0,
getContent(size, appContext), kMainColor1,
kMainColor2,
/*if(audioResourceModel != null)
AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!),*/
], ],
); ),
} else { image: widget.articleDTO.imageSource != null ? DecorationImage(
return SizedBox( fit: BoxFit.cover,
height: size.height, opacity: 0.65,
image: NetworkImage(
widget.articleDTO.imageSource!,
),
): null,
),
),
Column(
children: [
SizedBox(
height: size.height * 0.11,
width: size.width, width: size.width,
child: Column( child: Stack(
fit: StackFit.expand,
children: [ children: [
Row( Center(
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Padding(
children: [ padding: const EdgeInsets.only(top: 22.0),
if(widget.articleDTO.isContentTop!) child: SizedBox(
getContent(size, appContext), width: size.width *0.7,
if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) child: HtmlWidget(
getImages(size, widget.articleDTO.isContentTop!), cleanedTitle,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty) customStylesBuilder: (element)
getImages(size, widget.articleDTO.isContentTop!), {
if(!widget.articleDTO.isContentTop!) return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
getContent(size, appContext), },
], ),
),
),
),
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( floatingActionButton: Padding(
padding: const EdgeInsets.only(right: 0, top: 0), //size.height*0.1 padding: const EdgeInsets.only(right: 0, top: 0), //size.height*0.1
@ -143,7 +259,7 @@ class _ArticlePageState extends State<ArticlePage> {
), ),
color: Colors.white, color: Colors.white,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow], boxShadow: const [kDefaultShadow],
), ),
child: SliderImagesWidget( child: SliderImagesWidget(
@ -168,7 +284,7 @@ class _ArticlePageState extends State<ArticlePage> {
), ),
color: Colors.white, color: Colors.white,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow], boxShadow: const [kDefaultShadow],
), ),
child: SliderImagesWidget( child: SliderImagesWidget(
@ -190,7 +306,7 @@ class _ArticlePageState extends State<ArticlePage> {
height: size.height * 0.76, height: size.height * 0.76,
//color: Colors.blueAccent, //color: Colors.blueAccent,
child: Padding( 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( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
@ -199,7 +315,7 @@ class _ArticlePageState extends State<ArticlePage> {
), ),
color: Colors.white, color: Colors.white,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow], boxShadow: const [kDefaultShadow],
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
@ -227,7 +343,7 @@ class _ArticlePageState extends State<ArticlePage> {
//height: size.height * 0.65, //height: size.height * 0.65,
//color: Colors.blueAccent, //color: Colors.blueAccent,
child: Padding( 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( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
@ -236,12 +352,12 @@ class _ArticlePageState extends State<ArticlePage> {
), ),
color: Colors.white, color: Colors.white,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow], boxShadow: const [kDefaultShadow],
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(12.5),
child: HtmlWidget( child: HtmlWidget(
TranslationHelper.get(widget.articleDTO.content, appContext.getContext()), TranslationHelper.get(widget.articleDTO.content, appContext.getContext()),
//textAlign: TextAlign.left, //textAlign: TextAlign.left,

View File

@ -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<MenuPage> {
//MenuDTO menuDTO = MenuDTO();
SectionDTO? selectedSection;
bool isImageBackground = false;
late List<dynamic> rawSubSectionsData;
late List<SectionDTO> subSections;
String? searchValue;
int? searchNumberValue;
late List<SectionDTO> _allSections;
final ValueNotifier<List<SectionDTO>> 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<SectionDTO>().toList();
isImageBackground = widget.isImageBackground;
WidgetsBinding.instance.addPostFrameCallback((_) {
final appContext = Provider.of<AppContext>(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<AppContext>(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: <Widget>[
// 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<List<SectionDTO>>(
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<SectionDTO> 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 "";
}
}

View File

@ -105,6 +105,15 @@ class _QuizPageState extends State<QuizPage> {
offset: Offset(0, 1), // changes position of shadow 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( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(25), bottomLeft: Radius.circular(25),
bottomRight: Radius.circular(25), bottomRight: Radius.circular(25),

View File

@ -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/Article/article_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.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/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/PDF/pdf_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Puzzle/puzzle_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Puzzle/puzzle_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Quiz/quizz_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Quiz/quizz_page.dart';
@ -55,6 +56,8 @@ class _SectionPageState extends State<SectionPage> {
late final MapContext mapContext = MapContext(null); late final MapContext mapContext = MapContext(null);
List<Map<String, dynamic>>? icons; List<Map<String, dynamic>>? icons;
String? mainAudioId;
@override @override
void initState() { void initState() {
widget.visitAppContextIn.isContentCurrentlyShown = true; widget.visitAppContextIn.isContentCurrentlyShown = true;
@ -78,10 +81,6 @@ class _SectionPageState extends State<SectionPage> {
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
appBar: test!.type == SectionType.Menu ? CustomAppBar(
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "",
isHomeButton: false,
) : null,
body: MediaQuery.removeViewInsets( body: MediaQuery.removeViewInsets(
context: context, context: context,
removeBottom: true, removeBottom: true,
@ -98,34 +97,46 @@ class _SectionPageState extends State<SectionPage> {
return AgendaPage(section: agendaDTO); return AgendaPage(section: agendaDTO);
case SectionType.Article: case SectionType.Article:
ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!; ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!;
return ArticlePage(visitAppContextIn: widget.visitAppContextIn, articleDTO: articleDTO, resourcesModel: resourcesModel); return ArticlePage(
case SectionType.Quiz: visitAppContextIn: widget.visitAppContextIn,
QuizDTO quizDTO = QuizDTO.fromJson(sectionResult)!; articleDTO: articleDTO,
return QuizPage(visitAppContextIn: widget.visitAppContextIn, quizDTO: quizDTO, resourcesModel: resourcesModel); resourcesModel: resourcesModel,
case SectionType.Web: mainAudioId: mainAudioId,
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);
case SectionType.Map: case SectionType.Map:
MapDTO mapDTO = MapDTO.fromJson(sectionResult)!; MapDTO mapDTO = MapDTO.fromJson(sectionResult)!;
return ChangeNotifierProvider<MapContext>.value( return ChangeNotifierProvider<MapContext>.value(
value: mapContext, value: mapContext,
child: MapPage(section: mapDTO, icons: icons ?? []), 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: case SectionType.Weather:
WeatherDTO weatherDTO = WeatherDTO.fromJson(sectionResult)!; WeatherDTO weatherDTO = WeatherDTO.fromJson(sectionResult)!;
return WeatherPage(section: weatherDTO); return WeatherPage(section: weatherDTO);
case SectionType.Web:
WebDTO webDTO = WebDTO.fromJson(sectionResult)!;
return WebPage(section: webDTO);
default: default:
return const Center(child: Text("Unsupported type")); return const Center(child: Text("Unsupported type"));
} }
@ -213,12 +224,18 @@ class _SectionPageState extends State<SectionPage> {
break; break;
case SectionType.Article: case SectionType.Article:
ArticleDTO articleDTO = ArticleDTO.fromJson(rawSectionData)!; ArticleDTO articleDTO = ArticleDTO.fromJson(rawSectionData)!;
var audioToDownload = articleDTO.audioIds!.firstWhere((a) => a.language == visitAppContext.language!); var audioToDownload = articleDTO.audioIds!.firstWhere((a) => a.language == visitAppContext.language!).value;
if(audioToDownload.value != null) { 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) if(isConfigOffline)
{ {
// OFFLINE // OFFLINE
List<Map<String, dynamic>> ressourceArticle = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, audioToDownload.value!); List<Map<String, dynamic>> ressourceArticle = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, resourceToDownload);
if(ressourceArticle.isNotEmpty) { if(ressourceArticle.isNotEmpty) {
resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(ressourceArticle.first)); resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(ressourceArticle.first));
} else { } else {
@ -228,14 +245,14 @@ class _SectionPageState extends State<SectionPage> {
else else
{ {
// ONLINE // ONLINE
ResourceDTO? resourceDTO = await client.resourceApi!.resourceGetDetail(audioToDownload.value!); ResourceDTO? resourceDTO = await client.resourceApi!.resourceGetDetail(resourceToDownload);
if(resourceDTO != null && resourceDTO.url != null) { if(resourceDTO != null && resourceDTO.url != null) {
// ONLINE // ONLINE
//ResourceModel? resourceAudioOnline = await ApiService.downloadAudio(client, resourceDTO.url!, resourceDTO.id!); //ResourceModel? resourceAudioOnline = await ApiService.downloadAudio(client, resourceDTO.url!, resourceDTO.id!);
ResourceModel resourceAudioOnline = ResourceModel(); ResourceModel resourceAudioOnline = ResourceModel();
resourceAudioOnline.id = resourceDTO.id; resourceAudioOnline.id = resourceDTO.id;
resourceAudioOnline.source = resourceDTO.url; resourceAudioOnline.source = resourceDTO.url;
resourceAudioOnline.type = ResourceType.Audio; resourceAudioOnline.type = resourceDTO.type;
resourcesModel.add(resourceAudioOnline); resourcesModel.add(resourceAudioOnline);
@ -246,6 +263,7 @@ class _SectionPageState extends State<SectionPage> {
} }
} }
} }
break; break;
case SectionType.Map: case SectionType.Map:
MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!; MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!;