Wip manager app, update to support web + fix section event layout

This commit is contained in:
Thomas Fransolet 2026-05-07 16:48:58 +02:00
parent 7669f38ed8
commit 2e8b82aa80
38 changed files with 2775 additions and 2558 deletions

View File

@ -15,12 +15,22 @@ import 'flag_decoration.dart';
showMultiStringInput (String label, String modalLabel, bool isTitle, List<TranslationDTO> values, List<TranslationDTO> newValues, Function onGetResult, int maxLines, List<ResourceType>? 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: 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(
@ -45,7 +55,8 @@ showMultiStringInput (String label, String modalLabel, bool isTitle, List<Transl
],
)
),
actions: <Widget>[
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
@ -90,6 +101,9 @@ showMultiStringInput (String label, String modalLabel, bool isTitle, List<Transl
],
),
],
),
),
),
), context: context
);
}

View File

@ -38,9 +38,7 @@ class _ResourceTabState extends State<ResourceTab> with SingleTickerProviderStat
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TabBar(
@ -58,8 +56,6 @@ class _ResourceTabState extends State<ResourceTab> with SingleTickerProviderStat
),
),
],
),
),
);
}

View File

@ -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,25 +27,18 @@ class StringInputContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
child: Row(
return Row(
children: [
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
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,
@ -58,7 +50,6 @@ class StringInputContainer extends StatelessWidget {
),
),
],
),
);
}
}

View File

@ -25,7 +25,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex
future: getConfigurations(appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
List<ConfigurationDTO> configurations = snapshot.data;
List<ConfigurationDTO> 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) {

View File

@ -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<AppConfigurationLinkScreen>
final appContext = Provider.of<AppContext>(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<AppConfigurationLinkScreen>
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<AppConfigurationLinkScreen>
// 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<AppConfigurationLinkScreen>
// 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<AppConfigurationLinkScreen>
// 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<AppConfigurationLinkScreen>
// 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<AppConfigurationLinkScreen>
// 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<AppConfigurationLinkScreen>
// 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<AppConfigurationLinkScreen>
// 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<AppConfigurationLinkScreen>
),
),
// 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<AppConfigurationLinkScreen>
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,7 +334,9 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
],
),
),
),
);
}
)
)
],
),
@ -348,7 +356,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
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<AppConfigurationLinkScreen>
// 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) {

View File

@ -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<WebInstanceLoader> createState() => _WebInstanceLoaderState();
}
class _WebInstanceLoaderState extends State<WebInstanceLoader> {
bool _creating = false;
String? _error;
ApplicationInstanceDTO? _instance;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _init());
}
void _init() {
final appContext = Provider.of<AppContext>(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<void> _create() async {
final appContext = Provider.of<AppContext>(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<AppContext>(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);
},
),
],
);
}
}

View File

@ -431,13 +431,14 @@ class _EventConfigState extends State<EventConfig> {
Divider(),
// --- Parcours ---
if (eventDTO.id != null)
ParcoursConfig(
SizedBox(
height: 500,
child: ParcoursConfig(
initialValue: const [],
parentId: eventDTO.id!,
isEvent: true,
onChanged: (paths) {
// parcours are managed independently, no DTO update needed here
},
onChanged: (paths) {},
),
),
],
);

View File

@ -25,12 +25,19 @@ 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: 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: [
Flexible(
child: SingleChildScrollView(
child: Column(
children: [
@ -139,7 +146,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext
),
),
),
actions: <Widget>[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@ -180,6 +187,9 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext
],
),
],
),
),
),
), context: context
);
}

View File

@ -45,12 +45,19 @@ Future<OrderedTranslationAndResourceDTO?> 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: SizedBox(
width: 560,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -105,7 +112,7 @@ Future<OrderedTranslationAndResourceDTO?> showNewOrUpdatePDFFile(OrderedTranslat
),
),
),
actions: <Widget>[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@ -148,6 +155,9 @@ Future<OrderedTranslationAndResourceDTO?> showNewOrUpdatePDFFile(OrderedTranslat
],
),
],
),
),
),
), context: context
);
return result;

View File

@ -34,12 +34,19 @@ Future<QuestionDTO?> 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: SizedBox(
width: 560,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -124,7 +131,7 @@ Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO,
),
),
),
actions: <Widget>[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@ -168,6 +175,9 @@ Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO,
],
),
],
),
),
),
), context: context
);
return result;

View File

@ -34,13 +34,19 @@ Future<ContentDTO?> 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: SizedBox(
width: 480,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -115,7 +121,7 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
),
),
),
actions: <Widget>[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@ -157,6 +163,9 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
],
),
],
),
),
),
), context: context
);

View File

