diff --git a/lib/Components/multi_input_modal.dart b/lib/Components/multi_input_modal.dart index b6f2f3c..769c9b6 100644 --- a/lib/Components/multi_input_modal.dart +++ b/lib/Components/multi_input_modal.dart @@ -15,81 +15,95 @@ import 'flag_decoration.dart'; showMultiStringInput (String label, String modalLabel, bool isTitle, List values, List newValues, Function onGetResult, int maxLines, List? resourceTypes, BuildContext context) { /*Function onSelect,*/ showDialog( - builder: (BuildContext context) => AlertDialog( + builder: (BuildContext context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - title: Center(child: Text(modalLabel)), - content: SingleChildScrollView( - child: Column( - children: [ - Container( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: getTranslations(context, Provider.of(context), label, isTitle, resourceTypes, newValues), + child: SizedBox( + width: 560, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center(child: Text(modalLabel, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500))), + const SizedBox(height: 16), + Flexible( + child: SingleChildScrollView( + child: Column( + children: [ + Container( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: getTranslations(context, Provider.of(context), label, isTitle, resourceTypes, newValues), + ), + ), + ), + /*Container( + width: 500, + height: 200, + child: TranslationTab( + translations: newValues, + maxLines: maxLines + ) + ),*/ + /*Column( + children: showValues(newValues), + ),*/ + ], + ) ), ), - ), - /*Container( - width: 500, - height: 200, - child: TranslationTab( - translations: newValues, - maxLines: maxLines - ) - ),*/ - /*Column( - children: showValues(newValues), - ),*/ - ], - ) - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - width: 180, - height: 70, - child: RoundedButton( - text: "Annuler", - icon: Icons.undo, - color: kSecond, - press: () { - onGetResult(values); - Navigator.of(context).pop(); - }, - fontSize: 20, - ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 180, + height: 70, + child: RoundedButton( + text: "Annuler", + icon: Icons.undo, + color: kSecond, + press: () { + onGetResult(values); + Navigator.of(context).pop(); + }, + fontSize: 20, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 180, + height: 70, + child: RoundedButton( + text: "Valider", + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + Function deepEq = const DeepCollectionEquality().equals; + if (!deepEq(values, newValues)) { + onGetResult(newValues); + } + Navigator.of(context).pop(); + }, + fontSize: 20, + ), + ), + ), + ], ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - width: 180, - height: 70, - child: RoundedButton( - text: "Valider", - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - Function deepEq = const DeepCollectionEquality().equals; - if (!deepEq(values, newValues)) { - onGetResult(newValues); - } - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - ], + ], + ), ), - ], + ), ), context: context ); } diff --git a/lib/Components/resource_tab.dart b/lib/Components/resource_tab.dart index 1eff192..bafabfc 100644 --- a/lib/Components/resource_tab.dart +++ b/lib/Components/resource_tab.dart @@ -38,28 +38,24 @@ class _ResourceTabState extends State with SingleTickerProviderStat @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - TabBar( - unselectedLabelColor: Colors.black, - labelColor: kPrimaryColor, - tabs: tabsToShow, - controller: _tabController, - indicatorSize: TabBarIndicatorSize.tab, - indicatorColor: kPrimaryColor, - ), - Expanded( - child: TabBarView( - children: getContent(widget.resourceDTO, widget.onFileUpload, widget.onFileUploadWeb), - controller: _tabController, - ), - ), - ], + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TabBar( + unselectedLabelColor: Colors.black, + labelColor: kPrimaryColor, + tabs: tabsToShow, + controller: _tabController, + indicatorSize: TabBarIndicatorSize.tab, + indicatorColor: kPrimaryColor, ), - ), + Expanded( + child: TabBarView( + children: getContent(widget.resourceDTO, widget.onFileUpload, widget.onFileUploadWeb), + controller: _tabController, + ), + ), + ], ); } diff --git a/lib/Components/string_input_container.dart b/lib/Components/string_input_container.dart index 72f43e8..0cb1acb 100644 --- a/lib/Components/string_input_container.dart +++ b/lib/Components/string_input_container.dart @@ -1,4 +1,3 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/rounded_input_field.dart'; import 'package:manager_app/constants.dart'; @@ -28,37 +27,29 @@ class StringInputContainer extends StatelessWidget { @override Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; - - return Container( - child: Row( - children: [ - Align( - alignment: AlignmentDirectional.centerStart, - child: Text( - label, - style: const TextStyle( - fontWeight: FontWeight.w400, - fontSize: 16, - ), - ), + return Row( + children: [ + Text( + label, + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 16, ), - Padding( + ), + Expanded( + child: Padding( padding: const EdgeInsets.all(8.0), - child: Container( - width: isUrl ? size.width *0.6 : isSmall ? size.width *0.1 : size.width *0.25, - child: RoundedInputField( - color: color!, - textColor: kBlack, - fontSize: fontSizeText, - initialValue: initialValue, - onChanged: onChanged, - maxLength: maxLength, - ), + child: RoundedInputField( + color: color!, + textColor: kBlack, + fontSize: fontSizeText, + initialValue: initialValue, + onChanged: onChanged, + maxLength: maxLength, ), ), - ], - ), + ), + ], ); } -} \ No newline at end of file +} diff --git a/lib/Screens/Applications/add_configuration_link_popup.dart b/lib/Screens/Applications/add_configuration_link_popup.dart index 65c2f9f..5f98f39 100644 --- a/lib/Screens/Applications/add_configuration_link_popup.dart +++ b/lib/Screens/Applications/add_configuration_link_popup.dart @@ -25,7 +25,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex future: getConfigurations(appContext), builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { - List configurations = snapshot.data; + List configurations = snapshot.data ?? []; // filter by already linked configurations = configurations.where((c) => !configurationIds.contains(c.id)).toList(); @@ -61,7 +61,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex shape: BoxShape.rectangle, color: kWhite, borderRadius: BorderRadius.circular(10.0), - image: configuration.imageId != null + image: configuration.imageSource != null ? DecorationImage( fit: BoxFit.cover, image: NetworkImage(configuration.imageSource!), @@ -77,9 +77,9 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex ), ), ), - title: Text(configuration.label!, style: Theme.of(context).textTheme.titleMedium), - subtitle: Text(configuration.dateCreation!.toString(), style: Theme.of(context).textTheme.bodyMedium), - trailing: Text(configuration.languages!.toString(), style: Theme.of(context).textTheme.bodyMedium), + title: Text(configuration.label ?? '', style: Theme.of(context).textTheme.titleMedium), + subtitle: Text(configuration.dateCreation?.toString() ?? '', style: Theme.of(context).textTheme.bodyMedium), + trailing: Text(configuration.languages?.toString() ?? '', style: Theme.of(context).textTheme.bodyMedium), onTap: () { setState(() { if (isSelected) { diff --git a/lib/Screens/Applications/app_configuration_link_screen.dart b/lib/Screens/Applications/app_configuration_link_screen.dart index a14ec32..4832727 100644 --- a/lib/Screens/Applications/app_configuration_link_screen.dart +++ b/lib/Screens/Applications/app_configuration_link_screen.dart @@ -23,7 +23,10 @@ import 'package:provider/provider.dart'; class AppConfigurationLinkScreen extends StatefulWidget { final ApplicationInstanceDTO applicationInstanceDTO; - AppConfigurationLinkScreen({Key? key, required this.applicationInstanceDTO}) : super(key: key); + final bool showAssistant; + final String? configTitle; + final String? appUpdatedLabel; + AppConfigurationLinkScreen({Key? key, required this.applicationInstanceDTO, this.showAssistant = true, this.configTitle, this.appUpdatedLabel}) : super(key: key); @override _AppConfigurationLinkScreenState createState() => _AppConfigurationLinkScreenState(); @@ -43,11 +46,11 @@ class _AppConfigurationLinkScreenState extends State final appContext = Provider.of(context); Size size = MediaQuery.of(context).size; ManagerAppContext managerAppContext = appContext.getContext() as ManagerAppContext; + final appUpdatedMsg = widget.appUpdatedLabel ?? AppLocalizations.of(context)!.appUpdatedSuccess; _generalInfoCard() { - var elementWidth = 400.0; - var elementHeight = 125.0; + const elementHeight = 125.0; return Card( margin: const EdgeInsets.symmetric(vertical: 8), @@ -61,7 +64,10 @@ class _AppConfigurationLinkScreenState extends State Text(AppLocalizations.of(context)!.generalInfo, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), SizedBox(height: 8), Expanded( - child: Center( + child: LayoutBuilder( + builder: (context, constraints) { + final elementWidth = (constraints.maxWidth < 420) ? constraints.maxWidth : 400.0; + return Center( child: SingleChildScrollView( child: Wrap( alignment: WrapAlignment.center, @@ -92,7 +98,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); /*setState(() { });*/ @@ -123,7 +129,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); /*setState(() { });*/ @@ -146,7 +152,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); /*setState(() { });*/ @@ -169,7 +175,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); /*setState(() { });*/ @@ -194,7 +200,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); /*setState(() { });*/ @@ -222,7 +228,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); /*setState(() { });*/ @@ -274,7 +280,7 @@ class _AppConfigurationLinkScreenState extends State // automatic save var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); if(applicationLink != null) { - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); //setState(() { _applicationInstanceDTO.sectionEventDTO = applicationLink.sectionEventDTO; //_applicationInstanceDTO = applicationLink; @@ -287,7 +293,7 @@ class _AppConfigurationLinkScreenState extends State ), ), // Assistant IA — visible uniquement si l'instance a la fonctionnalité - if (managerAppContext.instanceDTO?.isAssistant == true) + if (widget.showAssistant && managerAppContext.instanceDTO?.isAssistant == true) Container( width: elementWidth, height: elementHeight, @@ -312,7 +318,7 @@ class _AppConfigurationLinkScreenState extends State localSetState(() { _applicationInstanceDTO.isAssistant = applicationLink.isAssistant; }); - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); } } catch (e) { showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null); @@ -328,8 +334,10 @@ class _AppConfigurationLinkScreenState extends State ], ), ), - ), + ); + } ) + ) ], ), ), @@ -348,7 +356,7 @@ class _AppConfigurationLinkScreenState extends State children: [ Padding( padding: const EdgeInsets.all(16), - child: Text(AppLocalizations.of(context)!.phoneConfigTitle, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), + child: Text(widget.configTitle ?? AppLocalizations.of(context)!.phoneConfigTitle, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), ), appConfigurationLinks != null ? Padding( padding: const EdgeInsets.only(left: 32, right: 32, top: 75), @@ -376,7 +384,7 @@ class _AppConfigurationLinkScreenState extends State // TODO use order put method var result = await updateAppConfigurationOrder(appContext, updatedList); localSetState(() {}); - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null); + showNotification(kSuccess, kWhite, appUpdatedMsg, context, null); }, actions: [ (BuildContext context, int index, AppConfigurationLinkDTO link) { diff --git a/lib/Screens/Applications/web_app_screen.dart b/lib/Screens/Applications/web_app_screen.dart new file mode 100644 index 0000000..5ea72b9 --- /dev/null +++ b/lib/Screens/Applications/web_app_screen.dart @@ -0,0 +1,194 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart'; +import 'package:manager_app/app_context.dart'; +import 'package:manager_app/Components/message_notification.dart'; +import 'package:manager_app/constants.dart'; +import 'package:manager_app/Models/managerContext.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:provider/provider.dart'; + +class WebInstanceLoader extends StatefulWidget { + const WebInstanceLoader({Key? key}) : super(key: key); + + @override + State createState() => _WebInstanceLoaderState(); +} + +class _WebInstanceLoaderState extends State { + bool _creating = false; + String? _error; + ApplicationInstanceDTO? _instance; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _init()); + } + + void _init() { + final appContext = Provider.of(context, listen: false); + final managerCtx = appContext.getContext() as ManagerAppContext; + final existing = managerCtx.instanceDTO?.applicationInstanceDTOs + ?.firstWhereOrNull((ai) => ai.appType == AppType.Web); + if (existing != null) { + setState(() => _instance = existing); + } + } + + Future _create() async { + final appContext = Provider.of(context, listen: false); + final managerCtx = appContext.getContext() as ManagerAppContext; + final instanceDTO = managerCtx.instanceDTO!; + + setState(() { + _creating = true; + _error = null; + }); + + try { + final newInstance = ApplicationInstanceDTO( + instanceId: instanceDTO.id, + appType: AppType.Web, + languages: ['fr'], + layoutMainPage: LayoutMainPageType.MasonryGrid, + isAssistant: false, + isStatistic: false, + ); + + final created = await managerCtx.clientAPI!.applicationInstanceApi! + .applicationInstanceCreate(newInstance); + + if (created != null) { + instanceDTO.applicationInstanceDTOs = [...?instanceDTO.applicationInstanceDTOs, created]; + setState(() => _instance = created); + } else { + setState(() => _error = 'La création a échoué (réponse vide)'); + } + } catch (e) { + setState(() => _error = e.toString()); + } finally { + setState(() => _creating = false); + } + } + + @override + Widget build(BuildContext context) { + if (_instance != null) { + return WebAppScreen(applicationInstanceDTO: _instance!); + } + + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'L\'application web n\'est pas encore initialisée.', + style: TextStyle(color: kBodyTextColor), + ), + if (_error != null) ...[ + const SizedBox(height: 8), + Text(_error!, style: TextStyle(color: kError, fontSize: 12)), + ], + const SizedBox(height: 16), + _creating + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: _create, + child: const Text('Initialiser l\'application web'), + ), + ], + ), + ); + } +} + +class WebAppScreen extends StatelessWidget { + final ApplicationInstanceDTO applicationInstanceDTO; + + const WebAppScreen({Key? key, required this.applicationInstanceDTO}) : super(key: key); + + @override + Widget build(BuildContext context) { + final appContext = Provider.of(context); + final managerAppContext = appContext.getContext() as ManagerAppContext; + final instanceDTO = managerAppContext.instanceDTO; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (instanceDTO?.webSlug != null || instanceDTO?.publicApiKey != null) + Card( + margin: const EdgeInsets.fromLTRB(0, 0, 0, 8), + color: kWhite, + elevation: 0, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Accès web', style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), + const SizedBox(height: 12), + _InfoRow( + label: 'URL visiteurs', + value: 'app.myinfomate.be/${instanceDTO?.webSlug ?? '—'}', + ), + const SizedBox(height: 8), + _InfoRow( + label: 'Clé publique API', + value: instanceDTO?.publicApiKey ?? '—', + ), + ], + ), + ), + ), + Expanded( + child: AppConfigurationLinkScreen( + applicationInstanceDTO: applicationInstanceDTO, + showAssistant: false, + configTitle: 'Contenu de l\'application web', + appUpdatedLabel: 'Application web mise à jour', + ), + ), + ], + ); + } +} + +class _InfoRow extends StatelessWidget { + final String label; + final String value; + + const _InfoRow({required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: TextStyle(fontSize: 12, color: kBodyTextColor)), + const SizedBox(height: 2), + Text( + value, + style: TextStyle(fontSize: 13, fontFamily: 'monospace', color: kPrimaryColor), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + IconButton( + icon: Icon(Icons.copy_outlined, size: 18, color: kPrimaryColor), + tooltip: 'Copier', + onPressed: () { + Clipboard.setData(ClipboardData(text: value)); + showNotification(kSuccess, kWhite, 'Copié !', context, null); + }, + ), + ], + ); + } +} diff --git a/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart b/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart index 2adc772..bc06f67 100644 --- a/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart @@ -431,13 +431,14 @@ class _EventConfigState extends State { Divider(), // --- Parcours --- if (eventDTO.id != null) - ParcoursConfig( - initialValue: const [], - parentId: eventDTO.id!, - isEvent: true, - onChanged: (paths) { - // parcours are managed independently, no DTO update needed here - }, + SizedBox( + height: 500, + child: ParcoursConfig( + initialValue: const [], + parentId: eventDTO.id!, + isEvent: true, + onChanged: (paths) {}, + ), ), ], ); diff --git a/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart b/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart index 1d7eddc..4fb99e4 100644 --- a/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart +++ b/lib/Screens/Configurations/Section/SubSection/Menu/showEditSubSection.dart @@ -25,112 +25,163 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext Size size = MediaQuery.of(context).size; showDialog( - builder: (BuildContext context) => AlertDialog( + builder: (BuildContext context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - content: Container( - width: size.width *0.85, - child: SingleChildScrollView( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.85, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Modifier sous section", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + Flexible( + child: SingleChildScrollView( + child: Column( children: [ - SizedBox( - height: 100, - child: StringInputContainer( - label: "Nom :", - initialValue: subSectionDTO.label, - onChanged: (String name) { - subSectionDTO.label = name; - }, - ), - ), - ResourceInputContainer( - label: "Image :", - initialValue: subSectionDTO.imageId, - color: kPrimaryColor, - onChanged: (ResourceDTO resource) { - if(resource.id == null) { - subSectionDTO.imageId = null; - subSectionDTO.imageSource = null; - } else { - subSectionDTO.imageId = resource.id; - subSectionDTO.imageSource = resource.url; - } - }, - isSmall: true, + Text("Modifier sous section", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SizedBox( + height: 100, + child: StringInputContainer( + label: "Nom :", + initialValue: subSectionDTO.label, + onChanged: (String name) { + subSectionDTO.label = name; + }, + ), + ), + ResourceInputContainer( + label: "Image :", + initialValue: subSectionDTO.imageId, + color: kPrimaryColor, + onChanged: (ResourceDTO resource) { + if(resource.id == null) { + subSectionDTO.imageId = null; + subSectionDTO.imageSource = null; + } else { + subSectionDTO.imageId = resource.id; + subSectionDTO.imageSource = resource.url; + } + }, + isSmall: true, + ), + ], + ), + Container( + height: size.height * 0.1, + width: double.infinity, + constraints: BoxConstraints(minHeight: 50), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + constraints: BoxConstraints(minHeight: 50, maxHeight: 80), + child: MultiStringInputContainer( + label: AppLocalizations.of(context)!.displayTitleLabel, + modalLabel: "Titre", + fontSize: 20, + isHTML: true, + color: kPrimaryColor, + initialValue: subSectionDTO.title != null ? subSectionDTO.title! : [], + onGetResult: (value) { + if (subSectionDTO.title != value) { + subSectionDTO.title = value; + } + }, + maxLines: 1, + isTitle: true + ), + ), + Container( + constraints: BoxConstraints(minHeight: 50, maxHeight: 80), + child: MultiStringInputContainer( + label: AppLocalizations.of(context)!.displayedDescriptionLabel, + modalLabel: "Description", + fontSize: 20, + isHTML: true, + color: kPrimaryColor, + initialValue: subSectionDTO.description != null ? subSectionDTO.description! : [], + isMandatory: false, + onGetResult: (value) { + if (subSectionDTO.description != value) { + subSectionDTO.description = value; + } + }, + maxLines: 1, + isTitle: false + ), + ), + ], + ) + ), + Container( + decoration: BoxDecoration( + color: kWhite, + borderRadius: BorderRadius.circular(20), + border: Border.all(width: 0.5, color: kSecond) + ), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: getSpecificData( + subSectionDTO, + rawSubSectionData, + rawSubSectionData, + appContext, + (updatedData) { + updatedRawSubSectionData = updatedData; + }, context), + ), + ), + ], ), ], ), - Container( - height: size.height * 0.1, - width: double.infinity, - constraints: BoxConstraints(minHeight: 50), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: AppLocalizations.of(context)!.displayTitleLabel, - modalLabel: "Titre", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: subSectionDTO.title != null ? subSectionDTO.title! : [], - onGetResult: (value) { - if (subSectionDTO.title != value) { - subSectionDTO.title = value; - } - }, - maxLines: 1, - isTitle: true - ), - ), - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: AppLocalizations.of(context)!.displayedDescriptionLabel, - modalLabel: "Description", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: subSectionDTO.description != null ? subSectionDTO.description! : [], - isMandatory: false, - onGetResult: (value) { - if (subSectionDTO.description != value) { - subSectionDTO.description = value; - } - }, - maxLines: 1, - isTitle: false - ), - ), - ], - ) - ), - Container( - decoration: BoxDecoration( - color: kWhite, - borderRadius: BorderRadius.circular(20), - border: Border.all(width: 0.5, color: kSecond) + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 175, + height: 70, + child: RoundedButton( + text: "Annuler", + icon: Icons.undo, + color: kSecond, + press: () { + Navigator.of(context).pop(); + }, + fontSize: 20, + ), ), - child: Padding( - padding: const EdgeInsets.all(10.0), - child: getSpecificData( - subSectionDTO, - rawSubSectionData, - rawSubSectionData, - appContext, - (updatedData) { - updatedRawSubSectionData = updatedData; - }, context), + ), + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: subSectionDTO != null ? 220: 150, + height: 70, + child: RoundedButton( + text: "Sauvegarder", + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + getResult(updatedRawSubSectionData); + Navigator.of(context).pop(); + }, + fontSize: 20, + ), ), ), ], @@ -139,47 +190,6 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext ), ), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: "Annuler", - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: subSectionDTO != null ? 220: 150, - height: 70, - child: RoundedButton( - text: "Sauvegarder", - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - getResult(updatedRawSubSectionData); - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - ], - ), - ], ), context: context ); } diff --git a/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart b/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart index 4d967ba..c2d9e91 100644 --- a/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart +++ b/lib/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart @@ -45,109 +45,119 @@ Future showNewOrUpdatePDFFile(OrderedTranslat Size size = MediaQuery.of(context).size; var result = await showDialog( - builder: (BuildContext dialogContext) => AlertDialog( + builder: (BuildContext dialogContext) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - content: Container( - width: size.width *0.5, - child: SingleChildScrollView( + child: SizedBox( + width: 560, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(text, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(text, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + height: size.height * 0.2, + constraints: BoxConstraints(minHeight: 50, maxHeight: 80), + child: MultiStringInputAndResourceContainer( + label: "Fichier et titre pdf :", + modalLabel: "Fichier et titre pdf", + fontSize: 20, + color: kPrimaryColor, + initialValue: pdfFileDTO.translationAndResourceDTOs != null ? pdfFileDTO.translationAndResourceDTOs! : [], + resourceTypes: [ResourceType.Pdf], + onGetResult: (value) { + if (pdfFileDTO.translationAndResourceDTOs != value) { + pdfFileDTO.translationAndResourceDTOs = value; + } + }, + maxLines: 1, + isTitle: true + ), + ), + /*Container( + height: size.height * 0.2, + constraints: BoxConstraints(minHeight: 50, maxHeight: 80), + child: ResourceInputContainer( + label: AppLocalizations.of(context)!.categoryIconLabel, + initialValue: categorieDTO.iconResourceId, + color: kPrimaryColor, + onChanged: (ResourceDTO resource) { + if(resource.id == null) { + categorieDTO.iconResourceId = null; + categorieDTO.iconUrl = null; + } else { + categorieDTO.iconResourceId = resource.id; + categorieDTO.iconUrl = resource.url; + print("Icône catégorieIcône catégorie"); + print(categorieDTO); + } + }, + isSmall: true + ), + ),*/ + ], + ), + ], + ), + ), + ), + const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Container( - height: size.height * 0.2, - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputAndResourceContainer( - label: "Fichier et titre pdf :", - modalLabel: "Fichier et titre pdf", - fontSize: 20, - color: kPrimaryColor, - initialValue: pdfFileDTO.translationAndResourceDTOs != null ? pdfFileDTO.translationAndResourceDTOs! : [], - resourceTypes: [ResourceType.Pdf], - onGetResult: (value) { - if (pdfFileDTO.translationAndResourceDTOs != value) { - pdfFileDTO.translationAndResourceDTOs = value; - } + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 175, + height: 70, + child: RoundedButton( + text: "Annuler", + icon: Icons.undo, + color: kSecond, + press: () { + Navigator.pop(dialogContext); }, - maxLines: 1, - isTitle: true + fontSize: 20, + ), ), ), - /*Container( - height: size.height * 0.2, - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: ResourceInputContainer( - label: AppLocalizations.of(context)!.categoryIconLabel, - initialValue: categorieDTO.iconResourceId, - color: kPrimaryColor, - onChanged: (ResourceDTO resource) { - if(resource.id == null) { - categorieDTO.iconResourceId = null; - categorieDTO.iconUrl = null; - } else { - categorieDTO.iconResourceId = resource.id; - categorieDTO.iconUrl = resource.url; - print("Icône catégorieIcône catégorie"); - print(categorieDTO); - } - }, - isSmall: true + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: inputPdfFile != null ? 220: 150, + height: 70, + child: RoundedButton( + text: inputPdfFile != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if(pdfFileDTO.translationAndResourceDTOs != null && pdfFileDTO.translationAndResourceDTOs!.isNotEmpty) + { + Navigator.pop(dialogContext, pdfFileDTO); + } + }, + fontSize: 20, ), - ),*/ + ), + ), ], ), ], ), ), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: "Annuler", - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.pop(dialogContext); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: inputPdfFile != null ? 220: 150, - height: 70, - child: RoundedButton( - text: inputPdfFile != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - if(pdfFileDTO.translationAndResourceDTOs != null && pdfFileDTO.translationAndResourceDTOs!.isNotEmpty) - { - Navigator.pop(dialogContext, pdfFileDTO); - } - }, - fontSize: 20, - ), - ), - ), - ], - ), - ], ), context: context ); return result; diff --git a/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart b/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart index 25af3e9..250f7af 100644 --- a/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart +++ b/lib/Screens/Configurations/Section/SubSection/Quizz/new_update_question_quizz.dart @@ -34,88 +34,142 @@ Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, Size size = MediaQuery.of(context).size; var result = await showDialog( - builder: (BuildContext dialogContext) => AlertDialog( + builder: (BuildContext dialogContext) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - content: Container( - width: size.width *0.5, - child: SingleChildScrollView( + child: SizedBox( + width: 560, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(text, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - height: size.height*0.25, - constraints: BoxConstraints(minHeight: 100, maxHeight: 150), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ResourceInputContainer( - label: AppLocalizations.of(context)!.backgroundImageLabel, - initialValue: questionDTO.imageBackgroundResourceId, - color: kPrimaryColor, - onChanged: (ResourceDTO resource) { - if(resource.id == null) { - questionDTO.imageBackgroundResourceId = null; - questionDTO.imageBackgroundResourceUrl = null; - questionDTO.imageBackgroundResourceType = null; - } else { - questionDTO.imageBackgroundResourceId = resource.id; - questionDTO.imageBackgroundResourceUrl = resource.url; - questionDTO.imageBackgroundResourceType = resource.type; - } - }, - isSmall: true - ), - Container( - //color: Colors.orangeAccent, - height: size.height * 0.15, - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputAndResourceContainer( - label: AppLocalizations.of(context)!.questionInputLabel, - modalLabel: AppLocalizations.of(context)!.questionLabel, - fontSize: 20, - color: kPrimaryColor, - resourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], - initialValue: questionDTO.label != null ? questionDTO.label! : [], - onGetResult: (value) { - if (questionDTO.label != value) { - questionDTO.label = value; - } - }, - maxLines: 1, - isTitle: true + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(text, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: size.height*0.25, + constraints: BoxConstraints(minHeight: 100, maxHeight: 150), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ResourceInputContainer( + label: AppLocalizations.of(context)!.backgroundImageLabel, + initialValue: questionDTO.imageBackgroundResourceId, + color: kPrimaryColor, + onChanged: (ResourceDTO resource) { + if(resource.id == null) { + questionDTO.imageBackgroundResourceId = null; + questionDTO.imageBackgroundResourceUrl = null; + questionDTO.imageBackgroundResourceType = null; + } else { + questionDTO.imageBackgroundResourceId = resource.id; + questionDTO.imageBackgroundResourceUrl = resource.url; + questionDTO.imageBackgroundResourceType = resource.type; + } + }, + isSmall: true + ), + Container( + //color: Colors.orangeAccent, + height: size.height * 0.15, + constraints: BoxConstraints(minHeight: 50, maxHeight: 80), + child: MultiStringInputAndResourceContainer( + label: AppLocalizations.of(context)!.questionInputLabel, + modalLabel: AppLocalizations.of(context)!.questionLabel, + fontSize: 20, + color: kPrimaryColor, + resourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], + initialValue: questionDTO.label != null ? questionDTO.label! : [], + onGetResult: (value) { + if (questionDTO.label != value) { + questionDTO.label = value; + } + }, + maxLines: 1, + isTitle: true + ), + ), + ], + ), ), - ), - ], + Container( + height: size.height * 0.33, + decoration: BoxDecoration( + color: kWhite, + //color: Colors.green, + shape: BoxShape.rectangle, + border: Border.all(width: 1.5, color: kSecond), + borderRadius: BorderRadius.circular(10.0), + boxShadow: [ + BoxShadow( + color: kSecond, + spreadRadius: 0.5, + blurRadius: 5, + offset: Offset(0, 1.5), // changes position of shadow + ), + ], + ), + child: QuizzResponseList( + responses: questionDTO.responses!, + onChanged: (List responsesOutput) { + questionDTO.responses = responsesOutput; + }, + ), + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 175, + height: 70, + child: RoundedButton( + text: AppLocalizations.of(context)!.cancel, + icon: Icons.undo, + color: kSecond, + press: () { + Navigator.pop(dialogContext); + }, + fontSize: 20, + ), ), ), - Container( - height: size.height * 0.33, - decoration: BoxDecoration( - color: kWhite, - //color: Colors.green, - shape: BoxShape.rectangle, - border: Border.all(width: 1.5, color: kSecond), - borderRadius: BorderRadius.circular(10.0), - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ), - child: QuizzResponseList( - responses: questionDTO.responses!, - onChanged: (List responsesOutput) { - questionDTO.responses = responsesOutput; - }, + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: inputQuestionDTO != null ? 220: 150, + height: 70, + child: RoundedButton( + text: inputQuestionDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if(!questionDTO.label!.any((label) => label.value == null || label.value!.trim() == "")) { + Navigator.pop(dialogContext, questionDTO); + } else { + showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.translationIncomplete, context, null); + } + }, + fontSize: 20, + ), ), ), ], @@ -124,50 +178,6 @@ Future showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, ), ), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: AppLocalizations.of(context)!.cancel, - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.pop(dialogContext); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: inputQuestionDTO != null ? 220: 150, - height: 70, - child: RoundedButton( - text: inputQuestionDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - if(!questionDTO.label!.any((label) => label.value == null || label.value!.trim() == "")) { - Navigator.pop(dialogContext, questionDTO); - } else { - showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.translationIncomplete, context, null); - } - }, - fontSize: 20, - ), - ), - ), - ], - ), - ], ), context: context ); return result; diff --git a/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart b/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart index ed6e984..504a449 100644 --- a/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart +++ b/lib/Screens/Configurations/Section/SubSection/Slider/new_update_image_slider.dart @@ -34,129 +34,138 @@ Future showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap Size size = MediaQuery.of(context).size; var result = await showDialog( - builder: (BuildContext dialogContext) => AlertDialog( + builder: (BuildContext dialogContext) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - content: Container( - width: size.width *0.35, - constraints: BoxConstraints(minWidth: 300), - child: SingleChildScrollView( + child: SizedBox( + width: 480, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Ressource", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), - Column( - mainAxisAlignment: MainAxisAlignment.center, + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Ressource", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: ResourceInputContainer( + label: "Ressource :", + initialValue: contentDTO.resourceId, + color: kPrimaryColor, + fontSize: 20, + inResourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], + onChanged: (ResourceDTO resource) { + if(resource.id == null) { + contentDTO.resourceId = null; + contentDTO.resource = null; + } else { + contentDTO.resourceId = resource.id; + contentDTO.resource = resource; + } + }, + isSmall: true + ), + ), + if(showTitle || showDescription) + Container( + height: size.height * 0.3, + width: double.infinity, + constraints: BoxConstraints(minHeight: 200), + child: Column( + children: [ + MultiStringInputContainer( + label: AppLocalizations.of(context)!.displayTitleLabel, + modalLabel: "Titre", + fontSize: 20, + isHTML: true, + color: kPrimaryColor, + initialValue: contentDTO.title != null ? contentDTO.title! : [], + onGetResult: (value) { + if (contentDTO.title != value) { + contentDTO.title = value; + } + }, + maxLines: 1, + isTitle: true + ), + MultiStringInputContainer( + label: AppLocalizations.of(context)!.displayedDescriptionLabel, + modalLabel: "Description", + fontSize: 20, + isHTML: true, + color: kPrimaryColor, + initialValue: contentDTO.description != null ? contentDTO.description! : [], + isMandatory: false, + onGetResult: (value) { + if (contentDTO.description != value) { + contentDTO.description = value; + } + }, + maxLines: 1, + isTitle: false + ), + ], + ) + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Center( - child: ResourceInputContainer( - label: "Ressource :", - initialValue: contentDTO.resourceId, - color: kPrimaryColor, - fontSize: 20, - inResourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], - onChanged: (ResourceDTO resource) { - if(resource.id == null) { - contentDTO.resourceId = null; - contentDTO.resource = null; - } else { - contentDTO.resourceId = resource.id; - contentDTO.resource = resource; - } - }, - isSmall: true + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 175, + height: 70, + child: RoundedButton( + text: "Annuler", + icon: Icons.undo, + color: kSecond, + press: () { + Navigator.pop(dialogContext); + }, + fontSize: 20, + ), ), ), - if(showTitle || showDescription) - Container( - height: size.height * 0.3, - width: double.infinity, - constraints: BoxConstraints(minHeight: 200), - child: Column( - children: [ - MultiStringInputContainer( - label: AppLocalizations.of(context)!.displayTitleLabel, - modalLabel: "Titre", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: contentDTO.title != null ? contentDTO.title! : [], - onGetResult: (value) { - if (contentDTO.title != value) { - contentDTO.title = value; - } - }, - maxLines: 1, - isTitle: true - ), - MultiStringInputContainer( - label: AppLocalizations.of(context)!.displayedDescriptionLabel, - modalLabel: "Description", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: contentDTO.description != null ? contentDTO.description! : [], - isMandatory: false, - onGetResult: (value) { - if (contentDTO.description != value) { - contentDTO.description = value; - } - }, - maxLines: 1, - isTitle: false - ), - ], - ) + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: inputContentDTO != null ? 220: 150, + height: 70, + child: RoundedButton( + text: inputContentDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if (contentDTO.resourceId != null) { + Navigator.pop(dialogContext, contentDTO); + } + }, + fontSize: 20, + ), ), + ), ], ), ], ), ), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: "Annuler", - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.pop(dialogContext); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: inputContentDTO != null ? 220: 150, - height: 70, - child: RoundedButton( - text: inputContentDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create, - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - if (contentDTO.resourceId != null) { - Navigator.pop(dialogContext, contentDTO); - } - }, - fontSize: 20, - ), - ), - ), - ], - ), - ], ), context: context ); diff --git a/lib/Screens/Configurations/Section/SubSection/Slider/slider_config.dart b/lib/Screens/Configurations/Section/SubSection/Slider/slider_config.dart index ba1e611..6c18db7 100644 --- a/lib/Screens/Configurations/Section/SubSection/Slider/slider_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Slider/slider_config.dart @@ -68,6 +68,7 @@ class _SliderConfigState extends State { children: [ Container( width: size.width * 0.95, + height: 300, child: ReorderableListView( onReorder: _onReorder, scrollDirection: Axis.horizontal, diff --git a/lib/Screens/Configurations/Section/section_detail_screen.dart b/lib/Screens/Configurations/Section/section_detail_screen.dart index 3604342..871d75f 100644 --- a/lib/Screens/Configurations/Section/section_detail_screen.dart +++ b/lib/Screens/Configurations/Section/section_detail_screen.dart @@ -13,7 +13,6 @@ import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/number_input_container.dart'; -import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Video/video_config.dart'; @@ -82,366 +81,351 @@ class _SectionDetailScreenState extends State { @override Widget build(BuildContext context) { final appContext = Provider.of(context); - Size size = MediaQuery.of(context).size; - - Object? rawSectionData; return FutureBuilder( - future: _sectionFuture, - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - rawSectionData = snapshot.data; + future: _sectionFuture, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Object? rawSectionData = snapshot.data; + var nullableSection = SectionDTO.fromJson(rawSectionData); - var nullableSection = SectionDTO.fromJson(rawSectionData); + if (nullableSection != null) { + sectionDTO = nullableSection; - if (nullableSection != null) { - sectionDTO = nullableSection; - - // Only initialize sectionDetailDTO if it's not already loaded for this section - if (sectionDetailDTO == null || - lastLoadedSectionId != widget.id) { - _initializeSectionDetail(rawSectionData); - lastLoadedSectionId = widget.id; - } - - return Stack( - fit: StackFit.expand, - children: [ - bodySection( - rawSectionData, size, appContext, context), - Align( - alignment: AlignmentDirectional.bottomCenter, - child: Container( - height: 80, - child: getButtons(sectionDTO, appContext), - ), - ) - ], - ); - } else { - return Center( - child: Text( - AppLocalizations.of(context)!.sectionLoadError)); + if (sectionDetailDTO == null || lastLoadedSectionId != widget.id) { + _initializeSectionDetail(rawSectionData); + lastLoadedSectionId = widget.id; } - } else if (snapshot.connectionState == ConnectionState.none) { - return Text(AppLocalizations.of(context)!.noData); + + return _buildBody(rawSectionData, appContext); } else { - return Center( - child: Container( - height: size.height * 0.2, child: CommonLoader())); + return Center(child: Text(AppLocalizations.of(context)!.sectionLoadError)); } - }); + } else if (snapshot.connectionState == ConnectionState.none) { + return Center(child: Text(AppLocalizations.of(context)!.noData)); + } + return const Center(child: CommonLoader()); + }, + ); } - Widget bodySection(Object? rawSectionDTO, Size size, AppContext appContext, - BuildContext context) { - ManagerAppContext managerAppContext = appContext.getContext(); - //SectionDTO? sectionDTO = SectionDTO.fromJson(rawSectionDTO); + // ── Main layout ── - return SingleChildScrollView( - child: Column( - //mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - //color: Colors.orangeAccent, - height: 82, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Widget _buildBody(Object? rawSectionData, AppContext appContext) { + final managerCtx = appContext.getContext() as ManagerAppContext; + final canEdit = managerCtx.canEdit; + final l = AppLocalizations.of(context)!; + + return Column( + children: [ + _buildHeader(appContext, l), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( children: [ - Align( - alignment: AlignmentDirectional.bottomStart, - child: Padding( - padding: const EdgeInsets.all(3.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - getSectionIcon(sectionDTO.type), - color: kPrimaryColor, - size: 25, - ), - ), - Text(sectionDTO.label!, - style: TextStyle( - fontSize: 30, - fontWeight: FontWeight.w400)), - //if((appContext.getContext() as ManagerAppContext).selectedConfiguration!.isMobile!) - DownloadPDF(sections: [sectionDTO]), - ], - ), - Padding( - padding: const EdgeInsets.all(5.0), - child: Text( - DateFormat('dd/MM/yyyy') - .format(sectionDTO.dateCreation!), - style: TextStyle( - fontSize: 15, fontWeight: FontWeight.w200)), - ), - ], - ), - )), - Padding( - padding: const EdgeInsets.all(5.0), - child: Align( - alignment: AlignmentDirectional.centerEnd, - child: InkWell( - onTap: () { - ManagerAppContext managerAppContext = - appContext.getContext(); - managerAppContext.selectedSection = null; - appContext.setContext(managerAppContext); - }, - child: Container( - child: Icon( - Icons.arrow_back, - color: kPrimaryColor, - size: 50.0, - ))), - ), - ) + _cardIdentity(appContext, l), + const SizedBox(height: 12), + _cardQR(appContext, l), + const SizedBox(height: 12), + _cardTypeConfig(rawSectionData, appContext), + const SizedBox(height: 80), ], ), - ), // TITLE - Container( - //color: Colors.blue, - child: Padding( - padding: const EdgeInsets.all(5.0), - child: Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - //if((appContext.getContext() as ManagerAppContext).selectedConfiguration!.isMobile!) - Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - InkWell( - onTap: () async { - var image = await _captureAndSharePng( - globalKey, sectionDTO.id!); - await readAndWriteFiles(image); - showNotification( - kSuccess, - kWhite, - AppLocalizations.of(context)!.qrCodeCopied, - context, - null); - }, - child: Container( - width: size.width * 0.1, - height: 125, - child: RepaintBoundary( - key: globalKey, - child: QrImageView( - padding: EdgeInsets.only( - left: 5.0, - top: 5.0, - bottom: 5.0, - right: 5.0), - data: - "${managerAppContext.host!.replaceFirst("api", "web")}/${managerAppContext.instanceId}/${managerAppContext.selectedConfiguration!.id}/${sectionDTO.id!}", - version: QrVersions.auto, - size: 50.0, - ), - ), - ), - ), - SelectableText(sectionDTO.id!, - style: new TextStyle(fontSize: 15)) - ], - ), - CheckInputContainer( - label: AppLocalizations.of(context)!.beaconLabel, - isChecked: sectionDTO.isBeacon!, - onChanged: (value) { - setState(() { - sectionDTO.isBeacon = value; - save(false, appContext); - }); - }, - ), - if (sectionDTO.isBeacon!) - NumberInputContainer( - label: AppLocalizations.of(context)!.beaconIdLabel, - initialValue: sectionDTO.beaconId != null - ? sectionDTO.beaconId! - : 0, - isSmall: true, - onChanged: (value) { - try { - sectionDTO.beaconId = int.parse(value); - } catch (e) { - print('BeaconId not a number'); - showNotification( - Colors.orange, - kWhite, - AppLocalizations.of(context)!.beaconMustBeNumber, - context, - null); - } - }, - ), - ], - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 100, - child: StringInputContainer( - label: AppLocalizations.of(context)!.identifierLabel, - initialValue: sectionDTO.label, - onChanged: (String value) { - sectionDTO.label = value; - }, - ), - ), - SizedBox( - height: 100, - child: MultiStringInputContainer( - label: AppLocalizations.of(context)!.displayTitleLabel, - modalLabel: AppLocalizations.of(context)!.messageTitle, - color: kPrimaryColor, - initialValue: sectionDTO.title!, - onGetResult: (value) { - if (sectionDTO.title! != value) { - sectionDTO.title = value; - save(true, appContext); - } - }, - maxLines: 1, - isHTML: true, - isTitle: true, - ), - ), - /*if(!(appContext.getContext() as ManagerAppContext).selectedConfiguration!.isMobile!) - MultiStringInputContainer( - label: "Description affichée:", - modalLabel: "Description", - color: kPrimaryColor, - isHTML: true, - initialValue: sectionDTO.description!, - isMandatory: false, - onGetResult: (value) { - if (sectionDTO.description != value) { - sectionDTO.description = value!; - save(true, appContext); - } - }, - maxLines: 2, - isTitle: true, - ),*/ - ], - ), - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ResourceInputContainer( - label: AppLocalizations.of(context)!.imageLabel, - initialValue: sectionDTO.imageId, - color: kPrimaryColor, - onChanged: (ResourceDTO resource) { - if (resource.id == null) { - sectionDTO.imageId = null; - sectionDTO.imageSource = null; - } else { - sectionDTO.imageId = resource.id; - sectionDTO.imageSource = resource.url; - } - }, - ), - ], - ) - ], - ), - ), - ], - ), - ), - ), - ), // FIELDS SECTION - Container( - //width: size.width * 0.8, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: getSpecificData(rawSectionDTO, appContext), - ), - decoration: BoxDecoration( - //color: Colors.lightGreen, - borderRadius: BorderRadius.circular(30), - border: Border.all(width: 1.5, color: kSecond)), ), + ), + _buildFooter(appContext, canEdit, l), + ], + ); + } + + // ── Header ── + + Widget _buildHeader(AppContext appContext, AppLocalizations l) { + final typeName = sectionDTO.type?.toString().split('.').last ?? ''; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: kWhite, + border: Border(bottom: BorderSide(color: kSecond, width: 1)), + ), + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: kPrimaryColor), + onPressed: () { + ManagerAppContext ctx = appContext.getContext(); + ctx.selectedSection = null; + appContext.setContext(ctx); + }, + ), + const SizedBox(width: 8), + Icon(getSectionIcon(sectionDTO.type), color: kPrimaryColor, size: 22), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + sectionDTO.label ?? '', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + ), + Text( + DateFormat('dd/MM/yyyy').format(sectionDTO.dateCreation!), + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w300, color: Colors.grey), + ), + ], + ), + ), + Chip( + label: Text(typeName.toUpperCase()), + backgroundColor: kPrimaryColor.withValues(alpha: 0.1), + labelStyle: const TextStyle(color: kPrimaryColor, fontWeight: FontWeight.w600, fontSize: 11), + side: BorderSide.none, + visualDensity: VisualDensity.compact, + ), + const SizedBox(width: 8), + DownloadPDF(sections: [sectionDTO]), ], ), ); } - getButtons(SectionDTO sectionDTO, AppContext appContext) { - final canEdit = (appContext.getContext() as ManagerAppContext).canEdit; - return Align( - alignment: AlignmentDirectional.bottomCenter, + // ── Footer ── + + Widget _buildFooter(AppContext appContext, bool canEdit, AppLocalizations l) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: const BoxDecoration( + color: kWhite, + boxShadow: [BoxShadow(color: kSecond, blurRadius: 8, offset: Offset(0, -2))], + ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: RoundedButton( - text: AppLocalizations.of(context)!.cancel, - icon: Icons.undo, - color: Colors.grey, - textColor: Colors.white, - fontSize: 15, - press: () { - cancel(appContext); - }, - ), + OutlinedButton.icon( + icon: const Icon(Icons.undo, size: 16), + label: Text(l.cancel), + onPressed: () => cancel(appContext), ), - if (canEdit) Padding( - padding: const EdgeInsets.all(8.0), - child: RoundedButton( - text: AppLocalizations.of(context)!.delete, - icon: Icons.delete, - color: kError, - textColor: Colors.white, - fontSize: 15, - press: () { - delete(appContext); - }, + if (canEdit) ...[ + const SizedBox(width: 8), + ElevatedButton.icon( + icon: const Icon(Icons.delete, size: 16, color: kWhite), + label: Text(l.delete, style: const TextStyle(color: kWhite)), + style: ElevatedButton.styleFrom(backgroundColor: kError), + onPressed: () => delete(appContext), ), - ), - if (canEdit) Padding( - padding: const EdgeInsets.all(8.0), - child: RoundedButton( - text: AppLocalizations.of(context)!.save, - icon: Icons.done, - color: kSuccess, - textColor: Colors.white, - fontSize: 15, - press: () { - save(false, appContext); - }, + const SizedBox(width: 8), + ElevatedButton.icon( + icon: const Icon(Icons.done, size: 16, color: kWhite), + label: Text(l.save, style: const TextStyle(color: kWhite)), + style: ElevatedButton.styleFrom(backgroundColor: kPrimaryColor), + onPressed: () => save(false, appContext), ), - ), + ], ], ), ); } + // ── Card Identité ── + + Widget _cardIdentity(AppContext appContext, AppLocalizations l) { + return Card( + elevation: 0, + color: kWhite, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l.identifierLabel.replaceAll(':', '').trim(), + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kTitleTextColor)), + const SizedBox(height: 12), + LayoutBuilder( + builder: (context, constraints) { + final isWide = constraints.maxWidth > kBreakpointMobile; + + final fields = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StringInputContainer( + label: l.identifierLabel, + initialValue: sectionDTO.label, + onChanged: (String value) => sectionDTO.label = value, + ), + const SizedBox(height: 12), + MultiStringInputContainer( + label: l.displayTitleLabel, + modalLabel: l.messageTitle, + color: kPrimaryColor, + initialValue: sectionDTO.title!, + onGetResult: (value) { + if (sectionDTO.title! != value) { + sectionDTO.title = value; + save(true, appContext); + } + }, + maxLines: 1, + isHTML: true, + isTitle: true, + ), + ], + ); + + final image = ResourceInputContainer( + label: l.imageLabel, + initialValue: sectionDTO.imageId, + color: kPrimaryColor, + onChanged: (ResourceDTO resource) { + if (resource.id == null) { + sectionDTO.imageId = null; + sectionDTO.imageSource = null; + } else { + sectionDTO.imageId = resource.id; + sectionDTO.imageSource = resource.url; + } + }, + ); + + if (isWide) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 3, child: fields), + const SizedBox(width: 16), + Expanded(flex: 2, child: image), + ], + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + fields, + const SizedBox(height: 12), + image, + ], + ); + }, + ), + ], + ), + ), + ); + } + + // ── Card QR / Beacon ── + + Widget _cardQR(AppContext appContext, AppLocalizations l) { + final managerCtx = appContext.getContext() as ManagerAppContext; + final qrData = "${managerCtx.host!.replaceFirst("api", "web")}/${managerCtx.instanceId}/${managerCtx.selectedConfiguration!.id}/${sectionDTO.id!}"; + + return Card( + elevation: 0, + color: kWhite, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + leading: const Icon(Icons.qr_code_2, color: kPrimaryColor), + title: const Text("QR Code / Identifiant", + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kTitleTextColor)), + childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + children: [ + Wrap( + spacing: 24, + runSpacing: 12, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + InkWell( + onTap: () async { + var image = await _captureAndSharePng(globalKey, sectionDTO.id!); + await readAndWriteFiles(image); + if (context.mounted) { + showNotification(kSuccess, kWhite, l.qrCodeCopied, context, null); + } + }, + child: SizedBox( + width: 120, + height: 120, + child: RepaintBoundary( + key: globalKey, + child: QrImageView( + padding: const EdgeInsets.all(5.0), + data: qrData, + version: QrVersions.auto, + size: 50.0, + ), + ), + ), + ), + SelectableText(sectionDTO.id!, style: const TextStyle(fontSize: 13)), + ], + ), + const SizedBox(height: 12), + Wrap( + spacing: 16, + runSpacing: 8, + children: [ + CheckInputContainer( + label: l.beaconLabel, + isChecked: sectionDTO.isBeacon!, + onChanged: (value) { + setState(() { + sectionDTO.isBeacon = value; + save(false, appContext); + }); + }, + ), + if (sectionDTO.isBeacon!) + NumberInputContainer( + label: l.beaconIdLabel, + initialValue: sectionDTO.beaconId ?? 0, + isSmall: true, + onChanged: (value) { + try { + sectionDTO.beaconId = int.parse(value); + } catch (e) { + showNotification(Colors.orange, kWhite, l.beaconMustBeNumber, context, null); + } + }, + ), + ], + ), + ], + ), + ), + ); + } + + // ── Card Type Config ── + + Widget _cardTypeConfig(Object? rawSectionData, AppContext appContext) { + final typeName = sectionDTO.type?.toString().split('.').last ?? 'Section'; + return Card( + elevation: 0, + color: kWhite, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Configuration $typeName", + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kTitleTextColor)), + const SizedBox(height: 12), + getSpecificData(rawSectionData, appContext), + ], + ), + ), + ); + } + + // ── Actions ── + Future cancel(AppContext appContext) async { ManagerAppContext managerAppContext = appContext.getContext(); Object? rawData = await (appContext.getContext() as ManagerAppContext) @@ -480,18 +464,16 @@ class _SectionDetailScreenState extends State { appContext.setContext(managerAppContext); if (isTraduction) { - showNotification( - kSuccess, - kWhite, - AppLocalizations.of(context)!.sectionTranslationSaved, - context, - null); + showNotification(kSuccess, kWhite, + AppLocalizations.of(context)!.sectionTranslationSaved, context, null); } else { showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.sectionSavedSuccess, context, null); } } + // ── Type-specific config widget ── + getSpecificData(Object? rawSectionData, AppContext appContext) { switch (sectionDTO.type) { case SectionType.Map: @@ -527,9 +509,7 @@ class _SectionDetailScreenState extends State { case SectionType.Menu: return MenuConfig( initialValue: sectionDetailDTO as MenuDTO, - onChanged: (String data) { - //sectionDTO.data = data; - }, + onChanged: (String data) {}, ); case SectionType.Quiz: return QuizzConfig( @@ -584,6 +564,8 @@ class _SectionDetailScreenState extends State { } } + // ── Section detail initialization ── + _initializeSectionDetail(Object? rawSectionData) { if (rawSectionData == null) return; switch (sectionDTO.type) { @@ -626,6 +608,8 @@ class _SectionDetailScreenState extends State { } } + // ── Sync sectionDTO fields back to detail DTO before save ── + updateSectionDetail() { switch (sectionDTO.type) { case SectionType.Map: @@ -634,8 +618,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as MapDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as MapDTO).type = sectionDTO.type; (sectionDetailDTO as MapDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as MapDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as MapDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as MapDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as MapDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as MapDTO).label = sectionDTO.label; @@ -655,8 +638,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as SliderDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as SliderDTO).type = sectionDTO.type; (sectionDetailDTO as SliderDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as SliderDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as SliderDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as SliderDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as SliderDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as SliderDTO).label = sectionDTO.label; @@ -676,8 +658,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as VideoDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as VideoDTO).type = sectionDTO.type; (sectionDetailDTO as VideoDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as VideoDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as VideoDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as VideoDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as VideoDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as VideoDTO).label = sectionDTO.label; @@ -697,8 +678,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as WebDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as WebDTO).type = sectionDTO.type; (sectionDetailDTO as WebDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as WebDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as WebDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as WebDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as WebDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as WebDTO).label = sectionDTO.label; @@ -718,8 +698,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as MenuDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as MenuDTO).type = sectionDTO.type; (sectionDetailDTO as MenuDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as MenuDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as MenuDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as MenuDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as MenuDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as MenuDTO).label = sectionDTO.label; @@ -739,8 +718,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as QuizDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as QuizDTO).type = sectionDTO.type; (sectionDetailDTO as QuizDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as QuizDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as QuizDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as QuizDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as QuizDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as QuizDTO).label = sectionDTO.label; @@ -760,8 +738,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as ArticleDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as ArticleDTO).type = sectionDTO.type; (sectionDetailDTO as ArticleDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as ArticleDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as ArticleDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as ArticleDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as ArticleDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as ArticleDTO).label = sectionDTO.label; @@ -781,8 +758,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as PdfDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as PdfDTO).type = sectionDTO.type; (sectionDetailDTO as PdfDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as PdfDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as PdfDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as PdfDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as PdfDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as PdfDTO).label = sectionDTO.label; @@ -802,8 +778,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as GameDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as GameDTO).type = sectionDTO.type; (sectionDetailDTO as GameDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as GameDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as GameDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as GameDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as GameDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as GameDTO).label = sectionDTO.label; @@ -823,8 +798,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as AgendaDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as AgendaDTO).type = sectionDTO.type; (sectionDetailDTO as AgendaDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as AgendaDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as AgendaDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as AgendaDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as AgendaDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as AgendaDTO).label = sectionDTO.label; @@ -844,8 +818,7 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as WeatherDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as WeatherDTO).type = sectionDTO.type; (sectionDetailDTO as WeatherDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as WeatherDTO).configurationId = - sectionDTO.configurationId; + (sectionDetailDTO as WeatherDTO).configurationId = sectionDTO.configurationId; (sectionDetailDTO as WeatherDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as WeatherDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as WeatherDTO).label = sectionDTO.label; @@ -862,29 +835,22 @@ class _SectionDetailScreenState extends State { case SectionType.Event: (sectionDetailDTO as SectionEventDTO).id = sectionDTO.id; (sectionDetailDTO as SectionEventDTO).order = sectionDTO.order; - (sectionDetailDTO as SectionEventDTO).dateCreation = - sectionDTO.dateCreation; + (sectionDetailDTO as SectionEventDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as SectionEventDTO).type = sectionDTO.type; - (sectionDetailDTO as SectionEventDTO).instanceId = - sectionDTO.instanceId; - (sectionDetailDTO as SectionEventDTO).configurationId = - sectionDTO.configurationId; - (sectionDetailDTO as SectionEventDTO).isSubSection = - sectionDTO.isSubSection; + (sectionDetailDTO as SectionEventDTO).instanceId = sectionDTO.instanceId; + (sectionDetailDTO as SectionEventDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as SectionEventDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as SectionEventDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as SectionEventDTO).label = sectionDTO.label; (sectionDetailDTO as SectionEventDTO).title = sectionDTO.title; - (sectionDetailDTO as SectionEventDTO).description = - sectionDTO.description; + (sectionDetailDTO as SectionEventDTO).description = sectionDTO.description; (sectionDetailDTO as SectionEventDTO).imageId = sectionDTO.imageId; - (sectionDetailDTO as SectionEventDTO).imageSource = - sectionDTO.imageSource; + (sectionDetailDTO as SectionEventDTO).imageSource = sectionDTO.imageSource; (sectionDetailDTO as SectionEventDTO).isBeacon = sectionDTO.isBeacon; (sectionDetailDTO as SectionEventDTO).beaconId = sectionDTO.beaconId; (sectionDetailDTO as SectionEventDTO).latitude = sectionDTO.latitude; (sectionDetailDTO as SectionEventDTO).longitude = sectionDTO.longitude; - (sectionDetailDTO as SectionEventDTO).meterZoneGPS = - sectionDTO.meterZoneGPS; + (sectionDetailDTO as SectionEventDTO).meterZoneGPS = sectionDTO.meterZoneGPS; break; } } diff --git a/lib/Screens/Configurations/configuration_detail_screen.dart b/lib/Screens/Configurations/configuration_detail_screen.dart index 4260015..a90be77 100644 --- a/lib/Screens/Configurations/configuration_detail_screen.dart +++ b/lib/Screens/Configurations/configuration_detail_screen.dart @@ -1,23 +1,17 @@ import 'dart:developer'; import 'dart:io'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/check_input_container.dart'; -import 'package:manager_app/Components/color_picker_input_container.dart'; import 'package:manager_app/Components/confirmation_dialog.dart'; -import 'package:manager_app/Components/number_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/multi_select_dropdown_language_container.dart'; -import 'package:manager_app/Components/multi_string_input_container.dart'; -import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Helpers/FileHelper.dart'; -import 'package:manager_app/Helpers/PDFHelper.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/section_reorderList.dart'; import 'package:manager_app/app_context.dart'; @@ -38,450 +32,387 @@ class ConfigurationDetailScreen extends StatefulWidget { class _ConfigurationDetailScreenState extends State { ConfigurationDTO? configurationDTO; - SectionDTO? selectedSection; List? sections; + Future? _configFuture; + Future?>? _sectionsFuture; @override Widget build(BuildContext context) { final appContext = Provider.of(context); - Size size = MediaQuery.of(context).size; - return FutureBuilder( - future: getConfiguration(this.widget, (appContext.getContext() as ManagerAppContext).clientAPI!), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return bodyConfiguration(snapshot.data, size, appContext, context); - } else if (snapshot.connectionState == ConnectionState.none) { - return Text(AppLocalizations.of(context)!.noData); - } else { - return Center( - child: Container( - height: size.height * 0.2, - child: CommonLoader() - ) - ); - } + final managerCtx = appContext.getContext() as ManagerAppContext; + + _configFuture ??= getConfiguration(widget, managerCtx.clientAPI!); + + return FutureBuilder( + future: _configFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) return Center(child: Text(AppLocalizations.of(context)!.noData)); + return _buildBody(snapshot.data!, appContext); + } else if (snapshot.connectionState == ConnectionState.none) { + return Center(child: Text(AppLocalizations.of(context)!.noData)); } + return const Center(child: CommonLoader()); + }, ); } - Widget bodyConfiguration(ConfigurationDTO configurationDTO, Size size, AppContext appContext, BuildContext context) { - ManagerAppContext managerAppContext = appContext.getContext(); - return Column( - //mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - //color: Colors.cyanAccent, - height: size.height *0.1, - child: SingleChildScrollView( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Align( - alignment: AlignmentDirectional.bottomStart, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text(configurationDTO.label!, style: TextStyle(fontSize: 30, fontWeight: FontWeight.w400)), - InkWell( - onTap: () async { - try { - // Export config - Client clientAPI = (appContext.getContext() as ManagerAppContext).clientAPI!; - ExportConfigurationDTO export = await clientAPI.configurationApi!.configurationExport(configurationDTO.id!); + Widget _buildBody(ConfigurationDTO config, AppContext appContext) { + final managerCtx = appContext.getContext() as ManagerAppContext; + final canEdit = managerCtx.canEdit; + final l = AppLocalizations.of(context)!; - if (kIsWeb) { - html.AnchorElement anchorElement = new html.AnchorElement(); - var uri = (Uri.parse((appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.apiClient.basePath+'/api/Configuration/${configurationDTO.id}/export')); - anchorElement.href = uri.toString(); - anchorElement.download = '${configurationDTO.label}.json'; - anchorElement.click(); - } else { - File test = await FileHelper().storeConfiguration(export); - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configExportSuccess(test.path), context, 3000); - } - } catch(e) { - log(e.toString()); - showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.configExportFailed, context, null); - } - }, - child: Padding( - padding: const EdgeInsets.only(left: 5.0), - child: Icon(Icons.cloud_download, color: kPrimaryColor) - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.all(5.0), - child: Text(DateFormat('dd/MM/yyyy').format(configurationDTO.dateCreation!), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200)), - ), - ], - ), - ) - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: Align( - alignment: AlignmentDirectional.centerEnd, - child: InkWell( - onTap: () { - ManagerAppContext managerAppContext = appContext.getContext(); - managerAppContext.selectedConfiguration = null; - appContext.setContext(managerAppContext); - }, - child: Container( - child: Icon( - Icons.arrow_back, - color: kPrimaryColor, - size: 50.0, - ) - ) - ), - ), - ) + return Column( + children: [ + _buildHeader(config, appContext, l), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + _cardGeneral(config, l), + const SizedBox(height: 12), + _cardImages(config, l), + const SizedBox(height: 12), + _cardSections(config, appContext), ], ), ), - ), // TITLE - Container( - height: size.height *0.78, - //color: Colors.red, - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LayoutBuilder( - builder: (context, constraints) { - final isWide = constraints.maxWidth > 700; - - final fields = Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: StringInputContainer( - label: AppLocalizations.of(context)!.identifierLabel, - fontSize: 20, - fontSizeText: 20, - initialValue: configurationDTO.label, - onChanged: (value) { - configurationDTO.label = value; - }, - ), - ), - Wrap( - spacing: 24, - runSpacing: 8, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - MultiSelectDropdownLanguageContainer( - label: AppLocalizations.of(context)!.languagesLabel, - initialValue: configurationDTO.languages != null ? configurationDTO.languages! : [], - values: languages, - isMultiple: true, - fontSize: 20, - isAtLeastOne: true, - onChanged: (value) { - var tempOutput = new List.from(value); - configurationDTO.languages = tempOutput; - }, - ), - CheckInputContainer( - icon: Icons.signal_wifi_off, - label: "Hors ligne :", // no ARB key for this one yet, leave as-is - fontSize: 20, - isChecked: configurationDTO.isOffline, - onChanged: (value) { - configurationDTO.isOffline = value; - }, - ), - ], - ), - ], - ); - - final images = Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ResourceInputContainer( - label: AppLocalizations.of(context)!.mainImageLabel, - fontSize: 20, - initialValue: configurationDTO.imageId, - color: kPrimaryColor, - onChanged: (ResourceDTO resource) { - if (resource.id == null) { - configurationDTO.imageId = null; - configurationDTO.imageSource = null; - } else { - configurationDTO.imageId = resource.id; - configurationDTO.imageSource = resource.url; - } - }, - ), - ResourceInputContainer( - label: AppLocalizations.of(context)!.loaderLabel, - fontSize: 20, - initialValue: configurationDTO.loaderImageId, - color: kPrimaryColor, - onChanged: (ResourceDTO resource) { - if (resource.id == null) { - configurationDTO.loaderImageId = null; - configurationDTO.loaderImageUrl = null; - } else { - configurationDTO.loaderImageId = resource.id; - configurationDTO.loaderImageUrl = resource.url; - } - }, - ), - ], - ); - - if (isWide) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(flex: 3, child: fields), - SizedBox(width: 24), - Expanded(flex: 2, child: images), - ], - ); - } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: StringInputContainer( - label: "Identifiant :", - fontSize: 20, - fontSizeText: 20, - initialValue: configurationDTO.label, - onChanged: (value) { - configurationDTO.label = value; - }, - ), - ), - SizedBox(height: 8), - MultiSelectDropdownLanguageContainer( - label: "Langues :", - initialValue: configurationDTO.languages != null ? configurationDTO.languages! : [], - values: languages, - isMultiple: true, - fontSize: 20, - isAtLeastOne: true, - onChanged: (value) { - var tempOutput = new List.from(value); - configurationDTO.languages = tempOutput; - }, - ), - SizedBox(height: 8), - CheckInputContainer( - icon: Icons.signal_wifi_off, - label: "Hors ligne :", - fontSize: 20, - isChecked: configurationDTO.isOffline, - onChanged: (value) { - configurationDTO.isOffline = value; - }, - ), - SizedBox(height: 16), - images, - ], - ); - } - }, - ), - Padding( - padding: const EdgeInsets.all(20.0), - child: Container( - decoration: BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(25.0), - border: Border.all(width: 0.5, color: kSecond) - ), - child: FutureBuilder( - future: getSections(configurationDTO, (appContext.getContext() as ManagerAppContext).clientAPI!), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) - { - sections = new List.from(snapshot.data).where((section) => !section.isSubSection!).toList(); - return bodyGrid(configurationDTO, size, appContext); - } - else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); - } else { - return Center( - child: Container( - height: size.height * 0.15, - child: CommonLoader() - ) - ); - } - } - ), - ), - ), - ], - ), - ), - ), - ), - ),// FIELDS SECTION - Container( - height: size.height *0.08, - //color: Colors.greenAccent, - child: SingleChildScrollView( - child: getButtons(configurationDTO, appContext) - ) - ) + ), + _buildFooter(config, appContext, canEdit, l), ], ); } - getButtons(ConfigurationDTO configurationDTO, AppContext appContext) { - final canEdit = (appContext.getContext() as ManagerAppContext).canEdit; - return Align( - alignment: AlignmentDirectional.bottomCenter, + // ── Header ── + + Widget _buildHeader(ConfigurationDTO config, AppContext appContext, AppLocalizations l) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: kWhite, + border: Border(bottom: BorderSide(color: kSecond, width: 1)), + ), child: Row( - mainAxisAlignment: MainAxisAlignment.end, children: [ - Padding( - padding: const EdgeInsets.all(5.0), - child: RoundedButton( - text: AppLocalizations.of(context)!.cancel, - icon: Icons.undo, - color: Colors.grey, - textColor: Colors.white, - fontSize: 15, - press: () { - cancel(configurationDTO, appContext); - }, + IconButton( + icon: const Icon(Icons.arrow_back, color: kPrimaryColor), + onPressed: () { + ManagerAppContext ctx = appContext.getContext(); + ctx.selectedConfiguration = null; + appContext.setContext(ctx); + }, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + config.label ?? '', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + ), + Text( + DateFormat('dd/MM/yyyy').format(config.dateCreation!), + style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w300, color: Colors.grey), + ), + ], ), ), - if (canEdit) Padding( - padding: const EdgeInsets.all(5.0), - child: RoundedButton( - text: AppLocalizations.of(context)!.delete, - icon: Icons.delete, - color: kError, - textColor: Colors.white, - fontSize: 15, - press: () { - delete(configurationDTO, appContext); - }, - ), - ), - if (canEdit) Padding( - padding: const EdgeInsets.all(5.0), - child: RoundedButton( - text: AppLocalizations.of(context)!.save, - icon: Icons.done, - color: kSuccess, - textColor: Colors.white, - fontSize: 15, - press: () { - save(configurationDTO, appContext); - }, - ), + IconButton( + icon: const Icon(Icons.download_outlined, color: kPrimaryColor), + tooltip: l.configExportSuccess(''), + onPressed: () => _export(config, appContext), ), ], ), ); } - Widget bodyGrid(ConfigurationDTO configurationDTO, Size size, AppContext appContext) { - return SingleChildScrollView( - child: Container( - height: size.height *0.40, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SectionReorderList( - sectionsIn: sections!, - configurationId: configurationDTO.id!, - onChangedOrder: (List sectionsOut) async { - sections = sectionsOut; + // ── Footer ── - // Update section order - ManagerAppContext managerAppContext = appContext.getContext(); - await managerAppContext.clientAPI!.sectionApi!.sectionUpdateOrder(sections!); - }, - askReload: () { - setState(() { - // refresh UI - }); - }, - ), + Widget _buildFooter(ConfigurationDTO config, AppContext appContext, bool canEdit, AppLocalizations l) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: const BoxDecoration( + color: kWhite, + boxShadow: [BoxShadow(color: kSecond, blurRadius: 8, offset: Offset(0, -2))], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton.icon( + icon: const Icon(Icons.undo, size: 16), + label: Text(l.cancel), + onPressed: () => cancel(config, appContext), + ), + if (canEdit) ...[ + const SizedBox(width: 8), + ElevatedButton.icon( + icon: const Icon(Icons.delete, size: 16, color: kWhite), + label: Text(l.delete, style: const TextStyle(color: kWhite)), + style: ElevatedButton.styleFrom(backgroundColor: kError), + onPressed: () => delete(config, appContext), + ), + const SizedBox(width: 8), + ElevatedButton.icon( + icon: const Icon(Icons.done, size: 16, color: kWhite), + label: Text(l.save, style: const TextStyle(color: kWhite)), + style: ElevatedButton.styleFrom(backgroundColor: kPrimaryColor), + onPressed: () => save(config, appContext), + ), + ], + ], + ), + ); + } + + // ── Card Général ── + + Widget _cardGeneral(ConfigurationDTO config, AppLocalizations l) { + return Card( + elevation: 0, + color: kWhite, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l.identifierLabel.replaceAll(':', '').trim(), + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kTitleTextColor)), + const SizedBox(height: 12), + StringInputContainer( + label: l.identifierLabel, + fontSize: 16, + fontSizeText: 16, + initialValue: config.label, + onChanged: (value) => config.label = value, + ), + const SizedBox(height: 12), + Wrap( + spacing: 24, + runSpacing: 12, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + MultiSelectDropdownLanguageContainer( + label: l.languagesLabel, + initialValue: config.languages ?? [], + values: languages, + isMultiple: true, + fontSize: 16, + isAtLeastOne: true, + onChanged: (value) { + config.languages = List.from(value); + }, + ), + CheckInputContainer( + icon: Icons.signal_wifi_off, + label: "Hors ligne :", + fontSize: 16, + isChecked: config.isOffline, + onChanged: (value) => config.isOffline = value, + ), + ], + ), + ], ), ), ); } - Future cancel(ConfigurationDTO configurationDTO, AppContext appContext) async { - ManagerAppContext managerAppContext = appContext.getContext(); - ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationGetDetail(configurationDTO.id!); - managerAppContext.selectedConfiguration = configuration; - appContext.setContext(managerAppContext); - } + // ── Card Images ── - Future delete(ConfigurationDTO configurationDTO, AppContext appContext) async { - showConfirmationDialog( - AppLocalizations.of(context)!.configDeleteConfirm, - () {}, - () async { - ManagerAppContext managerAppContext = appContext.getContext(); - await managerAppContext.clientAPI!.configurationApi!.configurationDelete(configurationDTO.id!); - managerAppContext.selectedConfiguration = null; - appContext.setContext(managerAppContext); - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeletedSuccess, context, null); - }, - context + Widget _cardImages(ConfigurationDTO config, AppLocalizations l) { + return Card( + elevation: 0, + color: kWhite, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Images", + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kTitleTextColor)), + const SizedBox(height: 12), + LayoutBuilder( + builder: (context, constraints) { + final isWide = constraints.maxWidth > kBreakpointMobile; + final children = [ + ResourceInputContainer( + label: l.mainImageLabel, + fontSize: 16, + initialValue: config.imageId, + color: kPrimaryColor, + onChanged: (ResourceDTO resource) { + if (resource.id == null) { + config.imageId = null; + config.imageSource = null; + } else { + config.imageId = resource.id; + config.imageSource = resource.url; + } + }, + ), + ResourceInputContainer( + label: l.loaderLabel, + fontSize: 16, + initialValue: config.loaderImageId, + color: kPrimaryColor, + onChanged: (ResourceDTO resource) { + if (resource.id == null) { + config.loaderImageId = null; + config.loaderImageUrl = null; + } else { + config.loaderImageId = resource.id; + config.loaderImageUrl = resource.url; + } + }, + ), + ]; + + if (isWide) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: children.map((c) => Expanded(child: Padding( + padding: const EdgeInsets.only(right: 16), + child: c, + ))).toList(), + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children.map((c) => Padding( + padding: const EdgeInsets.only(bottom: 12), + child: c, + )).toList(), + ); + }, + ), + ], + ), + ), ); } - Future save(ConfigurationDTO configurationDTO, AppContext appContext) async { + // ── Card Sections ── + + Widget _cardSections(ConfigurationDTO config, AppContext appContext) { + final managerCtx = appContext.getContext() as ManagerAppContext; + _sectionsFuture ??= getSections(config, managerCtx.clientAPI!); + + return Card( + elevation: 0, + color: kWhite, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Sections", + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kTitleTextColor)), + const SizedBox(height: 12), + FutureBuilder?>( + future: _sectionsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) return const Text("No data"); + sections = List.from(snapshot.data!) + .where((s) => !s.isSubSection!) + .toList(); + return SectionReorderList( + sectionsIn: sections!, + configurationId: config.id!, + onChangedOrder: (List sectionsOut) async { + sections = sectionsOut; + await managerCtx.clientAPI!.sectionApi!.sectionUpdateOrder(sections!); + }, + askReload: () => setState(() { + _sectionsFuture = null; + }), + ); + } else if (snapshot.connectionState == ConnectionState.none) { + return const Text("No data"); + } + return const Center(child: Padding( + padding: EdgeInsets.all(32), + child: CommonLoader(), + )); + }, + ), + ], + ), + ), + ); + } + + // ── Actions ── + + void _export(ConfigurationDTO config, AppContext appContext) async { + final l = AppLocalizations.of(context)!; + try { + Client clientAPI = (appContext.getContext() as ManagerAppContext).clientAPI!; + await clientAPI.configurationApi!.configurationExport(config.id!); + + if (kIsWeb) { + html.AnchorElement anchorElement = html.AnchorElement(); + var uri = Uri.parse('${clientAPI.resourceApi!.apiClient.basePath}/api/Configuration/${config.id}/export'); + anchorElement.href = uri.toString(); + anchorElement.download = '${config.label}.json'; + anchorElement.click(); + } else { + ExportConfigurationDTO export = await clientAPI.configurationApi!.configurationExport(config.id!); + File test = await FileHelper().storeConfiguration(export); + showNotification(kSuccess, kWhite, l.configExportSuccess(test.path), context, 3000); + } + } catch (e) { + log(e.toString()); + showNotification(kPrimaryColor, kWhite, l.configExportFailed, context, null); + } + } + + Future cancel(ConfigurationDTO config, AppContext appContext) async { ManagerAppContext managerAppContext = appContext.getContext(); - ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationUpdate(configurationDTO); + ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationGetDetail(config.id!); managerAppContext.selectedConfiguration = configuration; appContext.setContext(managerAppContext); + } + Future delete(ConfigurationDTO config, AppContext appContext) async { + showConfirmationDialog( + AppLocalizations.of(context)!.configDeleteConfirm, + () {}, + () async { + ManagerAppContext managerAppContext = appContext.getContext(); + await managerAppContext.clientAPI!.configurationApi!.configurationDelete(config.id!); + managerAppContext.selectedConfiguration = null; + appContext.setContext(managerAppContext); + showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeletedSuccess, context, null); + }, + context, + ); + } + + Future save(ConfigurationDTO config, AppContext appContext) async { + ManagerAppContext managerAppContext = appContext.getContext(); + ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationUpdate(config); + managerAppContext.selectedConfiguration = configuration; + appContext.setContext(managerAppContext); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configSavedSuccess, context, null); } Future getConfiguration(ConfigurationDetailScreen widget, Client client) async { - ConfigurationDTO? configuration = await client.configurationApi!.configurationGetDetail(widget.id); - return configuration; + return await client.configurationApi!.configurationGetDetail(widget.id); } - Future?> getSections(ConfigurationDTO configurationDTO, Client client) async { - List? sections = await client.sectionApi!.sectionGetFromConfiguration(configurationDTO.id!); - if(sections != null) { + Future?> getSections(ConfigurationDTO config, Client client) async { + List? sections = await client.sectionApi!.sectionGetFromConfiguration(config.id!); + if (sections != null) { sections.sort((a, b) => a.order!.compareTo(b.order!)); } return sections; } } - -boxDecoration(dynamic element) { - return BoxDecoration( - color: element.id == null ? kSuccess : kTextLightColor, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(25.0), - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ); -} - diff --git a/lib/Screens/Configurations/configurations_screen.dart b/lib/Screens/Configurations/configurations_screen.dart index dfe9d43..3c97abd 100644 --- a/lib/Screens/Configurations/configurations_screen.dart +++ b/lib/Screens/Configurations/configurations_screen.dart @@ -1,6 +1,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/common_loader.dart'; +import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Models/managerContext.dart'; @@ -23,255 +24,249 @@ class ConfigurationsScreen extends StatefulWidget { class _ConfigurationsScreenState extends State { ConfigurationDTO? selectedConfiguration; + String _filter = ''; + Future?>? _future; + + void _refresh() => setState(() => _future = null); @override Widget build(BuildContext context) { final appContext = Provider.of(context); - Size size = MediaQuery.of(context).size; - - ManagerAppContext managerAppContext = appContext.getContext(); + final Size size = MediaQuery.of(context).size; + final ManagerAppContext managerAppContext = appContext.getContext(); if (managerAppContext.selectedSection != null) { return SectionDetailScreen(id: managerAppContext.selectedSection!.id!); } if (managerAppContext.selectedConfiguration != null) { return ConfigurationDetailScreen(id: managerAppContext.selectedConfiguration!.id!); - } else { - return Align( - alignment: AlignmentDirectional.topCenter, - child: FutureBuilder( - future: getConfigurations(managerAppContext), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - var tempOutput = new List.from(snapshot.data); - if (managerAppContext.canEdit) tempOutput.add(ConfigurationDTO(id: null)); - return bodyGrid(tempOutput, size, appContext, context); - } else if (snapshot.connectionState == ConnectionState.none) { - return Text(AppLocalizations.of(context)!.noData); - } else { - return Center( - child: Container( - height: size.height * 0.2, - child: CommonLoader() - ) - ); - } - } - ), - ); } - } - Widget bodyGrid(data, Size size, AppContext appContext, BuildContext mainContext) { - final screenWidth = MediaQuery.of(context).size.width; - final itemWidth = 175; - final crossAxisCount = (screenWidth / itemWidth).floor().clamp(1, 6); - return GridView.builder( - shrinkWrap: true, - gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), - itemCount: data.length, - itemBuilder: (BuildContext context, int index) { - return - InkWell( - onTap: () async { - if (data[index].id == null) { - var configurationToCreate = await showNewConfiguration(appContext, (bool) { - if (bool) { - setState(() { - // Thanks future builder for the refresh.. - }); - } - }, context, mainContext); - if(configurationToCreate != null) { - if (configurationToCreate.label != null) { - configurationToCreate.dateCreation = DateTime.now(); - /*configurationToCreate.isMobile = false; - configurationToCreate.isTablet = false;*/ - configurationToCreate.isOffline = false; - /*configurationToCreate.isDate = false; - configurationToCreate.isHour = false; - configurationToCreate.isSectionImageBackground = false;*/ - configurationToCreate.instanceId = (appContext.getContext() as ManagerAppContext).instanceId; - await (appContext.getContext() as ManagerAppContext).clientAPI!.configurationApi!.configurationCreate(configurationToCreate); - ManagerAppContext managerAppContext = appContext.getContext(); - managerAppContext.selectedConfiguration = null; - await appContext.setContext(managerAppContext); + _future ??= getConfigurations(managerAppContext); - showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configCreatedSuccess, context, null); - } - } - } else { - setState(() { - ManagerAppContext managerAppContext = appContext.getContext(); - managerAppContext.selectedConfiguration = data[index]; - selectedConfiguration = data[index]; - }); - } - }, + return Align( + alignment: AlignmentDirectional.topCenter, + child: FutureBuilder( + future: _future, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + var all = List.from(snapshot.data); + if (managerAppContext.canEdit) all.add(ConfigurationDTO(id: null)); + + final filtered = _filter.isEmpty + ? all + : all + .where((c) => + c.id == null || + (c.label?.toLowerCase().contains(_filter.toLowerCase()) ?? + false)) + .toList(); + + return _buildGrid(filtered, size, appContext, context); + } else if (snapshot.connectionState == ConnectionState.none) { + return Text(AppLocalizations.of(context)!.noData); + } else { + return Center( child: Container( - decoration: boxDecoration(data[index]), - padding: const EdgeInsets.all(15), - margin: EdgeInsets.symmetric(vertical: 15, horizontal: 15), - child: Align( - alignment: Alignment.center, - child: getElement(data[index], size) - ), + height: size.height * 0.2, + child: CommonLoader(), ), ); - } + } + }, + ), ); } - getElement(ConfigurationDTO configuration, Size size) { - if (configuration.id != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Align( + Widget _buildGrid(List data, Size size, AppContext appContext, + BuildContext mainContext) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 4), + child: Align( alignment: Alignment.centerLeft, - child: AutoSizeText( - configuration.label!, - style: new TextStyle(fontSize: 25), - maxLines: 1, + child: SizedBox( + width: 280, + child: StringInputContainer( + label: AppLocalizations.of(context)!.searchLabel, + onChanged: (v) => setState(() => _filter = v), + ), ), ), - Align( - alignment: Alignment.bottomRight, - child: AutoSizeText( - DateFormat('dd/MM/yyyy').format(configuration.dateCreation!), - style: new TextStyle(fontSize: 18, fontFamily: ""), - maxLines: 1, + ), + Expanded( + child: GridView.builder( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 220, + childAspectRatio: 1.5, + mainAxisSpacing: 10, + crossAxisSpacing: 10, ), + itemCount: data.length, + itemBuilder: (BuildContext context, int index) { + return _ConfigCard( + config: data[index], + onTap: () => _onTap(data[index], appContext, mainContext), + ); + }, ), - ], + ), + ], + ); + } + + Future _onTap( + ConfigurationDTO config, AppContext appContext, BuildContext mainContext) async { + if (config.id == null) { + var configurationToCreate = await showNewConfiguration( + appContext, + (bool refresh) { + if (refresh) _refresh(); + }, + context, + mainContext, ); + if (configurationToCreate != null && configurationToCreate.label != null) { + configurationToCreate.dateCreation = DateTime.now(); + configurationToCreate.isOffline = false; + configurationToCreate.instanceId = + (appContext.getContext() as ManagerAppContext).instanceId; + await (appContext.getContext() as ManagerAppContext) + .clientAPI! + .configurationApi! + .configurationCreate(configurationToCreate); + final ctx = appContext.getContext() as ManagerAppContext; + ctx.selectedConfiguration = null; + await appContext.setContext(ctx); + showNotification( + kSuccess, kWhite, AppLocalizations.of(context)!.configCreatedSuccess, context, null); + _refresh(); + } } else { - return Icon( - Icons.add, - color: kTextLightColor, - size: size.height*0.1, - ); + setState(() { + final ctx = appContext.getContext() as ManagerAppContext; + ctx.selectedConfiguration = config; + selectedConfiguration = config; + }); } } } -Future?> getConfigurations(ManagerAppContext managerAppContext) async { - List? configurations = await managerAppContext.clientAPI!.configurationApi!.configurationGet(instanceId: managerAppContext.instanceId); - //print("number of configurations " + configurations.length.toString()); - if(configurations != null) { - configurations.forEach((element) { - //print(element); - }); +class _ConfigCard extends StatefulWidget { + final ConfigurationDTO config; + final VoidCallback onTap; + const _ConfigCard({required this.config, required this.onTap}); + + @override + State<_ConfigCard> createState() => _ConfigCardState(); +} + +class _ConfigCardState extends State<_ConfigCard> { + bool _hovered = false; + + @override + Widget build(BuildContext context) { + final config = widget.config; + final isAdd = config.id == null; + final hasImage = config.imageSource != null; + + return MouseRegion( + onEnter: (_) => setState(() => _hovered = true), + onExit: (_) => setState(() => _hovered = false), + child: GestureDetector( + onTap: widget.onTap, + child: AnimatedScale( + scale: _hovered ? 1.03 : 1.0, + duration: const Duration(milliseconds: 150), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: kSecond, + spreadRadius: _hovered ? 1 : 0.5, + blurRadius: _hovered ? 10 : 5, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Stack( + fit: StackFit.expand, + children: [ + // Background + if (!isAdd && hasImage) + Image.network(config.imageSource!, fit: BoxFit.cover) + else + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: isAdd + ? [kSuccess, kSuccess.withOpacity(0.7)] + : [kPrimaryColor, kPrimaryColor.withOpacity(0.6)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + ), + // Gradient scrim + if (!isAdd) + DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.35, 1.0], + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.65), + ], + ), + ), + ), + // Content + if (isAdd) + const Center( + child: Icon(Icons.add, color: kTextLightColor, size: 36), + ) + else + Positioned( + left: 10, + right: 10, + bottom: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AutoSizeText( + config.label ?? '', + style: kCardTitleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (config.dateCreation != null) + Text( + DateFormat('dd/MM/yyyy').format(config.dateCreation!), + style: kCardSubtitleStyle, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ); } - - return configurations; } -boxDecoration(ConfigurationDTO configurationDTO) { - return BoxDecoration( - color: configurationDTO.id == null ? kSuccess : kTextLightColor, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(25.0), - image: configurationDTO.imageSource != null ? new DecorationImage( - fit: BoxFit.cover, - colorFilter: new ColorFilter.mode(Colors.black.withOpacity(0.18), BlendMode.dstATop), - image: new NetworkImage( - configurationDTO.imageSource!, - ), - ) : null, - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ); +Future?> getConfigurations( + ManagerAppContext managerAppContext) async { + return managerAppContext.clientAPI!.configurationApi! + .configurationGet(instanceId: managerAppContext.instanceId); } - - -/*Widget bodyTable(data) => DataTable( - sortColumnIndex: 0, - sortAscending: true, - columns: [ - DataColumn( - label: Text("Label"), - onSort: (_, __) { - setState(() { - // data.sort((a) => a.label); - }); - }, - ), - DataColumn( - label: Text("Primary color"), - onSort: (_, __) { - setState(() { - /*data.sort((a, b) => a.data["stats"]["dividendYield"] - .compareTo(b.data["stats"]["dividendYield"]));*/ - }); - }, - ), - DataColumn( - label: Text("Secondary color"), - onSort: (_, __) { - setState(() { - /*data.sort((a, b) => a.data["quote"]["iexBidPrice"] - .compareTo(b.data["quote"]["iexBidPrice"]));*/ - }); - }, - ), - DataColumn( - label: Text("Languages"), - onSort: (_, __) { - setState(() { - /*data.sort((a, b) => a.data["stats"]["latestPrice"] - .compareTo(b.data["stats"]["latestPrice"]));*/ - }); - }, - ), - DataColumn( - label: Text("Date creation"), - onSort: (_, __) { - setState(() { - /*data.sort((a, b) => a.data["stats"]["latestPrice"] - .compareTo(b.data["stats"]["latestPrice"]));*/ - }); - }, - ), - DataColumn( - label: Text("Actions"), - onSort: (_, __) { - setState(() { - /*data.sort((a, b) => a.data["stats"]["latestPrice"] - .compareTo(b.data["stats"]["latestPrice"]));*/ - }); - }, - ), - ], - rows: data - .map( (configuration) => DataRow( - cells: [ - DataCell( - Text('${configuration.label ?? ""}'), - ), - DataCell( - Text('${configuration.primaryColor ?? ""}'), - ), - DataCell( - Text('${configuration.secondaryColor ?? ""}'), - ), - DataCell( - Text('${configuration.languages ?? ""}'), - ), - DataCell( - Text('${configuration.dateCreation ?? ""}'), - ), - DataCell( - Text("Todo buttons - open = edit, delete"), - ), - ], - ), - ).toList() - );*/ diff --git a/lib/Screens/Configurations/new_configuration_popup.dart b/lib/Screens/Configurations/new_configuration_popup.dart index 03f3b5a..cef73db 100644 --- a/lib/Screens/Configurations/new_configuration_popup.dart +++ b/lib/Screens/Configurations/new_configuration_popup.dart @@ -14,114 +14,101 @@ import 'package:manager_api_new/api.dart'; Future showNewConfiguration(AppContext appContext, ValueChanged isImport, BuildContext context, BuildContext mainContext) { final l = AppLocalizations.of(mainContext)!; ConfigurationDTO configurationDTO = new ConfigurationDTO(); - Size size = MediaQuery.of(mainContext).size; configurationDTO.label = ""; var configuration = showDialog( - builder: (BuildContext context) => AlertDialog( + builder: (BuildContext context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - content: SingleChildScrollView( - child: SizedBox( - width: size.width*0.35, - height: size.height*0.3, + child: SizedBox( + width: 460, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Center(child: Text(l.newConfiguration, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400))), - Center( - child: SizedBox( - height: 100, - child: StringInputContainer( - label: l.configNameLabel, - initialValue: configurationDTO.label, - onChanged: (value) { - configurationDTO.label = value; - }, + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Center(child: Text(l.newConfiguration, style: new TextStyle(fontSize: 22, fontWeight: FontWeight.w400))), + SizedBox(height: 8), + SizedBox( + height: 90, + child: StringInputContainer( + label: l.configNameLabel, + initialValue: configurationDTO.label, + onChanged: (value) { + configurationDTO.label = value; + }, + ), + ), + SizedBox(height: 8), + Text(l.orText), + SizedBox(height: 4), + Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 5.0), + child: Text(l.importLabel, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w300)), + ), + InkWell( + onTap: () async { + FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result != null) { + await FileHelper().importConfiguration(result, null, (appContext.getContext() as ManagerAppContext).clientAPI!, mainContext); + isImport(true); + Navigator.of(context).pop(); + } + }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Icon(Icons.file_upload, color: kPrimaryColor, size: 30), + ), + ) + ] + ) + ], ), ), ), - Text(l.orText), - Column( + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.end, children: [ - Padding( - padding: const EdgeInsets.only(bottom: 5.0), - child: Text(l.importLabel, style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)), + RoundedButton( + text: l.cancel, + icon: Icons.undo, + color: kSecond, + press: () => Navigator.of(context).pop(), + fontSize: 16, ), - InkWell( - onTap: () async { - FilePickerResult? result = await FilePicker.platform.pickFiles(); - - //String result = filePicker(); - if (result != null) { - - await FileHelper().importConfiguration(result, null, (appContext.getContext() as ManagerAppContext).clientAPI!, mainContext); - isImport(true); - Navigator.of(context).pop(); + RoundedButton( + text: l.create, + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if(configurationDTO.label != null && configurationDTO.label!.length > 2) { + Navigator.of(context).pop(configurationDTO); + } else { + showNotification(Colors.orange, kWhite, l.configNameRequired, context, null); } }, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Icon( - Icons.file_upload, - color: kPrimaryColor, - size: 30 - ), - ), - ) - ] - ) + fontSize: 16, + ), + ], + ), ], ), ), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: l.cancel, - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 150, - height: 70, - child: RoundedButton( - text: l.create, - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - if(configurationDTO.label != null && configurationDTO.label!.length > 2) { - //create(configurationDTO, appContext, context); - Navigator.of(context).pop(configurationDTO); - } else { - showNotification(Colors.orange, kWhite, l.configNameRequired, context, null); - } - }, - fontSize: 20, - ), - ), - ), - ], - ), - ], ), context: context ); @@ -129,38 +116,12 @@ Future showNewConfiguration(AppContext appContext, ValueChang } String filePicker() { - /*final file = OpenFilePicker() - ..filterSpecification = { - 'Fichier (*.json)': '*.json', - //'Video (*.mp4)': '*.mp4', - //'All Files': '*.*' - } - ..defaultFilterIndex = 0 - ..title = 'Sélectionner un fichier'; - - final result = file.getFile();*/ var result; - return result != null ? result.path : null; } void create(ConfigurationDTO configurationDTO, AppContext appContext, context) async { if (configurationDTO.label != null) { - /*configurationDTO.dateCreation = DateTime.now(); - configurationDTO.isMobile = false; - configurationDTO.isTablet = false; - configurationDTO.isOffline = false; - configurationDTO.isDate = false; - configurationDTO.isHour = false; - configurationDTO.isSectionImageBackground = false; - configurationDTO.instanceId = (appContext.getContext() as ManagerAppContext).instanceId; - await (appContext.getContext() as ManagerAppContext).clientAPI!.configurationApi!.configurationCreate(configurationDTO); - ManagerAppContext managerAppContext = appContext.getContext(); - managerAppContext.selectedConfiguration = null; - await appContext.setContext(managerAppContext); - - showNotification(Colors.green, kWhite, 'La configuration a été créée avec succès', context, null);*/ - Navigator.of(context).pop(configurationDTO); } -} \ No newline at end of file +} diff --git a/lib/Screens/Configurations/new_section_popup.dart b/lib/Screens/Configurations/new_section_popup.dart index ba0730a..30cad82 100644 --- a/lib/Screens/Configurations/new_section_popup.dart +++ b/lib/Screens/Configurations/new_section_popup.dart @@ -23,118 +23,130 @@ Future showNewSection(String configurationId, sectionDTO.type = SectionType.Map; var section = showDialog( - builder: (BuildContext context) => AlertDialog( + builder: (BuildContext context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0))), - content: SingleChildScrollView( - child: SizedBox( - width: 750, - height: 250, - child: Container( - constraints: BoxConstraints(minHeight: 300, minWidth: 300), - child: Column( - children: [ - Text( - isSubSection - ? l.newSubSection - : l.newSection, - style: new TextStyle( - fontSize: 25, fontWeight: FontWeight.w400)), - Column( - children: [ - SizedBox( - height: 100, - child: StringInputContainer( - label: l.sectionNameLabel, - initialValue: sectionDTO.label, - onChanged: (value) { - sectionDTO.label = value; - }, + child: SizedBox( + width: 780, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: SingleChildScrollView( + child: SizedBox( + height: 250, + child: Container( + constraints: BoxConstraints(minHeight: 300, minWidth: 300), + child: Column( + children: [ + Text( + isSubSection + ? l.newSubSection + : l.newSection, + style: new TextStyle( + fontSize: 25, fontWeight: FontWeight.w400)), + Column( + children: [ + SizedBox( + height: 100, + child: StringInputContainer( + label: l.sectionNameLabel, + initialValue: sectionDTO.label, + onChanged: (value) { + sectionDTO.label = value; + }, + ), + ), + DropDownInputContainer( + label: l.sectionTypeLabel, + values: isSubSection + ? section_types + .where( + (sectionType) => sectionType != "Menu") + .toList() + : section_types + .toList(), // Todo get menu by enum type + initialValue: "Map", + onChange: (String? value) { + sectionDTO.type = SectionType.fromJson(value); + }, + ), + /*MultiSelectContainer( + label: "Type:", + initialValue: isMobile ? ["Article"] : ["Map"], + isMultiple: false, + values: isMobile ? section_types.where((sectionType) => sectionType == "Article" || sectionType == "Quizz").toList(): isSubSection ? section_types.where((sectionType) => sectionType != "Menu" && sectionType != "Article").toList(): section_types.where((sectionType) => sectionType != "Article").toList(), // Todo get menu by enum type + onChanged: (value) { + var tempOutput = new List.from(value); + sectionDTO.type = SectionType.fromJson(tempOutput[0]); + }, + ),*/ + ], + ), + ], ), ), - DropDownInputContainer( - label: l.sectionTypeLabel, - values: isSubSection - ? section_types - .where( - (sectionType) => sectionType != "Menu") - .toList() - : section_types - .toList(), // Todo get menu by enum type - initialValue: "Map", - onChange: (String? value) { - sectionDTO.type = SectionType.fromJson(value); - }, - ), - /*MultiSelectContainer( - label: "Type:", - initialValue: isMobile ? ["Article"] : ["Map"], - isMultiple: false, - values: isMobile ? section_types.where((sectionType) => sectionType == "Article" || sectionType == "Quizz").toList(): isSubSection ? section_types.where((sectionType) => sectionType != "Menu" && sectionType != "Article").toList(): section_types.where((sectionType) => sectionType != "Article").toList(), // Todo get menu by enum type - onChanged: (value) { - var tempOutput = new List.from(value); - sectionDTO.type = SectionType.fromJson(tempOutput[0]); - }, - ),*/ - ], + ), ), - ], - ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 175, + height: 70, + child: RoundedButton( + text: l.cancel, + icon: Icons.undo, + color: kSecond, + press: () { + Navigator.of(context).pop(); + }, + fontSize: 20, + ), + ), + ), + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 150, + height: 70, + child: RoundedButton( + text: l.create, + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if (sectionDTO.label != null && + sectionDTO.label!.length > 2) { + //onYes(); + Navigator.of(context).pop(sectionDTO); + //create(sectionDTO, appContext, context, isSubSection, sendSubSection); + } else { + showNotification( + Colors.orange, + kWhite, + l.sectionNameRequired, + context, + null); + } + }, + fontSize: 20, + ), + ), + ), + ], + ), + ], ), ), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: l.cancel, - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 150, - height: 70, - child: RoundedButton( - text: l.create, - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - if (sectionDTO.label != null && - sectionDTO.label!.length > 2) { - //onYes(); - Navigator.of(context).pop(sectionDTO); - //create(sectionDTO, appContext, context, isSubSection, sendSubSection); - } else { - showNotification( - Colors.orange, - kWhite, - l.sectionNameRequired, - context, - null); - } - }, - fontSize: 20, - ), - ), - ), - ], - ), - ], ), context: contextBuild); diff --git a/lib/Screens/Configurations/section_reorderList.dart b/lib/Screens/Configurations/section_reorderList.dart index 51220cc..58872b1 100644 --- a/lib/Screens/Configurations/section_reorderList.dart +++ b/lib/Screens/Configurations/section_reorderList.dart @@ -62,130 +62,92 @@ class _SectionReorderListState extends State { final appContext = Provider.of(context); Size size = MediaQuery.of(context).size; - ManagerAppContext managerAppContext = appContext.getContext(); - ConfigurationDTO currentConfiguration = managerAppContext.selectedConfiguration!; - - return Stack( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0, top: 15.0), - child: Container( - height: size.height*0.3, - child: ReorderableListView.builder( - itemCount: sections.length, - onReorder: _onReorder, - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(vertical: 20.0), - itemBuilder: (context, index) { - //final String productName = _products[index]; - return ListViewCardSections( - sections, - index, - Key('$index'), - false, - //currentConfiguration.isMobile!, - appContext, - (section) { - setState(() { - ManagerAppContext managerAppContext = appContext.getContext(); - managerAppContext.selectedSection = section; - appContext.setContext(managerAppContext); - }); - } - ); - }, - /*children: List.generate( - sections.length, - (index) { - return ListViewCardSections( - sections, - index, - Key('$index'), - appContext, - (section) { - setState(() { - ManagerAppContext managerAppContext = appContext.getContext(); - managerAppContext.selectedSection = section; - appContext.setContext(managerAppContext); - }); - } - ); + Row( + children: [ + Expanded( + child: StringInputContainer( + label: "Recherche:", + isSmall: true, + fontSize: 15, + fontSizeText: 15, + onChanged: (String value) { + setState(() { + filterSearch = value; + filterResource(); + }); }, - ),*/ + ), ), - ), - ), - Positioned( - top: 10, - left: 10, - child: Text( - "Sections", - style: TextStyle(fontSize: 15), - ), - ), - Positioned( - bottom: -20, - left: 10, - child: Container( - height: size.height*0.1, - constraints: BoxConstraints(minHeight: 85), - child: StringInputContainer( - label: "Recherche:", - isSmall: true, - fontSize: 15, - fontSizeText: 15, - onChanged: (String value) { - setState(() { - filterSearch = value; - filterResource(); - }); + const SizedBox(width: 8), + InkWell( + onTap: () async { + var sectionToCreate = await showNewSection(widget.configurationId, appContext, context, false); + if (sectionToCreate != null) { + ManagerAppContext managerAppContext = appContext.getContext(); + sectionToCreate.instanceId = managerAppContext.instanceId; + sectionToCreate.isBeacon = false; + sectionToCreate.dateCreation = DateTime.now(); + await managerAppContext.clientAPI!.sectionApi!.sectionCreate(sectionToCreate); + showNotification(kSuccess, kWhite, 'La section a été créée avec succès !', context, null); + widget.askReload(); + } }, + child: Container( + height: 36, + width: 36, + decoration: BoxDecoration( + color: kSuccess, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(20.0), + boxShadow: [ + BoxShadow( + color: kSecond, + spreadRadius: 0.5, + blurRadius: 5, + offset: Offset(0, 1.5), + ), + ], + ), + child: const Icon( + Icons.add, + color: kTextLightColor, + size: 20.0, + ), + ), ), + ], + ), + const SizedBox(height: 8), + SizedBox( + height: size.height * 0.3, + child: ReorderableListView.builder( + itemCount: sections.length, + onReorder: _onReorder, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(vertical: 8.0), + itemBuilder: (context, index) { + return ListViewCardSections( + sections, + index, + Key('$index'), + false, + appContext, + (section) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + ManagerAppContext managerAppContext = appContext.getContext(); + managerAppContext.selectedSection = section; + appContext.setContext(managerAppContext); + }); + }); + }, + ); + }, ), ), - Positioned( - top: 10, - right: 10, - child: InkWell( - onTap: () async { - var sectionToCreate = await showNewSection(widget.configurationId, appContext, context, false); - if(sectionToCreate != null) - { - ManagerAppContext managerAppContext = appContext.getContext(); - sectionToCreate.instanceId = managerAppContext.instanceId; - sectionToCreate.isBeacon = false; - sectionToCreate.dateCreation = DateTime.now(); - SectionDTO? newSection = await managerAppContext.clientAPI!.sectionApi!.sectionCreate(sectionToCreate); - - showNotification(kSuccess, kWhite, 'La section a été créée avec succès !', context, null); - - widget.askReload(); - } - }, - child: Container( - height: MediaQuery.of(context).size.width * 0.04, - width: MediaQuery.of(context).size.width * 0.04, - child: Icon( - Icons.add, - color: kTextLightColor, - size: 30.0, - ), - decoration: BoxDecoration( - color: kSuccess, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(20.0), - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ), - ), - ), - ) ], ); } diff --git a/lib/Screens/Kiosk_devices/change_device_info_modal.dart b/lib/Screens/Kiosk_devices/change_device_info_modal.dart index d9b7b3a..b346e5b 100644 --- a/lib/Screens/Kiosk_devices/change_device_info_modal.dart +++ b/lib/Screens/Kiosk_devices/change_device_info_modal.dart @@ -15,236 +15,221 @@ import 'package:manager_api_new/api.dart'; import '../../Components/resource_input_container.dart'; import 'dropDown_configuration.dart'; -showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appConfiguration, Function onGetResult, int maxLines, BuildContext mainContext, dynamic appContext) async { - Size size = MediaQuery.of(mainContext).size; - +showChangeInfo(String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appConfiguration, Function onGetResult, int maxLines, BuildContext mainContext, dynamic appContext) async { var result = await showDialog( - builder: (BuildContext context) => AlertDialog( + useRootNavigator: false, + builder: (BuildContext context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - title: Center(child: Text(text)), - content: Container( - width: size.width * 0.5, - height: size.height * 0.5, - constraints: BoxConstraints(minWidth: 400, minHeight: 500), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - width: size.width * 0.3, - height: size.height * 0.15, - child: StringInputContainer( - label: "Nom:", - initialValue: inputDevice.name, - onChanged: (value) { - inputDevice.name = value; - }, - maxLength: 20, - ), - ), - Row( - children: [ - Align( - alignment: AlignmentDirectional.centerStart, - child: Text("Configuration:", style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)) - ), - Container( - height: size.height * 0.1, - child: FutureBuilder( - future: getConfigurations(appContext), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data.length > 0) { - return Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: DropDownConfig( + child: SizedBox( + width: 580, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center(child: Text(text, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))), + const SizedBox(height: 16), + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Nom + Configuration + Wrap( + spacing: 16, + runSpacing: 12, + children: [ + SizedBox( + width: 220, + height: 90, + child: StringInputContainer( + label: "Nom:", + initialValue: inputDevice.name, + onChanged: (value) => inputDevice.name = value, + maxLength: 20, + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Configuration:", style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400)), + const SizedBox(width: 8), + FutureBuilder( + future: getConfigurations(appContext), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data != null && snapshot.data.length > 0) { + return DropDownConfig( configurations: snapshot.data, selectedConfigurationId: inputDevice.configurationId!, onChange: (ConfigurationDTO configurationOut) { inputDevice.configuration = configurationOut.label; inputDevice.configurationId = configurationOut.id; }, - ), - ), - ], - ); - } else { - return Text(AppLocalizations.of(context)!.noConfigFound); - } - - } else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); - } else { - return Center( - child: Container( - child: Text("Loading..") - ) - ); - } - } + ); + } else { + return Text(AppLocalizations.of(context)!.noConfigFound); + } + } else if (snapshot.connectionState == ConnectionState.none) { + return const Text("No data"); + } else { + return const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2)); + } + } + ), + ], + ), + ], ), - ), - ], - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ColorPickerInputContainer( - label: "Couleur principale :", - fontSize: 20, - color: appConfiguration.primaryColor, - onChanged: (value) { - appConfiguration.primaryColor = value; - }, - ), - ColorPickerInputContainer( - label: AppLocalizations.of(context)!.backgroundColorLabel, - fontSize: 20, - color: appConfiguration.secondaryColor, - onChanged: (value) { - appConfiguration.secondaryColor = value; - }, - ), - ResourceInputContainer( - label: "Loader :", - initialValue: appConfiguration.loaderImageId, - color: kPrimaryColor, - imageFit: BoxFit.fitHeight, - onChanged: (ResourceDTO resource) async { - if(resource.id == null) { - appConfiguration.loaderImageId = null; - appConfiguration.loaderImageUrl = null; - } else { - appConfiguration.loaderImageId = resource.id; - appConfiguration.loaderImageUrl = resource.url; - } - }, - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - height: 100, - child: NumberInputContainer( - label: "Place des sections (%) :", - initialValue: appConfiguration.screenPercentageSectionsMainPage ?? 0, - isSmall: true, - maxLength: 3, - onChanged: (value) { - try { - appConfiguration.screenPercentageSectionsMainPage = int.parse(value); - } catch (e) { - print('Screen percentage value not a number'); - showNotification(Colors.orange, kWhite, 'Cela doit être un chiffre', context, null); - } - }, + const SizedBox(height: 12), + // Couleurs + Loader + Wrap( + spacing: 16, + runSpacing: 12, + children: [ + ColorPickerInputContainer( + label: "Couleur principale :", + fontSize: 16, + color: appConfiguration.primaryColor, + onChanged: (value) => appConfiguration.primaryColor = value, + ), + ColorPickerInputContainer( + label: AppLocalizations.of(context)!.backgroundColorLabel, + fontSize: 16, + color: appConfiguration.secondaryColor, + onChanged: (value) => appConfiguration.secondaryColor = value, + ), + ResourceInputContainer( + label: "Loader :", + initialValue: appConfiguration.loaderImageId, + color: kPrimaryColor, + imageFit: BoxFit.fitHeight, + onChanged: (ResourceDTO resource) async { + if (resource.id == null) { + appConfiguration.loaderImageId = null; + appConfiguration.loaderImageUrl = null; + } else { + appConfiguration.loaderImageId = resource.id; + appConfiguration.loaderImageUrl = resource.url; + } + }, + ), + ], + ), + const SizedBox(height: 12), + // Pourcentages + Wrap( + spacing: 16, + runSpacing: 12, + children: [ + SizedBox( + height: 90, + child: NumberInputContainer( + label: "Place des sections (%) :", + initialValue: appConfiguration.screenPercentageSectionsMainPage ?? 0, + isSmall: true, + maxLength: 3, + onChanged: (value) { + try { + appConfiguration.screenPercentageSectionsMainPage = int.parse(value); + } catch (e) { + showNotification(Colors.orange, kWhite, 'Cela doit être un chiffre', context, null); + } + }, + ), + ), + SizedBox( + height: 90, + child: NumberInputContainer( + label: "Pourcentage des arrondis (0-50) :", + initialValue: appConfiguration.roundedValue ?? 0, + isSmall: true, + maxLength: 2, + onChanged: (value) { + try { + appConfiguration.roundedValue = int.parse(value); + } catch (e) { + showNotification(Colors.orange, kWhite, 'Cela doit être un chiffre', context, null); + } + }, + ), + ), + ], + ), + const SizedBox(height: 12), + // Toggles + Wrap( + spacing: 16, + runSpacing: 8, + children: [ + CheckInputContainer( + icon: Icons.image, + label: "Fond pour les images des sections :", + fontSize: 16, + isChecked: appConfiguration.isSectionImageBackground, + onChanged: (value) => appConfiguration.isSectionImageBackground = value, + ), + CheckInputContainer( + icon: Icons.watch_later_outlined, + label: "Heure :", + fontSize: 16, + isChecked: appConfiguration.isHour, + onChanged: (value) => appConfiguration.isHour = value, + ), + CheckInputContainer( + icon: Icons.date_range, + label: "Date :", + fontSize: 16, + isChecked: appConfiguration.isDate, + onChanged: (value) => appConfiguration.isDate = value, + ), + ], + ), + ], ), ), - Container( - height: 100, - child: NumberInputContainer( - label: "Pourcentage des arrondis (0-50) :", - initialValue: appConfiguration.roundedValue ?? 0, - isSmall: true, - maxLength: 2, - onChanged: (value) { - try { - appConfiguration.roundedValue = int.parse(value); - } catch (e) { - print('Rounded value not a number'); - showNotification(Colors.orange, kWhite, 'Cela doit être un chiffre', context, null); - } - }, + ), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.end, + children: [ + RoundedButton( + text: "Annuler", + icon: Icons.undo, + color: kSecond, + press: () => Navigator.of(context).pop(), + fontSize: 16, ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - CheckInputContainer( - icon: Icons.image, - label: "Fond pour les images des sections :", - fontSize: 20, - isChecked: appConfiguration.isSectionImageBackground, - onChanged: (value) { - appConfiguration.isSectionImageBackground = value; - }, - ), - CheckInputContainer( - icon: Icons.watch_later_outlined, - label: "Heure :", - fontSize: 20, - isChecked: appConfiguration.isHour, - onChanged: (value) { - appConfiguration.isHour = value; - }, - ), - CheckInputContainer( - icon: Icons.date_range, - label: "Date :", - fontSize: 20, - isChecked: appConfiguration.isDate, - onChanged: (value) { - appConfiguration.isDate = value; - }, - ), - ] - ), - ], + RoundedButton( + text: "Changer", + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + onGetResult(inputDevice, appConfiguration); + Navigator.of(context).pop(); + }, + fontSize: 16, + ), + ], + ), + ], + ), ), ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - width: 180, - height: 70, - child: RoundedButton( - text: "Annuler", - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - Container( - width: 180, - height: 70, - child: RoundedButton( - text: "Changer", - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - onGetResult(inputDevice, appConfiguration); - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ], - ), - ], ), context: mainContext ); } getConfigurationsElement(DeviceDTO inputDevice, data, Function onGetResult) { List widgets = []; - for(var configuration in data as List) { + for (var configuration in data as List) { var widget = new InkWell( onTap: () { inputDevice.configuration = configuration.label; @@ -264,15 +249,8 @@ getConfigurationsElement(DeviceDTO inputDevice, data, Function onGetResult) { } Future?> getConfigurations(AppContext appContext) async { - List? configurations = await (appContext.getContext() as ManagerAppContext).clientAPI!.configurationApi!.configurationGet(instanceId: (appContext.getContext() as ManagerAppContext).instanceId); - //print("number of configurations " + configurations.length.toString()); - - if(configurations != null) { - configurations.forEach((element) { - //print(element); - }); - } - - return configurations; + return (appContext.getContext() as ManagerAppContext) + .clientAPI! + .configurationApi! + .configurationGet(instanceId: (appContext.getContext() as ManagerAppContext).instanceId); } - diff --git a/lib/Screens/Kiosk_devices/device_element.dart b/lib/Screens/Kiosk_devices/device_element.dart index b0b72dd..3cebd96 100644 --- a/lib/Screens/Kiosk_devices/device_element.dart +++ b/lib/Screens/Kiosk_devices/device_element.dart @@ -66,7 +66,7 @@ class _DeviceElementState extends State { children: [ AutoSizeText( widget.deviceDTO.name != null ? widget.deviceDTO.name! : widget.deviceDTO.identifier!, - style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w300), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400), maxLines: 1, ), ], @@ -75,7 +75,7 @@ class _DeviceElementState extends State { Center( child: Text( updatedDevice?.configuration != null ? updatedDevice?.configuration! ?? "Aucune configuration" : "Aucune configuration", - style: new TextStyle(fontSize: 20), + style: const TextStyle(fontSize: 13), maxLines: 1, ), ), diff --git a/lib/Screens/Kiosk_devices/kiosk_screen.dart b/lib/Screens/Kiosk_devices/kiosk_screen.dart index 17be9e1..f0f5608 100644 --- a/lib/Screens/Kiosk_devices/kiosk_screen.dart +++ b/lib/Screens/Kiosk_devices/kiosk_screen.dart @@ -60,23 +60,18 @@ class _KioskScreenState extends State { List? appConfigurationLinks = snapshot.data; if (snapshot.connectionState == ConnectionState.done && appConfigurationLinks != null) { - final screenWidth = MediaQuery.of(context).size.width; - final itemWidth = 175; - final crossAxisCount = (screenWidth / itemWidth).floor().clamp(1, 6); - return GridView.builder( shrinkWrap: true, - gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 220, + childAspectRatio: 1.5, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), itemCount: appConfigurationLinks.length, itemBuilder: (BuildContext context, int index) { - return Container( - decoration: boxDecoration(), - padding: const EdgeInsets.all(10), - margin: EdgeInsets.symmetric(vertical: 10, horizontal: 10), - child: Align( - alignment: Alignment.center, - child: DeviceElement(deviceDTO: appConfigurationLinks[index].device!, appConfigurationLinkDTO: appConfigurationLinks[index]), //getElement() - ), + return _DeviceCard( + deviceLink: appConfigurationLinks[index], ); } ); @@ -243,20 +238,47 @@ class _KioskScreenState extends State { }*/ } -boxDecoration() { - return BoxDecoration( - color: kTextLightColor, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(25.0), - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow +class _DeviceCard extends StatefulWidget { + final AppConfigurationLinkDTO deviceLink; + const _DeviceCard({required this.deviceLink}); + + @override + State<_DeviceCard> createState() => _DeviceCardState(); +} + +class _DeviceCardState extends State<_DeviceCard> { + bool _hovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => _hovered = true), + onExit: (_) => setState(() => _hovered = false), + child: AnimatedScale( + scale: _hovered ? 1.03 : 1.0, + duration: const Duration(milliseconds: 150), + child: Container( + decoration: BoxDecoration( + color: kTextLightColor, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: kSecond, + spreadRadius: _hovered ? 1 : 0.5, + blurRadius: _hovered ? 10 : 5, + offset: const Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.all(10), + child: DeviceElement( + deviceDTO: widget.deviceLink.device!, + appConfigurationLinkDTO: widget.deviceLink, + ), + ), ), - ], - ); + ); + } } Future?> getDevices(AppContext appContext) async { diff --git a/lib/Screens/Main/main_screen.dart b/lib/Screens/Main/main_screen.dart index e3755a4..2a334c5 100644 --- a/lib/Screens/Main/main_screen.dart +++ b/lib/Screens/Main/main_screen.dart @@ -14,6 +14,7 @@ import 'package:manager_app/Screens/Kiosk_devices/kiosk_screen.dart'; import 'package:manager_app/Screens/Resources/resources_screen.dart'; import 'package:manager_app/Screens/Statistics/statistics_screen.dart'; import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart'; +import 'package:manager_app/Screens/Applications/web_app_screen.dart'; import 'package:manager_app/Screens/Notifications/notifications_screen.dart'; import 'package:manager_app/Screens/Users/users_screen.dart'; import 'package:manager_app/l10n/app_localizations.dart'; @@ -430,47 +431,54 @@ class _MainScreenState extends State { child: Column( children: [ const QuotaBarsWidget(), - DropdownButton( - value: managerAppContext.locale, - underline: const SizedBox(), - isDense: true, - items: const [ - DropdownMenuItem(value: Locale('fr'), child: Text('🇫🇷 FR')), - DropdownMenuItem(value: Locale('en'), child: Text('🇬🇧 EN')), - DropdownMenuItem(value: Locale('nl'), child: Text('🇧🇪 NL')), - ], - onChanged: (locale) { - if (locale != null) appContext.setLocale(locale); - }, - ), - const SizedBox(height: 4), - AutoSizeText( - (appContext.getContext() as ManagerAppContext).email ?? "", - style: TextStyle(color: kBodyTextColor, fontSize: 16, fontWeight: FontWeight.w300, fontFamily: "Helvetica"), - maxLines: 1, - ), - if ((appContext.getContext() as ManagerAppContext).role == UserRole.SuperAdmin) - IconButton( - icon: Icon(Icons.swap_horiz, color: kPrimaryColor), - tooltip: AppLocalizations.of(context)!.tooltipSwitchInstance, - onPressed: () => _showSwitchInstanceDialog(context, appContext.getContext() as ManagerAppContext), - ), - IconButton( - icon: Icon(Icons.logout, color: kPrimaryColor), - onPressed: () async { - var session = await loadJsonSessionFile(); - setState(() { - Storage localStorage = window.localStorage; - localStorage.clear(); - ManagerAppContext managerAppContext = appContext.getContext(); - managerAppContext.accessToken = null; - managerAppContext.instanceId = null; - managerAppContext.instanceDTO = null; - appContext.setContext(managerAppContext); + Row( + children: [ + DropdownButton( + value: managerAppContext.locale, + underline: const SizedBox(), + isDense: true, + items: const [ + DropdownMenuItem(value: Locale('fr'), child: Text('🇫🇷 FR')), + DropdownMenuItem(value: Locale('en'), child: Text('🇬🇧 EN')), + DropdownMenuItem(value: Locale('nl'), child: Text('🇧🇪 NL')), + ], + onChanged: (locale) { + if (locale != null) appContext.setLocale(locale); + }, + ), + const SizedBox(width: 8), + Expanded( + child: AutoSizeText( + (appContext.getContext() as ManagerAppContext).email ?? "", + style: TextStyle(color: kBodyTextColor, fontSize: 14, fontWeight: FontWeight.w300, fontFamily: "Helvetica"), + maxLines: 1, + ), + ), + if ((appContext.getContext() as ManagerAppContext).role == UserRole.SuperAdmin) + IconButton( + icon: Icon(Icons.swap_horiz, color: kPrimaryColor), + tooltip: AppLocalizations.of(context)!.tooltipSwitchInstance, + onPressed: () => _showSwitchInstanceDialog(context, appContext.getContext() as ManagerAppContext), + ), + IconButton( + icon: Icon(Icons.logout, color: kPrimaryColor), + tooltip: AppLocalizations.of(context)!.tooltipLogout, + onPressed: () async { + await loadJsonSessionFile(); + setState(() { + Storage localStorage = window.localStorage; + localStorage.clear(); + ManagerAppContext managerAppContext = appContext.getContext(); + managerAppContext.accessToken = null; + managerAppContext.instanceId = null; + managerAppContext.instanceDTO = null; + appContext.setContext(managerAppContext); - context.go('/login'); - }); - }, + context.go('/login'); + }); + }, + ), + ], ) ], ), @@ -597,25 +605,24 @@ class _MainScreenState extends State { switch (elementToShow.type) { case 'mobile' : - var applicationInstanceMobile = instanceDTO.applicationInstanceDTOs!.firstWhere((ai) => ai.appType == AppType.Mobile); + var applicationInstanceMobile = instanceDTO.applicationInstanceDTOs?.firstWhereOrNull((ai) => ai.appType == AppType.Mobile); + if (applicationInstanceMobile == null) return const Center(child: Text('Application mobile non configurée')); return Padding( padding: const EdgeInsets.all(8.0), child: AppConfigurationLinkScreen(applicationInstanceDTO: applicationInstanceMobile) ); case 'kiosk' : - var applicationInstanceTablet = instanceDTO.applicationInstanceDTOs!.firstWhere((ai) => ai.appType == AppType.Tablet); + var applicationInstanceTablet = instanceDTO.applicationInstanceDTOs?.firstWhereOrNull((ai) => ai.appType == AppType.Tablet); + if (applicationInstanceTablet == null) return const Center(child: Text('Application kiosk non configurée')); return Padding( padding: const EdgeInsets.all(8.0), child: KioskScreen(applicationInstanceDTO: applicationInstanceTablet) ); case 'web' : - var applicationInstanceWeb = instanceDTO.applicationInstanceDTOs!.firstWhere((ai) => ai.appType == AppType.Web); - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text("TODO web") - ); + return const WebInstanceLoader(); case 'vr' : - var applicationInstanceVR = instanceDTO.applicationInstanceDTOs!.firstWhere((ai) => ai.appType == AppType.VR); + var applicationInstanceVR = instanceDTO.applicationInstanceDTOs?.firstWhereOrNull((ai) => ai.appType == AppType.VR); + if (applicationInstanceVR == null) return const Center(child: Text('Application VR non configurée')); return Padding( padding: const EdgeInsets.all(8.0), child: Text("TODO vr") diff --git a/lib/Screens/Notifications/notifications_screen.dart b/lib/Screens/Notifications/notifications_screen.dart index 97527e9..03d2448 100644 --- a/lib/Screens/Notifications/notifications_screen.dart +++ b/lib/Screens/Notifications/notifications_screen.dart @@ -275,7 +275,9 @@ class _NotificationsScreenState extends State { ], ), const SizedBox(height: 16), - Row( + Wrap( + spacing: 12, + runSpacing: 12, children: [ _KpiCard( label: l.allNotifications, @@ -284,7 +286,6 @@ class _NotificationsScreenState extends State { selected: _statusFilter == null, onTap: () => _setFilter(null), ), - const SizedBox(width: 12), _KpiCard( label: l.sentNotifications, count: _sentCount, @@ -292,7 +293,6 @@ class _NotificationsScreenState extends State { selected: _statusFilter == 'Sent', onTap: () => _setFilter('Sent'), ), - const SizedBox(width: 12), _KpiCard( label: l.scheduledNotifications, count: _scheduledCount, @@ -300,7 +300,6 @@ class _NotificationsScreenState extends State { selected: _statusFilter == 'Scheduled', onTap: () => _setFilter('Scheduled'), ), - const SizedBox(width: 12), _KpiCard( label: l.failedNotifications, count: _failedCount, diff --git a/lib/Screens/Resources/new_resource_popup.dart b/lib/Screens/Resources/new_resource_popup.dart index b39272b..57147da 100644 --- a/lib/Screens/Resources/new_resource_popup.dart +++ b/lib/Screens/Resources/new_resource_popup.dart @@ -13,112 +13,89 @@ import 'package:manager_api_new/api.dart'; dynamic showNewResource(AppContext appContext, BuildContext context) async { ResourceDTO resourceDetailDTO = new ResourceDTO(); - Size size = MediaQuery.of(context).size; - var fileName; List? filesToSend; List? filesToSendWeb; var result = await showDialog( - builder: (BuildContext context) => AlertDialog( + builder: (BuildContext context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - content: Container( - width: size.width *0.5, - child: SingleChildScrollView( + child: SizedBox( + width: 560, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppLocalizations.of(context)!.newResource, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), - /*Column( - children: [ - StringInputContainer( - label: "Nom :", - initialValue: resourceDetailDTO.label, - onChanged: (value) { - resourceDetailDTO.label = value; - }, + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context)!.newResource, + style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w400), + ), + const SizedBox(height: 10), + SizedBox( + height: 400, + child: ResourceTab( + resourceDTO: resourceDetailDTO, + onFileUpload: (List files) { + filesToSend = files; + }, + onFileUploadWeb: (List files) { + filesToSendWeb = files; + }, + ), + ), + ], ), - if (fileName != null) new Text(fileName), - ], - ),*/ - Padding( - padding: const EdgeInsets.only(top: 10.0), - child: Container( - width: size.width *0.85, - height: size.height *0.5, - child: ResourceTab( - resourceDTO: resourceDetailDTO, - onFileUpload: (List files) { - filesToSend = files; - }, - onFileUploadWeb: (List files) { - filesToSendWeb = files; - }, - ) ), ), - ], - ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: AppLocalizations.of(context)!.cancel, - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 150, - height: 70, - child: RoundedButton( - text: AppLocalizations.of(context)!.create, - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - //if (resourceDetailDTO.label != null && resourceDetailDTO.label!.trim() != '') { - if(kIsWeb) { - if(resourceDetailDTO.url != null || filesToSendWeb != null) { // TODO clarify resourceDetailDTO.data != null + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.end, + children: [ + RoundedButton( + text: AppLocalizations.of(context)!.cancel, + icon: Icons.undo, + color: kSecond, + press: () => Navigator.of(context).pop(), + fontSize: 16, + ), + RoundedButton( + text: AppLocalizations.of(context)!.create, + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if (kIsWeb) { + if (resourceDetailDTO.url != null || filesToSendWeb != null) { Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]); } else { showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.resourceNoFileLoaded, context, null); } } else { - if (resourceDetailDTO.url != null || filesToSendWeb!.length > 0 || filesToSend!.length > 0) { // TODO clarify resourceDetailDTO.data != null + if (resourceDetailDTO.url != null || filesToSendWeb!.length > 0 || filesToSend!.length > 0) { Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]); } else { showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.resourceNoFileLoaded, context, null); } } - /*} else { - showNotification(Colors.orange, kWhite, 'Veuillez donner un nom à la ressource', context, null); - }*/ - //Navigator.of(context).pop(); - //create(resourceDetailDTO, fileToSend, appContext, context); - }, - fontSize: 20, - ), + }, + fontSize: 16, + ), + ], ), - ), - ], + ], + ), ), - ], + ), ), context: context ); diff --git a/lib/Screens/Resources/resource_body_grid.dart b/lib/Screens/Resources/resource_body_grid.dart index 8e25536..4cb1603 100644 --- a/lib/Screens/Resources/resource_body_grid.dart +++ b/lib/Screens/Resources/resource_body_grid.dart @@ -11,7 +11,7 @@ import 'package:provider/provider.dart'; import 'package:diacritic/diacritic.dart'; class ResourceBodyGrid extends StatefulWidget { - final List resources; //return ResourceDTO + final List resources; final Function onSelect; final bool isAddButton; final bool isSelectModal; @@ -22,7 +22,7 @@ class ResourceBodyGrid extends StatefulWidget { required this.onSelect, required this.isAddButton, required this.resourceTypesIn, - this.isSelectModal = false + this.isSelectModal = false, }) : super(key: key); @override @@ -39,34 +39,42 @@ class _ResourceBodyGridState extends State { @override void initState() { resourcesToShow = widget.resources; - currentFilterTypes = resource_types.where((rt) => widget.resourceTypesIn.contains(rt.type)).map((rt) => rt.label).toList();//, resource_types[2]]; // resource_types[3] - filterTypes = resource_types.where((rt) => widget.resourceTypesIn.contains(rt.type)).map((rt) => rt.label).toList();//, resource_types[2]]; // resource_types[3] - selectedTypes = resource_types.where((rt) => widget.resourceTypesIn.contains(rt.type)).map((rt) => rt.label).toList(); + currentFilterTypes = resource_types + .where((rt) => widget.resourceTypesIn.contains(rt.type)) + .map((rt) => rt.label) + .toList(); + filterTypes = resource_types + .where((rt) => widget.resourceTypesIn.contains(rt.type)) + .map((rt) => rt.label) + .toList(); + selectedTypes = resource_types + .where((rt) => widget.resourceTypesIn.contains(rt.type)) + .map((rt) => rt.label) + .toList(); super.initState(); } @override Widget build(BuildContext context) { final appContext = Provider.of(context); - Size size = MediaQuery.of(context).size; - - return bodyGrid(resourcesToShow, size, appContext); + return bodyGrid(resourcesToShow, appContext); } - Widget bodyGrid(data, Size size, AppContext appContext) { - final screenWidth = MediaQuery.of(context).size.width; - final itemWidth = 150; - final crossAxisCount = (screenWidth / itemWidth).floor().clamp(1, 9); + Widget bodyGrid(List data, AppContext appContext) { + final canEdit = (appContext.getContext() as ManagerAppContext).canEdit; return Column( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: StringInputContainer( - label: "Recherche:", + // Header: search + filter + add button — Wrap pour mobile + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 4), + child: Wrap( + spacing: 8, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + StringInputContainer( + label: 'Recherche:', onChanged: (String value) { setState(() { filterSearch = value; @@ -74,156 +82,214 @@ class _ResourceBodyGridState extends State { }); }, ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: MultiSelectContainer( - label: "Type:", + MultiSelectContainer( + label: 'Type:', initialValue: filterTypes, values: currentFilterTypes, isMultiple: true, onChanged: (result) { setState(() { - selectedTypes = result as List; // TO TEST + selectedTypes = result as List; filterResource(); }); }, ), - ), - if (widget.isAddButton && (appContext.getContext() as ManagerAppContext).canEdit) - InkWell( - onTap: () { - widget.onSelect(ResourceDTO(id: widget.isSelectModal ? "-1" : null)); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - height: size.height *0.08, - width: size.height *0.08, - decoration: BoxDecoration( - color: kSuccess, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(25.0), - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ), - child: - Padding( - padding: const EdgeInsets.all(10.0), - child: LayoutBuilder(builder: (context, constraint) { - return new Icon(Icons.add, size: constraint.biggest.height, color: kTextLightColor); - }), - ), - ), - ), - ) - ], + if (widget.isAddButton && canEdit) + _AddButton(onTap: () { + widget.onSelect( + ResourceDTO(id: widget.isSelectModal ? '-1' : null)); + }), + ], + ), ), + // Grid Expanded( child: GridView.builder( - shrinkWrap: true, - gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: crossAxisCount), - itemCount: data.length, - itemBuilder: (BuildContext context, int index) { - return - InkWell( - onTap: () { - widget.onSelect(resourcesToShow[index]); - }, - child: Container( - decoration: boxDecoration(data[index], appContext), - padding: const EdgeInsets.all(15), - margin: EdgeInsets.symmetric(vertical: 15, horizontal: 15), - child: Align( - alignment: Alignment.center, - child: getElement(data[index], size, appContext) - ), - ), - ); - } + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 160, + childAspectRatio: 1.0, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + itemCount: data.length, + itemBuilder: (BuildContext context, int index) { + return _ResourceCard( + resource: resourcesToShow[index], + onTap: () => widget.onSelect(resourcesToShow[index]), + ); + }, ), ), ], ); } - getElement(ResourceDTO resource, Size size, AppContext appContext) { - if (resource.id != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - height: size.width *0.01, - ), - Align( - alignment: Alignment.center, - child: AutoSizeText( - resource.label == null ? "" : resource.label!, - style: new TextStyle(fontSize: 20), - maxLines: 1, - ), - ), - Align( - alignment: Alignment.bottomRight, - child: Icon( - getResourceIcon(resource.type), - color: kPrimaryColor, - size: 15, - ), - ), - ], - ); - } else { - return Icon( - Icons.close, - color: kTextLightColor, - size: 50.0, - ); - } - } - void filterResource() { - resourcesToShow = filterSearch.isEmpty ? widget.resources: widget.resources.where((ResourceDTO resource) => resource.id == null || removeDiacritics(resource.label!.toUpperCase()).contains(removeDiacritics(filterSearch.toUpperCase()))).toList(); - var getTypesInSelected = resource_types.where((ft) => selectedTypes.contains(ft.label)).map((rt) => rt.type).toList(); - resourcesToShow = resourcesToShow.where((resource) => resource.id == null || getTypesInSelected.contains(resource.type)).toList(); + resourcesToShow = filterSearch.isEmpty + ? widget.resources + : widget.resources + .where((ResourceDTO resource) => + resource.id == null || + removeDiacritics(resource.label!.toUpperCase()) + .contains(removeDiacritics(filterSearch.toUpperCase()))) + .toList(); + var getTypesInSelected = resource_types + .where((ft) => selectedTypes.contains(ft.label)) + .map((rt) => rt.type) + .toList(); + resourcesToShow = resourcesToShow + .where((resource) => + resource.id == null || + getTypesInSelected.contains(resource.type)) + .toList(); } } -boxDecoration(dynamic resourceDetailDTO, appContext) { - return BoxDecoration( - color: resourceDetailDTO.id == null ? kSecond : kBackgroundColor, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(30.0), - image: resourceDetailDTO.id != null && (resourceDetailDTO.type == ResourceType.Image || resourceDetailDTO.type == ResourceType.ImageUrl) && resourceDetailDTO.url != null ? new DecorationImage( - fit: BoxFit.cover, - colorFilter: new ColorFilter.mode(Colors.black.withValues(alpha: 0.3), BlendMode.dstATop), - image: new NetworkImage( - resourceDetailDTO.url!, +// ── Add button ──────────────────────────────────────────────────────────────── + +class _AddButton extends StatelessWidget { + final VoidCallback onTap; + const _AddButton({required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: kSuccess, + borderRadius: BorderRadius.circular(16), + boxShadow: const [kDefaultShadow], + ), + child: const Icon(Icons.add, color: kTextLightColor, size: 28), ), - ) : null, - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ); + ); + } } +// ── Resource card ───────────────────────────────────────────────────────────── -/*Future> getResources(Function onGetResult, bool isImage, AppContext appContext) async { - List resources = await (appContext.getContext() as ManagerAppContext).clientAPI.resourceApi.resourceGet(instanceId:(appContext.getContext() as ManagerAppContext).instanceId); - if (onGetResult != null && isImage) { - resources = resources.where((element) => element.type == ResourceType.image || element.type == ResourceType.imageUrl).toList(); +class _ResourceCard extends StatefulWidget { + final ResourceDTO resource; + final VoidCallback onTap; + const _ResourceCard({required this.resource, required this.onTap}); + + @override + State<_ResourceCard> createState() => _ResourceCardState(); +} + +class _ResourceCardState extends State<_ResourceCard> { + bool _hovered = false; + + @override + Widget build(BuildContext context) { + final resource = widget.resource; + final isRemove = resource.id == null; + final hasImage = !isRemove && + (resource.type == ResourceType.Image || + resource.type == ResourceType.ImageUrl) && + resource.url != null; + + return MouseRegion( + onEnter: (_) => setState(() => _hovered = true), + onExit: (_) => setState(() => _hovered = false), + child: GestureDetector( + onTap: widget.onTap, + child: AnimatedScale( + scale: _hovered ? 1.03 : 1.0, + duration: const Duration(milliseconds: 150), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: kSecond, + spreadRadius: _hovered ? 1 : 0.5, + blurRadius: _hovered ? 10 : 5, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Stack( + fit: StackFit.expand, + children: [ + // Background + if (hasImage) + Image.network(resource.url!, fit: BoxFit.cover) + else + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: isRemove + ? [kSecond, kSecond.withValues(alpha: 0.7)] + : [kPrimaryColor, kPrimaryColor.withValues(alpha: 0.65)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + ), + // Gradient scrim (only when there's an image) + if (hasImage) + DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.35, 1.0], + colors: [ + Colors.transparent, + Colors.black.withValues(alpha: 0.65), + ], + ), + ), + ), + // Type icon — top right badge + if (!isRemove) + Positioned( + top: 8, + right: 8, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.35), + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + getResourceIcon(resource.type), + color: kTextLightColor, + size: 14, + ), + ), + ), + // Label — bottom left + if (!isRemove) + Positioned( + left: 8, + right: 8, + bottom: 8, + child: AutoSizeText( + resource.label ?? '', + style: kCardTitleStyle.copyWith(fontSize: 13), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + // Remove icon + if (isRemove) + const Center( + child: Icon(Icons.close, color: kTextLightColor, size: 40), + ), + ], + ), + ), + ), + ), + ), + ); } - return resources; -}*/ +} diff --git a/lib/Screens/Resources/show_resource_popup.dart b/lib/Screens/Resources/show_resource_popup.dart index 1556c59..88871a1 100644 --- a/lib/Screens/Resources/show_resource_popup.dart +++ b/lib/Screens/Resources/show_resource_popup.dart @@ -15,126 +15,90 @@ import 'get_element_for_resource.dart'; Future showResource(ResourceDTO resourceDTO, AppContext appContext, BuildContext context, Size size) async { var result = await showDialog( - builder: (BuildContext context) => AlertDialog( + builder: (BuildContext context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(20.0)) ), - content: SingleChildScrollView( - child: Column( - children: [ - Align( - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.6, - height: MediaQuery.of(context).size.height * 0.2, - child: StringInputContainer( - label: "Label :", - initialValue: resourceDTO.label != null ? resourceDTO.label : "", - onChanged: (value) { - resourceDTO.label = value; - }, - ), - ),/*Text( - resourceDTO.label == null ? "" : resourceDTO.label, - style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)),*/ - ), - ), - Container( - height: resourceDTO.type == ResourceType.Audio ? size.height *0.1 : size.height *0.3, - width: resourceDTO.type == ResourceType.Audio ? size.width *0.1 : size.width *0.3, - child: Center( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - border: Border.all(width: 3, color: kSecond) - ), - child: Padding( - padding: const EdgeInsets.all(10.0), - child: getElementForResource(resourceDTO, appContext), + child: SizedBox( + width: 520, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 80, + child: StringInputContainer( + label: "Label :", + initialValue: resourceDTO.label != null ? resourceDTO.label : "", + onChanged: (value) { + resourceDTO.label = value; + }, + ), + ), + const SizedBox(height: 8), + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: resourceDTO.type == ResourceType.Audio ? 80 : 300, + maxWidth: resourceDTO.type == ResourceType.Audio ? 200 : double.infinity, + ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + border: Border.all(width: 3, color: kSecond), + ), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: getElementForResource(resourceDTO, appContext), + ), + ), + ), + ], ), ), ), - ), - ], - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.end, + children: [ + RoundedButton( text: "Retour", icon: Icons.undo, color: kSecond, - press: () { - Navigator.of(context).pop(); - }, - fontSize: 20, + press: () => Navigator.of(context).pop(), + fontSize: 16, ), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 200, - height: 70, - child: RoundedButton( + RoundedButton( text: "Supprimer", icon: Icons.delete, color: kError, textColor: kWhite, - press: () { - delete(resourceDTO, appContext, context); - }, - fontSize: 20, + press: () => delete(resourceDTO, appContext, context), + fontSize: 16, ), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 220, - height: 70, - child: RoundedButton( + RoundedButton( text: AppLocalizations.of(context)!.download, icon: Icons.download, color: kPrimaryColor, press: () { - if(resourceDTO.url != null) { + if (resourceDTO.url != null) { html.AnchorElement anchorElement = html.AnchorElement(href: resourceDTO.url!); anchorElement.download = '${resourceDTO.label}.json'; anchorElement.target = '_blank'; anchorElement.click(); } }, - fontSize: 20, + fontSize: 16, ), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 220, - height: 70, - child: RoundedButton( + RoundedButton( text: AppLocalizations.of(context)!.save, icon: Icons.check, color: kPrimaryColor, @@ -143,14 +107,14 @@ Future showResource(ResourceDTO resourceDTO, AppContext appContext Navigator.pop(context, resourceDTO); } }, - fontSize: 20, + fontSize: 16, ), - ), + ], ), - ), - ], + ], + ), ), - ], + ), ), context: context ); return result; @@ -162,11 +126,9 @@ Future delete(ResourceDTO resourceDTO, AppContext appContext, context) asy () {}, () async { await (appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.resourceDelete(resourceDTO.id!); - // TODO Remove from firebase storage via service or ? FirebaseStorage storage = FirebaseStorage.instance; Reference storageReference = storage.ref().child('pictures/${appContext.getContext().instanceId}/${resourceDTO.id}'); - // Supprimer le fichier try { await storageReference.delete(); print('Fichier supprimé avec succès'); @@ -176,7 +138,6 @@ Future delete(ResourceDTO resourceDTO, AppContext appContext, context) asy try {} catch (e) {} - // just to refresh ManagerAppContext managerAppContext = appContext.getContext(); appContext.setContext(managerAppContext); Navigator.of(context).pop(); diff --git a/lib/constants.dart b/lib/constants.dart index 7c26e7e..094e195 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -16,6 +16,34 @@ const kWhite = Color(0xFFFFFFFF); const kBlack = Color(0xFF000000); const kSuccess = Color(0xFF8bc34a); +// Responsive +const kBreakpointMobile = 850.0; + +// Shadows +const kDefaultShadow = BoxShadow( + color: kSecond, + spreadRadius: 0.5, + blurRadius: 5, + offset: Offset(0, 1.5), +); + +// TextStyles for cards +const kCardTitleStyle = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: kWhite, +); +const kCardSubtitleStyle = TextStyle( + fontSize: 11, + fontWeight: FontWeight.w300, + color: Color(0xCCFFFFFF), +); +const kSectionLabelStyle = TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: kTitleTextColor, +); + const List section_types = ["Map", "Slider", "Video", "Web", "Menu", "Quiz", "Article", "PDF", "Game", "Agenda", "Weather", "Event"]; const List map_types = ["none", "normal", "satellite", "terrain", "hybrid"]; const List languages = ["FR", "NL", "EN", "DE", "IT", "ES", "CN", "PL", "AR", "UK"]; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1904094..9640bd4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -52,6 +52,7 @@ "noInstanceFound": "No instance found", "configurePlan": "Configure plan", "tooltipSwitchInstance": "Switch instance", + "tooltipLogout": "Log out", "usersTitle": "Users", "createUserBtn": "Create user", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fb36b46..2c92a53 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -52,6 +52,7 @@ "noInstanceFound": "Aucune instance trouvée", "configurePlan": "Configurer le plan", "tooltipSwitchInstance": "Changer d'instance", + "tooltipLogout": "Se déconnecter", "usersTitle": "Utilisateurs", "createUserBtn": "Créer un utilisateur", @@ -369,7 +370,7 @@ "configRemoveConfirm": "Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", "configRemovedSuccess": "La configuration a été retirée de l'application avec succès", "configRemoveError": "Une erreur est survenue lors du retrait de la configuration", - "phoneConfigTitle": "Configurations par appareil", + "phoneConfigTitle": "Contenu de l'application mobile", "addConfig": "Ajouter une configuration", "selectConfigToAdd": "Sélectionner une configuration à ajouter", "noItems": "Aucun élément à afficher", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index bcc1f96..24c6042 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -328,6 +328,12 @@ abstract class AppLocalizations { /// **'Changer d\'instance'** String get tooltipSwitchInstance; + /// No description provided for @tooltipLogout. + /// + /// In fr, this message translates to: + /// **'Se déconnecter'** + String get tooltipLogout; + /// No description provided for @usersTitle. /// /// In fr, this message translates to: @@ -1909,7 +1915,7 @@ abstract class AppLocalizations { /// No description provided for @phoneConfigTitle. /// /// In fr, this message translates to: - /// **'Configurations par appareil'** + /// **'Contenu de l\'application mobile'** String get phoneConfigTitle; /// No description provided for @addConfig. diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a3304c1..af5b38e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -126,6 +126,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get tooltipSwitchInstance => 'Switch instance'; + @override + String get tooltipLogout => 'Log out'; + @override String get usersTitle => 'Users'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 6bb606f..936f0de 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -127,6 +127,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get tooltipSwitchInstance => 'Changer d\'instance'; + @override + String get tooltipLogout => 'Se déconnecter'; + @override String get usersTitle => 'Utilisateurs'; @@ -998,7 +1001,7 @@ class AppLocalizationsFr extends AppLocalizations { 'Une erreur est survenue lors du retrait de la configuration'; @override - String get phoneConfigTitle => 'Configurations par appareil'; + String get phoneConfigTitle => 'Contenu de l\'application mobile'; @override String get addConfig => 'Ajouter une configuration'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index f470680..05a1700 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -127,6 +127,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get tooltipSwitchInstance => 'Instantie wisselen'; + @override + String get tooltipLogout => 'Uitloggen'; + @override String get usersTitle => 'Gebruikers'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index d02dbcb..acf7ca3 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -52,6 +52,7 @@ "noInstanceFound": "Geen instantie gevonden", "configurePlan": "Plan configureren", "tooltipSwitchInstance": "Instantie wisselen", + "tooltipLogout": "Uitloggen", "usersTitle": "Gebruikers", "createUserBtn": "Gebruiker aanmaken", diff --git a/lib/main.dart b/lib/main.dart index 9331542..4131190 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -49,13 +49,14 @@ Future main() async { usePathUrlStrategy(); - runApp( + runApp( ChangeNotifierProvider( create: (_) => AppContext(managerAppContext), child: MaterialApp( locale: const Locale('fr'), supportedLocales: const [Locale('fr')], localizationsDelegates: const [ + AppLocalizations.delegate, FlutterQuillLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, diff --git a/manager_api_new/lib/model/instance_dto.dart b/manager_api_new/lib/model/instance_dto.dart index 46d2a24..42cd418 100644 --- a/manager_api_new/lib/model/instance_dto.dart +++ b/manager_api_new/lib/model/instance_dto.dart @@ -34,6 +34,8 @@ class InstanceDTO { this.statsHistoryDays, this.hasAdvancedStats, this.applicationInstanceDTOs = const [], + this.webSlug, + this.publicApiKey, }); String? id; @@ -114,6 +116,10 @@ class InstanceDTO { List? applicationInstanceDTOs; + String? webSlug; + + String? publicApiKey; + @override bool operator ==(Object other) => identical(this, other) || @@ -272,6 +278,16 @@ class InstanceDTO { } else { json[r'applicationInstanceDTOs'] = null; } + if (this.webSlug != null) { + json[r'webSlug'] = this.webSlug; + } else { + json[r'webSlug'] = null; + } + if (this.publicApiKey != null) { + json[r'publicApiKey'] = this.publicApiKey; + } else { + json[r'publicApiKey'] = null; + } return json; } @@ -318,6 +334,8 @@ class InstanceDTO { hasAdvancedStats: mapValueOfType(json, r'hasAdvancedStats'), applicationInstanceDTOs: ApplicationInstanceDTO.listFromJson( json[r'applicationInstanceDTOs']), + webSlug: mapValueOfType(json, r'webSlug'), + publicApiKey: mapValueOfType(json, r'publicApiKey'), ); } return null; diff --git a/web/favicon.svg b/web/favicon.svg new file mode 100644 index 0000000..ffdd75f --- /dev/null +++ b/web/favicon.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + diff --git a/web/index.html b/web/index.html index ab849df..b36c14f 100644 --- a/web/index.html +++ b/web/index.html @@ -31,6 +31,7 @@ +