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/confirmation_dialog.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/string_input_container.dart'; import 'package:manager_app/Helpers/FileHelper.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/section_reorderList.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/client.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import 'dart:html' as html; class ConfigurationDetailScreen extends StatefulWidget { final String id; ConfigurationDetailScreen({Key? key, required this.id}) : super(key: key); @override _ConfigurationDetailScreenState createState() => _ConfigurationDetailScreenState(); } class _ConfigurationDetailScreenState extends State { ConfigurationDTO? configurationDTO; List? sections; Future? _configFuture; Future?>? _sectionsFuture; @override Widget build(BuildContext context) { final appContext = Provider.of(context); 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 _buildBody(ConfigurationDTO config, AppContext appContext) { final managerCtx = appContext.getContext() as ManagerAppContext; final canEdit = managerCtx.canEdit; final l = AppLocalizations.of(context)!; 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), ], ), ), ), _buildFooter(config, appContext, canEdit, l), ], ); } // ── 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( children: [ 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), ), ], ), ), IconButton( icon: const Icon(Icons.download_outlined, color: kPrimaryColor), tooltip: l.configExportSuccess(''), onPressed: () => _export(config, appContext), ), ], ), ); } // ── Footer ── 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, ), ], ), ], ), ), ); } // ── Card Images ── 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(), ); }, ), ], ), ), ); } // ── 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!.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 { return await client.configurationApi!.configurationGetDetail(widget.id); } 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; } }