@ -68,6 +68,7 @@ class _SliderConfigState extends State<SliderConfig> {
children: [
Container(
width: size.width * 0.95,
height: 300,
child: ReorderableListView(
onReorder: _onReorder,
scrollDirection: Axis.horizontal,

View File

@ -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,239 +81,186 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
Object? rawSectionData;
return FutureBuilder(
future: _sectionFuture,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
rawSectionData = snapshot.data;
Object? rawSectionData = snapshot.data;
var nullableSection = SectionDTO.fromJson(rawSectionData);
if (nullableSection != null) {
sectionDTO = nullableSection;
// Only initialize sectionDetailDTO if it's not already loaded for this section
if (sectionDetailDTO == null ||
lastLoadedSectionId != widget.id) {
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),
),
)
],
);
return _buildBody(rawSectionData, appContext);
} else {
return Center(
child: Text(
AppLocalizations.of(context)!.sectionLoadError));
return Center(child: Text(AppLocalizations.of(context)!.sectionLoadError));
}
} else if (snapshot.connectionState == ConnectionState.none) {
return Text(AppLocalizations.of(context)!.noData);
} else {
return Center(
child: Container(
height: size.height * 0.2, child: CommonLoader()));
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(
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(
//mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
//color: Colors.orangeAccent,
height: 82,
_cardIdentity(appContext, l),
const SizedBox(height: 12),
_cardQR(appContext, l),
const SizedBox(height: 12),
_cardTypeConfig(rawSectionData, appContext),
const SizedBox(height: 80),
],
),
),
),
_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(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Align(
alignment: AlignmentDirectional.bottomStart,
child: Padding(
padding: const EdgeInsets.all(3.0),
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: [
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: 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),
),
],
),
),
Text(sectionDTO.label!,
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w400)),
//if((appContext.getContext() as ManagerAppContext).selectedConfiguration!.isMobile!)
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]),
],
),
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,
))),
),
)
],
),
), // 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);
);
}
},
// 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: [
OutlinedButton.icon(
icon: const Icon(Icons.undo, size: 16),
label: Text(l.cancel),
onPressed: () => cancel(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),
),
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),
),
],
],
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
);
}
// 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: [
SizedBox(
height: 100,
child: StringInputContainer(
label: AppLocalizations.of(context)!.identifierLabel,
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;
},
onChanged: (String value) => sectionDTO.label = value,
),
),
SizedBox(
height: 100,
child: MultiStringInputContainer(
label: AppLocalizations.of(context)!.displayTitleLabel,
modalLabel: AppLocalizations.of(context)!.messageTitle,
const SizedBox(height: 12),
MultiStringInputContainer(
label: l.displayTitleLabel,
modalLabel: l.messageTitle,
color: kPrimaryColor,
initialValue: sectionDTO.title!,
onGetResult: (value) {
@ -327,32 +273,11 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
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,
);
final image = ResourceInputContainer(
label: l.imageLabel,
initialValue: sectionDTO.imageId,
color: kPrimaryColor,
onChanged: (ResourceDTO resource) {
@ -364,84 +289,143 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
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,
],
),
);
},
),
],
),
),
),
), // 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)),
),
],
),
);
}
getButtons(SectionDTO sectionDTO, AppContext appContext) {
final canEdit = (appContext.getContext() as ManagerAppContext).canEdit;
return Align(
alignment: AlignmentDirectional.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
// 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: [
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);
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,
),
),
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) 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: () {
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<void> cancel(AppContext appContext) async {
ManagerAppContext managerAppContext = appContext.getContext();
Object? rawData = await (appContext.getContext() as ManagerAppContext)
@ -480,18 +464,16 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
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<SectionDetailScreen> {
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<SectionDetailScreen> {
}
}
// Section detail initialization
_initializeSectionDetail(Object? rawSectionData) {
if (rawSectionData == null) return;
switch (sectionDTO.type) {
@ -626,6 +608,8 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
}
}
// Sync sectionDTO fields back to detail DTO before save
updateSectionDetail() {
switch (sectionDTO.type) {
case SectionType.Map:
@ -634,8 +618,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
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;
}
}

View File

@ -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<ConfigurationDetailScreen> {
ConfigurationDTO? configurationDTO;
SectionDTO? selectedSection;
List<SectionDTO>? sections;
Future<ConfigurationDTO?>? _configFuture;
Future<List<SectionDTO>?>? _sectionsFuture;
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
return FutureBuilder(
future: getConfiguration(this.widget, (appContext.getContext() as ManagerAppContext).clientAPI!),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
final managerCtx = appContext.getContext() as ManagerAppContext;
_configFuture ??= getConfiguration(widget, managerCtx.clientAPI!);
return FutureBuilder<ConfigurationDTO?>(
future: _configFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return bodyConfiguration(snapshot.data, size, appContext, context);
if (snapshot.data == null) return Center(child: Text(AppLocalizations.of(context)!.noData));
return _buildBody(snapshot.data!, appContext);
} else if (snapshot.connectionState == ConnectionState.none) {
return Text(AppLocalizations.of(context)!.noData);
} else {
return Center(
child: Container(
height: size.height * 0.2,
child: CommonLoader()
)
);
}
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();
Widget _buildBody(ConfigurationDTO config, AppContext appContext) {
final managerCtx = appContext.getContext() as ManagerAppContext;
final canEdit = managerCtx.canEdit;
final l = AppLocalizations.of(context)!;
return Column(
//mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
//color: Colors.cyanAccent,
height: size.height *0.1,
_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(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Align(
alignment: AlignmentDirectional.bottomStart,
child: Padding(
padding: const EdgeInsets.all(10.0),
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: [
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!);
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),
),
],
),
);
}
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);
// 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),
),
],
],
),
);
}
} catch(e) {
log(e.toString());
showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.configExportFailed, context, null);
}
},
// 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.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,
)
)
),
),
)
],
),
),
), // TITLE
Container(
height: size.height *0.78,
//color: Colors.red,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
padding: const EdgeInsets.all(16),
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;
},
),
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: 8,
runSpacing: 12,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
MultiSelectDropdownLanguageContainer(
label: AppLocalizations.of(context)!.languagesLabel,
initialValue: configurationDTO.languages != null ? configurationDTO.languages! : [],
label: l.languagesLabel,
initialValue: config.languages ?? [],
values: languages,
isMultiple: true,
fontSize: 20,
fontSize: 16,
isAtLeastOne: true,
onChanged: (value) {
var tempOutput = new List<String>.from(value);
configurationDTO.languages = tempOutput;
config.languages = List<String>.from(value);
},
),
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;
},
label: "Hors ligne :",
fontSize: 16,
isChecked: config.isOffline,
onChanged: (value) => config.isOffline = value,
),
],
),
],
),
),
);
}
final images = Column(
// 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: AppLocalizations.of(context)!.mainImageLabel,
fontSize: 20,
initialValue: configurationDTO.imageId,
label: l.mainImageLabel,
fontSize: 16,
initialValue: config.imageId,
color: kPrimaryColor,
onChanged: (ResourceDTO resource) {
if (resource.id == null) {
configurationDTO.imageId = null;
configurationDTO.imageSource = null;
config.imageId = null;
config.imageSource = null;
} else {
configurationDTO.imageId = resource.id;
configurationDTO.imageSource = resource.url;
config.imageId = resource.id;
config.imageSource = resource.url;
}
},
),
ResourceInputContainer(
label: AppLocalizations.of(context)!.loaderLabel,
fontSize: 20,
initialValue: configurationDTO.loaderImageId,
label: l.loaderLabel,
fontSize: 16,
initialValue: config.loaderImageId,
color: kPrimaryColor,
onChanged: (ResourceDTO resource) {
if (resource.id == null) {
configurationDTO.loaderImageId = null;
configurationDTO.loaderImageUrl = null;
config.loaderImageId = null;
config.loaderImageUrl = null;
} else {
configurationDTO.loaderImageId = resource.id;
configurationDTO.loaderImageUrl = resource.url;
config.loaderImageId = resource.id;
config.loaderImageUrl = resource.url;
}
},
),
],
);
];
if (isWide) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(flex: 3, child: fields),
SizedBox(width: 24),
Expanded(flex: 2, child: images),
],
children: children.map((c) => Expanded(child: Padding(
padding: const EdgeInsets.only(right: 16),
child: c,
))).toList(),
);
} 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<String>.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,
],
children: children.map((c) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: c,
)).toList(),
);
}
},
),
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<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done)
{
sections = new List<SectionDTO>.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)
)
)
],
);
}
getButtons(ConfigurationDTO configurationDTO, AppContext appContext) {
final canEdit = (appContext.getContext() as ManagerAppContext).canEdit;
return Align(
alignment: AlignmentDirectional.bottomCenter,
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);
},
),
),
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);
},
),
),
],
),
);
}
Widget bodyGrid(ConfigurationDTO configurationDTO, Size size, AppContext appContext) {
return SingleChildScrollView(
child: Container(
height: size.height *0.40,
// 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(8.0),
child: SectionReorderList(
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<List<SectionDTO>?>(
future: _sectionsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) return const Text("No data");
sections = List<SectionDTO>.from(snapshot.data!)
.where((s) => !s.isSubSection!)
.toList();
return SectionReorderList(
sectionsIn: sections!,
configurationId: configurationDTO.id!,
configurationId: config.id!,
onChangedOrder: (List<SectionDTO> sectionsOut) async {
sections = sectionsOut;
// Update section order
ManagerAppContext managerAppContext = appContext.getContext();
await managerAppContext.clientAPI!.sectionApi!.sectionUpdateOrder(sections!);
await managerCtx.clientAPI!.sectionApi!.sectionUpdateOrder(sections!);
},
askReload: () {
setState(() {
// refresh UI
});
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(),
));
},
),
],
),
),
);
}
Future<void> cancel(ConfigurationDTO configurationDTO, AppContext appContext) async {
// 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<void> cancel(ConfigurationDTO config, AppContext appContext) async {
ManagerAppContext managerAppContext = appContext.getContext();
ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationGetDetail(configurationDTO.id!);
ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationGetDetail(config.id!);
managerAppContext.selectedConfiguration = configuration;
appContext.setContext(managerAppContext);
}
Future<void> delete(ConfigurationDTO configurationDTO, AppContext appContext) async {
Future<void> delete(ConfigurationDTO config, AppContext appContext) async {
showConfirmationDialog(
AppLocalizations.of(context)!.configDeleteConfirm,
() {},
() async {
ManagerAppContext managerAppContext = appContext.getContext();
await managerAppContext.clientAPI!.configurationApi!.configurationDelete(configurationDTO.id!);
await managerAppContext.clientAPI!.configurationApi!.configurationDelete(config.id!);
managerAppContext.selectedConfiguration = null;
appContext.setContext(managerAppContext);
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeletedSuccess, context, null);
},
context
context,
);
}
Future<void> save(ConfigurationDTO configurationDTO, AppContext appContext) async {
Future<void> save(ConfigurationDTO config, AppContext appContext) async {
ManagerAppContext managerAppContext = appContext.getContext();
ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationUpdate(configurationDTO);
ConfigurationDTO? configuration = await managerAppContext.clientAPI!.configurationApi!.configurationUpdate(config);
managerAppContext.selectedConfiguration = configuration;
appContext.setContext(managerAppContext);
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configSavedSuccess, context, null);
}
Future<ConfigurationDTO?> getConfiguration(ConfigurationDetailScreen widget, Client client) async {
ConfigurationDTO? configuration = await client.configurationApi!.configurationGetDetail(widget.id);
return configuration;
return await client.configurationApi!.configurationGetDetail(widget.id);
}
Future<List<SectionDTO>?> getSections(ConfigurationDTO configurationDTO, Client client) async {
List<SectionDTO>? sections = await client.sectionApi!.sectionGetFromConfiguration(configurationDTO.id!);
if(sections != null) {
Future<List<SectionDTO>?> getSections(ConfigurationDTO config, Client client) async {
List<SectionDTO>? 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
),
],
);
}

View File

@ -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<ConfigurationsScreen> {
ConfigurationDTO? selectedConfiguration;
String _filter = '';
Future<List<ConfigurationDTO>?>? _future;
void _refresh() => setState(() => _future = null);
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(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 {
}
_future ??= getConfigurations(managerAppContext);
return Align(
alignment: AlignmentDirectional.topCenter,
child: FutureBuilder(
future: getConfigurations(managerAppContext),
future: _future,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var tempOutput = new List<ConfigurationDTO>.from(snapshot.data);
if (managerAppContext.canEdit) tempOutput.add(ConfigurationDTO(id: null));
return bodyGrid(tempOutput, size, appContext, context);
var all = List<ConfigurationDTO>.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(
height: size.height * 0.2,
child: CommonLoader()
)
);
}
}
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);
showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configCreatedSuccess, context, null);
}
}
} else {
setState(() {
ManagerAppContext managerAppContext = appContext.getContext();
managerAppContext.selectedConfiguration = data[index];
selectedConfiguration = data[index];
});
}
},
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)
),
),
);
}
);
}
getElement(ConfigurationDTO configuration, Size size) {
if (configuration.id != null) {
Widget _buildGrid(List<ConfigurationDTO> data, Size size, AppContext appContext,
BuildContext mainContext) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Align(
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),
);
},
),
),
],
);
} else {
return Icon(
Icons.add,
color: kTextLightColor,
size: size.height*0.1,
);
}
}
}
Future<List<ConfigurationDTO>?> getConfigurations(ManagerAppContext managerAppContext) async {
List<ConfigurationDTO>? configurations = await managerAppContext.clientAPI!.configurationApi!.configurationGet(instanceId: managerAppContext.instanceId);
//print("number of configurations " + configurations.length.toString());
if(configurations != null) {
configurations.forEach((element) {
//print(element);
Future<void> _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 {
setState(() {
final ctx = appContext.getContext() as ManagerAppContext;
ctx.selectedConfiguration = config;
selectedConfiguration = config;
});
}
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,
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: 0.5,
blurRadius: 5,
offset: Offset(0, 1.5), // changes position of shadow
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,
),
],
),
),
],
),
),
),
),
),
);
}
}
/*Widget bodyTable(data) => DataTable(
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
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<DataRow>( (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()
);*/
Future<List<ConfigurationDTO>?> getConfigurations(
ManagerAppContext managerAppContext) async {
return managerAppContext.clientAPI!.configurationApi!
.configurationGet(instanceId: managerAppContext.instanceId);
}

View File

@ -14,26 +14,31 @@ import 'package:manager_api_new/api.dart';
Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChanged<bool> 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<ConfigurationDTO?>(
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,
width: 460,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Center(child: Text(l.newConfiguration, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400))),
Center(
child: SizedBox(
height: 100,
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,
@ -42,21 +47,19 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
},
),
),
),
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: 25, fontWeight: FontWeight.w300)),
child: Text(l.importLabel, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w300)),
),
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();
@ -64,11 +67,7 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
},
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Icon(
Icons.file_upload,
color: kPrimaryColor,
size: 30
),
child: Icon(Icons.file_upload, color: kPrimaryColor, size: 30),
),
)
]
@ -77,51 +76,39 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
),
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.end,
children: [
Align(
alignment: AlignmentDirectional.bottomEnd,
child: Container(
width: 175,
height: 70,
child: RoundedButton(
RoundedButton(
text: l.cancel,
icon: Icons.undo,
color: kSecond,
press: () {
Navigator.of(context).pop();
},
fontSize: 20,
press: () => Navigator.of(context).pop(),
fontSize: 16,
),
),
),
Align(
alignment: AlignmentDirectional.bottomEnd,
child: Container(
width: 150,
height: 70,
child: RoundedButton(
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,
),
),
fontSize: 16,
),
],
),
],
),
),
),
), context: context
);
@ -129,38 +116,12 @@ Future<ConfigurationDTO?> 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);
}
}

View File

@ -23,12 +23,20 @@ Future<SectionDTO?> showNewSection(String configurationId,
sectionDTO.type = SectionType.Map;
var section = showDialog<SectionDTO?>(
builder: (BuildContext context) => AlertDialog(
builder: (BuildContext context) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))),
content: SingleChildScrollView(
child: SizedBox(
width: 750,
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),
@ -83,7 +91,8 @@ Future<SectionDTO?> showNewSection(String configurationId,
),
),
),
actions: <Widget>[
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
@ -136,6 +145,9 @@ Future<SectionDTO?> showNewSection(String configurationId,
),
],
),
),
),
),
context: contextBuild);
return section;

View File

@ -62,73 +62,12 @@ class _SectionReorderListState extends State<SectionReorderList> {
final appContext = Provider.of<AppContext>(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);
});
}
);
},
),*/
),
),
),
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),
Row(
children: [
Expanded(
child: StringInputContainer(
label: "Recherche:",
isSmall: true,
@ -142,34 +81,23 @@ class _SectionReorderListState extends State<SectionReorderList> {
},
),
),
),
Positioned(
top: 10,
right: 10,
child: InkWell(
const SizedBox(width: 8),
InkWell(
onTap: () async {
var sectionToCreate = await showNewSection(widget.configurationId, appContext, context, false);
if(sectionToCreate != null)
{
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);
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,
),
height: 36,
width: 36,
decoration: BoxDecoration(
color: kSuccess,
shape: BoxShape.rectangle,
@ -179,13 +107,47 @@ class _SectionReorderListState extends State<SectionReorderList> {
color: kSecond,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1.5), // changes position of shadow
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);
});
});
},
);
},
),
),
)
],
);
}

View File

@ -15,102 +15,93 @@ 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: SizedBox(
width: 580,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
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: [
Container(
width: size.width * 0.3,
height: size.height * 0.15,
// 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;
},
onChanged: (value) => inputDevice.name = value,
maxLength: 20,
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: AlignmentDirectional.centerStart,
child: Text("Configuration:", style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300))
),
Container(
height: size.height * 0.1,
child: FutureBuilder(
Text("Configuration:", style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400)),
const SizedBox(width: 8),
FutureBuilder(
future: getConfigurations(appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.length > 0) {
return Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: DropDownConfig(
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");
return const Text("No data");
} else {
return Center(
child: Container(
child: Text("Loading..")
)
);
return const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2));
}
}
),
),
],
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
const SizedBox(height: 12),
// Couleurs + Loader
Wrap(
spacing: 16,
runSpacing: 12,
children: [
ColorPickerInputContainer(
label: "Couleur principale :",
fontSize: 20,
fontSize: 16,
color: appConfiguration.primaryColor,
onChanged: (value) {
appConfiguration.primaryColor = value;
},
onChanged: (value) => appConfiguration.primaryColor = value,
),
ColorPickerInputContainer(
label: AppLocalizations.of(context)!.backgroundColorLabel,
fontSize: 20,
fontSize: 16,
color: appConfiguration.secondaryColor,
onChanged: (value) {
appConfiguration.secondaryColor = value;
},
onChanged: (value) => appConfiguration.secondaryColor = value,
),
ResourceInputContainer(
label: "Loader :",
@ -118,7 +109,7 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
color: kPrimaryColor,
imageFit: BoxFit.fitHeight,
onChanged: (ResourceDTO resource) async {
if(resource.id == null) {
if (resource.id == null) {
appConfiguration.loaderImageId = null;
appConfiguration.loaderImageUrl = null;
} else {
@ -129,11 +120,14 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
const SizedBox(height: 12),
// Pourcentages
Wrap(
spacing: 16,
runSpacing: 12,
children: [
Container(
height: 100,
SizedBox(
height: 90,
child: NumberInputContainer(
label: "Place des sections (%) :",
initialValue: appConfiguration.screenPercentageSectionsMainPage ?? 0,
@ -143,14 +137,13 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
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);
}
},
),
),
Container(
height: 100,
SizedBox(
height: 90,
child: NumberInputContainer(
label: "Pourcentage des arrondis (0-50) :",
initialValue: appConfiguration.roundedValue ?? 0,
@ -160,7 +153,6 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
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);
}
},
@ -168,62 +160,53 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
const SizedBox(height: 12),
// Toggles
Wrap(
spacing: 16,
runSpacing: 8,
children: [
CheckInputContainer(
icon: Icons.image,
label: "Fond pour les images des sections :",
fontSize: 20,
fontSize: 16,
isChecked: appConfiguration.isSectionImageBackground,
onChanged: (value) {
appConfiguration.isSectionImageBackground = value;
},
onChanged: (value) => appConfiguration.isSectionImageBackground = value,
),
CheckInputContainer(
icon: Icons.watch_later_outlined,
label: "Heure :",
fontSize: 20,
fontSize: 16,
isChecked: appConfiguration.isHour,
onChanged: (value) {
appConfiguration.isHour = value;
},
onChanged: (value) => appConfiguration.isHour = value,
),
CheckInputContainer(
icon: Icons.date_range,
label: "Date :",
fontSize: 20,
fontSize: 16,
isChecked: appConfiguration.isDate,
onChanged: (value) {
appConfiguration.isDate = value;
},
onChanged: (value) => appConfiguration.isDate = value,
),
]
],
),
],
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.end,
children: [
Container(
width: 180,
height: 70,
child: RoundedButton(
RoundedButton(
text: "Annuler",
icon: Icons.undo,
color: kSecond,
press: () {
Navigator.of(context).pop();
},
fontSize: 20,
press: () => Navigator.of(context).pop(),
fontSize: 16,
),
),
Container(
width: 180,
height: 70,
child: RoundedButton(
RoundedButton(
text: "Changer",
icon: Icons.check,
color: kPrimaryColor,
@ -232,19 +215,21 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
onGetResult(inputDevice, appConfiguration);
Navigator.of(context).pop();
},
fontSize: 20,
),
fontSize: 16,
),
],
),
],
),
),
),
), context: mainContext
);
}
getConfigurationsElement(DeviceDTO inputDevice, data, Function onGetResult) {
List<Widget> widgets = <Widget>[];
for(var configuration in data as List<ConfigurationDTO>) {
for (var configuration in data as List<ConfigurationDTO>) {
var widget = new InkWell(
onTap: () {
inputDevice.configuration = configuration.label;
@ -264,15 +249,8 @@ getConfigurationsElement(DeviceDTO inputDevice, data, Function onGetResult) {
}
Future<List<ConfigurationDTO>?> getConfigurations(AppContext appContext) async {
List<ConfigurationDTO>? 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);
}

View File

@ -66,7 +66,7 @@ class _DeviceElementState extends State<DeviceElement> {
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<DeviceElement> {
Center(
child: Text(
updatedDevice?.configuration != null ? updatedDevice?.configuration! ?? "Aucune configuration" : "Aucune configuration",
style: new TextStyle(fontSize: 20),
style: const TextStyle(fontSize: 13),
maxLines: 1,
),
),

View File

@ -60,23 +60,18 @@ class _KioskScreenState extends State<KioskScreen> {
List<AppConfigurationLinkDTO>? 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<KioskScreen> {
}*/
}
boxDecoration() {
return BoxDecoration(
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,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(25.0),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: kSecond,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1.5), // changes position of shadow
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<List<DeviceDTO>?> getDevices(AppContext appContext) async {

View File

@ -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,6 +431,8 @@ class _MainScreenState extends State<MainScreen> {
child: Column(
children: [
const QuotaBarsWidget(),
Row(
children: [
DropdownButton<Locale>(
value: managerAppContext.locale,
underline: const SizedBox(),
@ -443,12 +446,14 @@ class _MainScreenState extends State<MainScreen> {
if (locale != null) appContext.setLocale(locale);
},
),
const SizedBox(height: 4),
AutoSizeText(
const SizedBox(width: 8),
Expanded(
child: AutoSizeText(
(appContext.getContext() as ManagerAppContext).email ?? "",
style: TextStyle(color: kBodyTextColor, fontSize: 16, fontWeight: FontWeight.w300, fontFamily: "Helvetica"),
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),
@ -457,8 +462,9 @@ class _MainScreenState extends State<MainScreen> {
),
IconButton(
icon: Icon(Icons.logout, color: kPrimaryColor),
tooltip: AppLocalizations.of(context)!.tooltipLogout,
onPressed: () async {
var session = await loadJsonSessionFile();
await loadJsonSessionFile();
setState(() {
Storage localStorage = window.localStorage;
localStorage.clear();
@ -471,6 +477,8 @@ class _MainScreenState extends State<MainScreen> {
context.go('/login');
});
},
),
],
)
],
),
@ -597,25 +605,24 @@ class _MainScreenState extends State<MainScreen> {
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")

View File

@ -275,7 +275,9 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
],
),
const SizedBox(height: 16),
Row(
Wrap(
spacing: 12,
runSpacing: 12,
children: [
_KpiCard(
label: l.allNotifications,
@ -284,7 +286,6 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
selected: _statusFilter == null,
onTap: () => _setFilter(null),
),
const SizedBox(width: 12),
_KpiCard(
label: l.sentNotifications,
count: _sentCount,
@ -292,7 +293,6 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
selected: _statusFilter == 'Sent',
onTap: () => _setFilter('Sent'),
),
const SizedBox(width: 12),
_KpiCard(
label: l.scheduledNotifications,
count: _scheduledCount,
@ -300,7 +300,6 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
selected: _statusFilter == 'Scheduled',
onTap: () => _setFilter('Scheduled'),
),
const SizedBox(width: 12),
_KpiCard(
label: l.failedNotifications,
count: _failedCount,

View File

@ -13,39 +13,34 @@ 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<File>? filesToSend;
List<PlatformFile>? 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: SizedBox(
width: 560,
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: [
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;
},
Text(
AppLocalizations.of(context)!.newResource,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w400),
),
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,
const SizedBox(height: 10),
SizedBox(
height: 400,
child: ResourceTab(
resourceDTO: resourceDetailDTO,
onFileUpload: (List<File> files) {
@ -54,71 +49,53 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async {
onFileUploadWeb: (List<PlatformFile> files) {
filesToSendWeb = files;
},
)
),
),
],
),
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.end,
children: [
Align(
alignment: AlignmentDirectional.bottomEnd,
child: Container(
width: 175,
height: 70,
child: RoundedButton(
RoundedButton(
text: AppLocalizations.of(context)!.cancel,
icon: Icons.undo,
color: kSecond,
press: () {
Navigator.of(context).pop();
},
fontSize: 20,
press: () => Navigator.of(context).pop(),
fontSize: 16,
),
),
),
Align(
alignment: AlignmentDirectional.bottomEnd,
child: Container(
width: 150,
height: 70,
child: RoundedButton(
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
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
);

View File

@ -11,7 +11,7 @@ import 'package:provider/provider.dart';
import 'package:diacritic/diacritic.dart';
class ResourceBodyGrid extends StatefulWidget {
final List<ResourceDTO> resources; //return ResourceDTO
final List<ResourceDTO> 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<ResourceBodyGrid> {
@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<AppContext>(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<ResourceDTO> data, AppContext appContext) {
final canEdit = (appContext.getContext() as ManagerAppContext).canEdit;
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Header: search + filter + add button Wrap pour mobile
Padding(
padding: const EdgeInsets.all(8.0),
child: StringInputContainer(
label: "Recherche:",
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<ResourceBodyGrid> {
});
},
),
),
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<String>; // TO TEST
selectedTypes = result as List<String>;
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),
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
InkWell(
onTap: () {
widget.onSelect(resourcesToShow[index]);
return _ResourceCard(
resource: resourcesToShow[index],
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)
),
),
);
}
),
),
],
);
}
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],
),
) : null,
child: const Icon(Icons.add, color: kTextLightColor, size: 28),
),
);
}
}
// Resource card
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: 0.5,
blurRadius: 5,
offset: Offset(0, 1.5), // changes position of shadow
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),
),
],
),
),
),
),
),
);
}
/*Future<List<ResourceDTO>> getResources(Function onGetResult, bool isImage, AppContext appContext) async {
List<ResourceDTO> 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();
}
return resources;
}*/
}

View File

@ -15,20 +15,25 @@ import 'get_element_for_resource.dart';
Future<ResourceDTO?> 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,
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 : "",
@ -36,19 +41,17 @@ Future<ResourceDTO?> showResource(ResourceDTO resourceDTO, AppContext appContext
resourceDTO.label = value;
},
),
),/*Text(
resourceDTO.label == null ? "" : resourceDTO.label,
style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)),*/
),
const SizedBox(height: 8),
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: resourceDTO.type == ResourceType.Audio ? 80 : 300,
maxWidth: resourceDTO.type == ResourceType.Audio ? 200 : double.infinity,
),
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)
border: Border.all(width: 3, color: kSecond),
),
child: Padding(
padding: const EdgeInsets.all(10.0),
@ -56,85 +59,46 @@ Future<ResourceDTO?> showResource(ResourceDTO resourceDTO, AppContext appContext
),
),
),
),
],
),
),
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.end,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: AlignmentDirectional.bottomEnd,
child: Container(
width: 175,
height: 70,
child: RoundedButton(
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<ResourceDTO?> showResource(ResourceDTO resourceDTO, AppContext appContext
Navigator.pop(context, resourceDTO);
}
},
fontSize: 20,
),
),
),
fontSize: 16,
),
],
),
],
),
),
),
), context: context
);
return result;
@ -162,11 +126,9 @@ Future<void> 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<void> delete(ResourceDTO resourceDTO, AppContext appContext, context) asy
try {} catch (e) {}
// just to refresh
ManagerAppContext managerAppContext = appContext.getContext();
appContext.setContext(managerAppContext);
Navigator.of(context).pop();

View File

@ -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<String> section_types = ["Map", "Slider", "Video", "Web", "Menu", "Quiz", "Article", "PDF", "Game", "Agenda", "Weather", "Event"];
const List<String> map_types = ["none", "normal", "satellite", "terrain", "hybrid"];
const List<String> languages = ["FR", "NL", "EN", "DE", "IT", "ES", "CN", "PL", "AR", "UK"];

View File

@ -52,6 +52,7 @@
"noInstanceFound": "No instance found",
"configurePlan": "Configure plan",
"tooltipSwitchInstance": "Switch instance",
"tooltipLogout": "Log out",
"usersTitle": "Users",
"createUserBtn": "Create user",

View File

@ -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",

View File

@ -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.

View File

@ -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';

View File

@ -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';

View File

@ -127,6 +127,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get tooltipSwitchInstance => 'Instantie wisselen';
@override
String get tooltipLogout => 'Uitloggen';
@override
String get usersTitle => 'Gebruikers';

View File

@ -52,6 +52,7 @@
"noInstanceFound": "Geen instantie gevonden",
"configurePlan": "Plan configureren",
"tooltipSwitchInstance": "Instantie wisselen",
"tooltipLogout": "Uitloggen",
"usersTitle": "Gebruikers",
"createUserBtn": "Gebruiker aanmaken",

View File

@ -56,6 +56,7 @@ Future<void> main() async {
locale: const Locale('fr'),
supportedLocales: const [Locale('fr')],
localizationsDelegates: const [
AppLocalizations.delegate,
FlutterQuillLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,

View File

@ -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<ApplicationInstanceDTO>? 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<bool>(json, r'hasAdvancedStats'),
applicationInstanceDTOs: ApplicationInstanceDTO.listFromJson(
json[r'applicationInstanceDTOs']),
webSlug: mapValueOfType<String>(json, r'webSlug'),
publicApiKey: mapValueOfType<String>(json, r'publicApiKey'),
);
}
return null;

70
web/favicon.svg Normal file
View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="439"
height="419"
id="svg137"
sodipodi:docname="MyInfoMate_logo_only.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs137" />
<sodipodi:namedview
id="namedview137"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="5.6568543"
inkscape:cx="348.69203"
inkscape:cy="38.448931"
inkscape:window-width="5120"
inkscape:window-height="1369"
inkscape:window-x="1912"
inkscape:window-y="1072"
inkscape:window-maximized="1"
inkscape:current-layer="svg137" />
<path
d="m 178.04447,135.21247 c 0.002,9.09742 0.17625,49.4843 0.17178,58.58172 -0.005,10.44131 -0.004,20.8826 0.002,31.32392 0.005,9.06176 0.005,18.12351 0.003,27.18527 -0.002,5.37036 -0.002,10.7407 0.002,16.11105 0.0204,38.14483 0.0204,38.14483 -0.54694,53.99548 l -0.10305,3.01772 c -0.80885,18.6575 -7.8096,35.94603 -20.4863,49.70518 l -2.15235,2.35938 c -12.39998,12.98838 -30.69723,23.86033 -49.02594,24.74586 -2.64076,0.0575 -5.28022,0.0839 -7.921566,0.10008 l -3.044189,0.03 c -3.328264,0.0316 -6.656542,0.0564 -9.984864,0.0811 -2.346596,0.0206 -4.693189,0.0416 -7.039779,0.063 -4.940416,0.044 -9.880843,0.0849 -14.821305,0.12331 -6.216775,0.0484 -12.433489,0.10212 -18.650202,0.158 -6.037887,0.054 -17.776846,0.19345 -40.2740759,0.28883 L 57.571753,137.10368 c 14.685204,-1.44088 35.90966,0.0974 45.537897,0.12284 41.79401,0.11064 74.93393,-6.04872 74.93482,-2.01405 z"
fill="#d62f60"
id="path1"
transform="translate(249.91406,6.8671875)"
sodipodi:nodetypes="cscsccccccccccscccsc" />
<path
d="m 64.688834,203.13197 c 0.0035,6.66536 0.0067,13.33073 0.0093,19.99609 l 9.86e-4,2.48865 c 0.005,13.72121 -0.0058,27.44236 -0.02756,41.16355 -0.01265,8.44313 -0.01133,16.88604 0.0085,25.32916 0.01266,5.78741 0.0111,11.57471 -0.0014,17.36211 -0.0067,3.33159 -0.0047,6.66256 0.01058,9.99416 0.111194,25.65838 0.111194,25.65838 -4.500365,37.04128 l -0.849251,2.18899 c -7.771476,18.55174 -21.788527,30.7335 -39.685905,38.97898 -3.37688,1.34444 -5.301227,2.48389 -8.978708,2.48389 l -3.2442484,1.47057 c -4.8987376,0.0599 -14.0045433,0.089 -15.4540455,0.10655 -25.7222011,0.30356 -49.7229121,-5.03616 -68.8916011,-23.19214 -3.286346,-3.24956 -6.197141,-6.66938 -8.810303,-10.47827 -1.170385,-1.6798 -3.844026,-5.57044 -5.590218,-8.45711 -0.851523,-1.40767 -3.595298,-4.4516 -7.983006,-16.21404 -0.853709,-1.50963 -2.403339,-12.94213 -2.403339,-12.94213 -0.0915,-1.48718 -0.23669,-2.92345 -0.23801,-4.41344 l -0.01,-2.78866 0.005,-3.06828 -0.007,-3.25582 c -0.006,-3.61823 -0.006,-7.23642 -0.005,-10.85466 -0.003,-2.58667 -0.006,-5.17335 -0.0102,-7.76002 -0.008,-6.30723 -0.0111,-12.61445 -0.0116,-18.92169 -4.7e-4,-5.12481 -0.003,-10.24962 -0.006,-15.37442 -0.009,-14.52085 -0.0134,-29.0417 -0.0127,-43.56255 l 1.3e-4,-2.37302 1.2e-4,-2.3759 c 4.1e-4,-12.71422 -0.009,-25.42841 -0.0233,-38.14262 -0.0143,-13.04611 -0.0213,-26.0922 -0.0204,-39.13831 3.3e-4,-7.32798 -0.002,-14.65593 -0.0132,-21.9839 -0.01,-6.88802 -0.01,-13.77597 -0.002,-20.663998 10e-4,-2.533683 1.124,-6.567371 1.118,-9.101047 -0.008,-3.44738 -1.128,-5.394528 82.246422,49.699935 83.374422,55.09446 83.377322,60.92629 83.380322,66.75811 z"
fill="#51cdc8"
id="path2"
transform="translate(112.81113,8.4930303)"
sodipodi:nodetypes="sscccccccccccccsccccccccscsccccccccs" />
<path
d="m 0,0 c 2.3047995,-6.7559e-4 4.6095989,-0.00164773 6.9143982,-0.00289917 4.8125578,-0.00146675 9.6250968,6.4331e-4 14.4376528,0.00534058 6.108101,0.00567724 12.216159,0.00242443 18.324259,-0.00357247 4.756666,-0.00364996 9.513323,-0.00244655 14.269991,1.2779e-4 2.249005,6.5808e-4 4.498012,-1.1905e-4 6.747016,-0.00247955 10.980782,-0.00899429 21.945816,0.06768592 32.917523,0.54693985 l 2.536386,0.10305596 C 119.97909,1.8334817 140.8053,14.193522 157.08594,31.132812 c 14.55199,16.47268 20.55085,36.163532 20.75,57.8125 0.0272,1.535344 0.0557,3.070663 0.0855,4.605958 0.0684,3.811023 0.11277,7.62181 0.14563,11.43329 0.018,2.0392 0.0412,21.15431 0.0616,30.38266 0.0376,3.46864 -119.042723,1.76559 -119.042723,1.76559 l -20.85965,1.44008 -111.140349,85.55992 c -0.161213,-39.22828 -0.161213,-39.22828 -0.195313,-55.73828 -0.02368,-11.37426 -0.05144,-22.7484 -0.106445,-34.12256 -0.04005,-8.28583 -0.06572,-16.57157 -0.0746,-24.8575 -0.0052,-4.3814 -0.01724,-8.76252 -0.04657,-13.143825 -0.02746,-4.136862 -0.03546,-8.273336 -0.02951,-12.41028 -0.0011,-1.50588 -0.0089,-3.011774 -0.02439,-4.517574 -0.131739,-13.445652 1.806738,-26.659985 8.289322,-38.647479 l 1.049072,-2.008056 c 2.851223,-5.317287 6.068313,-10.084275 10.138428,-14.554444 L -51.898437,21.875 C -43.655062,13.127533 -37.90534,5.8098811 -26.601562,1.8203125 l -12.3125,0.3125 v -1 C -25.942811,-0.00501656 -13.012554,-0.01357216 0,0 Z"
fill="#fec728"
id="path3"
transform="translate(249.91406,6.8671875)"
sodipodi:nodetypes="cccccccccccccccccccccccccccccc" />
<path
d="M 122.4545,-1.5555303 C 104.75322,5.7344897 77.764999,26.892615 70.396697,44.304966 65.927561,56.152831 64.595666,67.366945 64.64297,79.919079 c -0.0088,1.568853 -0.01926,3.137697 -0.03133,4.706528 -0.02754,4.203537 -0.03127,8.406777 -0.03028,12.610391 -0.0032,4.413592 -0.02883,8.827052 -0.05198,13.240572 -0.04035,8.33285 -0.06085,16.66561 -0.07397,24.99854 -0.01594,9.49723 -0.05439,18.99434 -0.09459,28.4915 -0.08212,19.51341 -0.136384,39.02681 -0.171948,58.54036 -3.137967,0.048 -6.275837,0.0763 -9.414062,0.10156 l -2.652588,0.0413 C 23.81406,222.81977 1.5565346,207.45478 -18.375829,188.75648 l -2.718735,-2.53775 c -8.235245,-7.71396 -16.371582,-15.53064 -24.472001,-23.38588 -5.420898,-5.25288 -10.871421,-10.46504 -16.411803,-15.59199 -41.322082,-38.29606 -41.811362,-38.47284 -39.697042,-61.82222 2.114319,-23.349383 3.306846,-25.445419 3.989278,-27.72417 6.748595,-20.960043 21.800751,-37.772405 41,-48.4375003 18.641419,-9.40752742 36.480818,-9.88154862 87.01386,-9.85750697 50.533043,0.0240417 73.811792,-1.09807633 92.064262,-0.98624303"
fill="#264863"
id="path4"
transform="translate(112.81113,8.4930303)"
sodipodi:nodetypes="cccccccccccccccccccc" />
<path
d="m 0,0 131.25,-0.25 v 63.875 l 0.11035,19.705566 c 0.0181,7.998291 0.0181,7.998291 0.0214,11.753754 0.006,2.598872 0.0186,5.19694 0.0405,7.79565 0.033,3.95755 0.0333,7.91478 0.0318,11.87247 l 0.0436,3.45275 c -0.0875,17.24733 -6.73078,32.78677 -18.61099,45.21278 C 106.22208,169.37917 98.630621,174.4762 90,177 l -11.397775,1.80377 -1.45256,0.16395 c -7.104685,-0.0441 -3.43355,-0.0556 -10.538337,-0.0776 -2.653987,-0.0101 -5.307964,-0.0237 -7.961914,-0.041 -3.807645,-0.0242 -7.615144,-0.0356 -11.422852,-0.0444 l -3.614349,-0.0313 -3.336334,-4.6e-4 -2.947357,-0.0135 c -2.521553,0.0131 41.626196,-2.01422 39.819227,0.21323 L 33,179 H 30.1875 24 c -1.532555,0.0113 -3.065107,0.0231 -4.597656,0.0352 -1.821614,0.009 -3.643229,0.0184 -5.464844,0.0273 l -2.798828,0.0254 -2.6738282,0.01 -2.4494629,0.0159 C 4,179 5.0139855,178.84803 4.0508683,177.7768 l 1.9580347,1.35049 -19.547353,-0.15833 -0.795115,0.0476 L -16,179 l -3.272949,0.0317 c -3.999316,0.0361 -7.998616,0.0591 -11.998047,0.0781 -1.732447,0.0101 -3.464876,0.0237 -5.197266,0.041 -2.487184,0.0242 -4.974145,0.0356 -7.461425,0.0444 l -2.35495,0.0313 c -1.906618,4e-4 -13.996389,0.0497 -15.899078,-0.0727 l 18.898307,-4.96534 C -32.858078,170.63158 -17.423649,159.0936 -8,141 c 5.0244847,-11.01947 7.29498468,-20.88967 7.31884766,-32.92383 l 0.0307674,-3.60735 c 0.0305076,-3.83335 0.0477557,-7.666634 0.0644474,-11.50007 0.0169446,-2.374681 0.0347624,-4.749356 0.0534668,-7.124023 C -0.4694063,77.063221 -0.4232179,68.2816 -0.375,59.5 Z"
fill="#773aaa"
id="path5"
transform="translate(177,231)"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccc" />
<path
d="m 130.75,72.125 c 0,0 -130.9459176,38.46063 -130.5354588,-0.05093 L 130.75,72.125 l 0.5,86.75 L 0,159 0.2145412,72.074065"
fill="#ef7a34"
id="path6"
transform="translate(177,72)"
sodipodi:nodetypes="cccccc" />
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -31,6 +31,7 @@
<link rel="apple-touch-icon" sizes="144x144" href="icons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-icon-180x180.png">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="icon" type="image/png" sizes="192x192" href="icons/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">