Geometry update, update quill editor, fix save in multiple new features + MISC

This commit is contained in:
Thomas Fransolet 2026-03-06 15:23:10 +01:00
parent de3f89e038
commit 3520d2d3d2
19 changed files with 1137 additions and 737 deletions

View File

@ -57,12 +57,24 @@ class _MapGeometryPickerState extends State<MapGeometryPicker> {
if (currentType == "Point") {
var coords = widget.initialGeometry!.coordinates as List<dynamic>;
points = [LatLng(coords[0].toDouble(), coords[1].toDouble())];
} else if (currentType == "LineString" || currentType == "Polygon") {
} else if (currentType == "LineString") {
var list = widget.initialGeometry!.coordinates as List<dynamic>;
points = list.map((e) {
var pair = e as List<dynamic>;
return LatLng(pair[0].toDouble(), pair[1].toDouble());
}).toList();
} else if (currentType == "Polygon") {
// Polygon coordinates: [[[lat,lng],...]] first element is exterior ring
var rings = widget.initialGeometry!.coordinates as List<dynamic>;
var ring = rings[0] as List<dynamic>;
points = ring.map((e) {
var pair = e as List<dynamic>;
return LatLng(pair[0].toDouble(), pair[1].toDouble());
}).toList();
// Remove closing point if it duplicates the first
if (points.length > 1 && points.first == points.last) {
points.removeLast();
}
}
} catch (e) {
print("Error parsing geometry: $e");
@ -87,6 +99,12 @@ class _MapGeometryPickerState extends State<MapGeometryPicker> {
? [points[0].latitude, points[0].longitude]
: null,
);
} else if (currentType == "Polygon") {
// Polygon: [[[lat,lng],...]] wrap ring in outer array
return GeometryDTO(
type: "Polygon",
coordinates: [points.map((e) => [e.latitude, e.longitude]).toList()],
);
} else {
return GeometryDTO(
type: currentType,

View File

@ -11,19 +11,28 @@ showMultiStringInputHTML (String label, String modalLabel, bool isTitle, List<Tr
showDialog(
useRootNavigator: false,
builder: (BuildContext context) {
return AlertDialog(
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))
),
title: Center(child: Text(modalLabel)),
content: SingleChildScrollView(
child: TranslationInputContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes)
insetPadding: const EdgeInsets.symmetric(horizontal: 60, vertical: 24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 800),
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))),
const SizedBox(height: 16),
SingleChildScrollView(
child: TranslationInputContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes),
),
actions: <Widget>[
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
SizedBox(
width: 180,
height: 70,
child: RoundedButton(
@ -37,7 +46,7 @@ showMultiStringInputHTML (String label, String modalLabel, bool isTitle, List<Tr
fontSize: 20,
),
),
Container(
SizedBox(
width: 180,
height: 70,
child: RoundedButton(
@ -64,6 +73,9 @@ showMultiStringInputHTML (String label, String modalLabel, bool isTitle, List<Tr
],
),
],
),
),
),
);
}, context: context
);
@ -73,19 +85,28 @@ showMultiStringInputAndResourceHTML (String label, String modalLabel, bool isTit
showDialog(
useRootNavigator: false,
builder: (BuildContext context) {
return AlertDialog(
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))
),
title: Center(child: Text(modalLabel)),
content: SingleChildScrollView(
child: TranslationInputAndResourceContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes)
insetPadding: const EdgeInsets.symmetric(horizontal: 60, vertical: 24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 800),
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))),
const SizedBox(height: 16),
SingleChildScrollView(
child: TranslationInputAndResourceContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes),
),
actions: <Widget>[
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
SizedBox(
width: 180,
height: 70,
child: RoundedButton(
@ -99,7 +120,7 @@ showMultiStringInputAndResourceHTML (String label, String modalLabel, bool isTit
fontSize: 20,
),
),
Container(
SizedBox(
width: 180,
height: 70,
child: RoundedButton(
@ -126,6 +147,9 @@ showMultiStringInputAndResourceHTML (String label, String modalLabel, bool isTit
],
),
],
),
),
),
);
}, context: context
);

View File

@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/Components/audio_input_container.dart';
import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart';
import 'package:provider/provider.dart';
import 'package:quill_html_editor/quill_html_editor.dart';
import 'flag_decoration.dart';
@ -28,225 +27,224 @@ class TranslationInputAndResourceContainer extends StatefulWidget {
List<ResourceType>? resourceTypes;
@override
State<TranslationInputAndResourceContainer> createState() => _TranslationInputAndResourceContainerState();
State<TranslationInputAndResourceContainer> createState() =>
_TranslationInputAndResourceContainerState();
}
class _TranslationInputAndResourceContainerState extends State<TranslationInputAndResourceContainer> with TickerProviderStateMixin {
TabController? _tabController;
QuillEditorController controllerQuill = QuillEditorController();
ValueNotifier<String?>? currentLanguage;
bool isInit = false;
class _TranslationInputAndResourceContainerState
extends State<TranslationInputAndResourceContainer> {
late Map<String, QuillController> _controllers;
bool _isEnforcingLimit = false;
@override
void initState() {
super.initState();
_tabController = new TabController(length: widget.newValues.length, vsync: this);
currentLanguage = ValueNotifier<String>(widget.newValues.first.language!);
controllerQuill.onEditorLoaded(() {
isInit = true;
});
Future.delayed(Duration(milliseconds: 500), () {
controllerQuill.clear();
controllerQuill.insertText(widget.newValues[_tabController!.index].value!);
isInit = true;
});
controllerQuill.onTextChanged((p0) async {
var plainText = await controllerQuill.getPlainText();
if(widget.isTitle) {
if(plainText.length > kTitleMaxLength) {
controllerQuill.undo();
_controllers = _buildControllers();
}
} else {
if(plainText.length > kDescriptionMaxLength) {
print("to much text description au dessus");
controllerQuill.undo();
static const _emptyDelta = [{'insert': '\n'}];
List<dynamic> _htmlToDeltaJson(String html) {
if (html.trim().isEmpty) return _emptyDelta;
final ops = HtmlToDelta().convert(html).toJson();
return ops.isEmpty ? _emptyDelta : ops;
}
Map<String, QuillController> _buildControllers() {
final map = <String, QuillController>{};
for (final translation in widget.newValues) {
final html = translation.value ?? '';
final controller = QuillController(
document: Document.fromJson(_htmlToDeltaJson(html)),
selection: const TextSelection.collapsed(offset: 0),
);
_setupListener(translation.language!, controller);
map[translation.language!] = controller;
}
return map;
}
void _setupListener(String lang, QuillController controller) {
controller.document.changes.listen((_) {
if (!mounted || _isEnforcingLimit) return;
final limit = widget.isTitle ? kTitleMaxLength : kDescriptionMaxLength;
final plain = controller.document.toPlainText().trimRight();
if (plain.length > limit) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && !_isEnforcingLimit) {
_isEnforcingLimit = true;
controller.undo();
_isEnforcingLimit = false;
}
});
_tabController!.addListener(() {
if (!_tabController!.indexIsChanging) {
setState(() {
currentLanguage!.value = widget.newValues[_tabController!.index].language;
//if(widget.resourceTypes == null) {
print("insert try without ress");
print(widget.newValues[_tabController!.index].value!);
controllerQuill.clear();
controllerQuill.insertText(widget.newValues[_tabController!.index].value!);
//}
return;
}
widget.newValues
.firstWhere((e) => e.language == lang)
.value = _controllerToHtml(controller);
});
}
});
String _controllerToHtml(QuillController controller) {
final ops = controller.document.toDelta().toJson();
return QuillDeltaToHtmlConverter(
List<Map<String, dynamic>>.from(ops),
).convert();
}
@override
void dispose() {
for (final c in _controllers.values) {
c.dispose();
}
super.dispose();
_tabController!.dispose();
}
@override
Widget build(BuildContext context) {
final customToolBarList = widget.isTitle ? [
ToolBarStyle.bold,
ToolBarStyle.italic,
ToolBarStyle.color,
ToolBarStyle.background,
ToolBarStyle.clean
] : [
ToolBarStyle.bold,
ToolBarStyle.italic,
ToolBarStyle.color,
ToolBarStyle.background,
ToolBarStyle.listBullet,
ToolBarStyle.listOrdered,
ToolBarStyle.clean
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: widget.newValues.map((t) => _buildLanguageSection(t)).toList(),
);
}
return Container(
height: widget.isTitle ? MediaQuery.of(context).size.height *0.45 : MediaQuery.of(context).size.height *0.5,
//color: Colors.orange,
width: MediaQuery.of(context).size.width *0.7,
constraints: BoxConstraints(
minHeight: 300,
minWidth: 300
),
child: DefaultTabController(
length: widget.newValues.length,
Widget _buildLanguageSection(TranslationAndResourceDTO translation) {
final lang = translation.language!;
// Resource-only mode (no text editor)
if (widget.resourceTypes != null && _controllers[lang] == null) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RotatedBox(
quarterTurns: 0, // Can be used to test vertical tab in case of smaller screen
child: TabBar(
indicatorColor: kPrimaryColor,
//overlayColor: MaterialStateProperty().c,
labelColor: kPrimaryColor,
unselectedLabelColor: Colors.black,
controller: _tabController,
tabs: widget.newValues.map((v) => Tab(icon: FlagDecoration(language: v.language!))).toList(), // text: v.language!.toUpperCase(),
_buildLangHeader(lang),
const SizedBox(height: 4),
SizedBox(
width: 250,
height: 120,
child: ResourceInputContainer(
label: "",
initialValue: translation.resourceId,
inResourceTypes: widget.resourceTypes!,
onChanged: (ResourceDTO resource) {
setState(() {
translation.resourceId =
resource.id == null ? null : resource.id;
translation.resource =
resource.id == null ? null : resource;
});
},
),
),
getTranslation(context, Provider.of<AppContext>(context), controllerQuill, customToolBarList, widget.isTitle, widget.resourceTypes, widget.newValues, currentLanguage!)
],
),
),
);
}
getTranslation(BuildContext context, AppContext appContext, QuillEditorController controllerQuill, List<ToolBarStyle> customToolBarList, bool isTitle, List<ResourceType>? resourceTypes, List<TranslationAndResourceDTO> newValues, ValueNotifier<String?> currentLanguage) {
final controller = _controllers[lang]!;
return Padding(
padding: const EdgeInsets.all(6.0),
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Container(
width: MediaQuery.of(context).size.width *0.7,
//color: Colors.blueAccent,
height: widget.isTitle ? MediaQuery.of(context).size.height *0.34 : MediaQuery.of(context).size.height *0.37,
child: resourceTypes != null ?
Column(
children: [
ToolBar(
toolBarColor: kSecond,
activeIconColor: kPrimaryColor,
padding: const EdgeInsets.all(8),
iconSize: 20,
toolBarConfig: customToolBarList,
controller: controllerQuill,
customButtons: [],
),
SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLangHeader(lang),
const SizedBox(height: 4),
QuillSimpleToolbar(
controller: controller,
config: _buildToolbarConfig(),
),
Container(
height: widget.isTitle ? MediaQuery.of(context).size.height *0.13 : MediaQuery.of(context).size.height *0.2,
child: QuillHtmlEditor(
text: newValues.where((element) => element.language! == currentLanguage.value).first.value!,
hintText: '',
controller: controllerQuill,
minHeight: widget.isTitle ? 80 : 240,
/*textStyle: _editorTextStyle,
hintTextStyle: _hintTextStyle,*/
hintTextAlign: TextAlign.start,
padding: const EdgeInsets.only(left: 10, right: 10, top: 5),
hintTextPadding: EdgeInsets.zero,
backgroundColor: kBackgroundColor,
ensureVisible: true,
inputAction: widget.isTitle ? InputAction.send : InputAction.newline, // don't accept enter if title
//onFocusChanged: (hasFocus) => debugPrint('has focus $hasFocus'),
//onTextChanged: (text) => debugPrint('widget text change $text'),
onTextChanged: (value) {
if(isInit) {
newValues.where((element) => element.language! == currentLanguage.value).first.value = value;
}
},
onEditorCreated: () => debugPrint('Editor has been loaded'),
onEditorResized: (height) =>
debugPrint('Editor resized $height'),
onSelectionChanged: (sel) =>
debugPrint('${sel.index},${sel.length}'),
height: widget.isTitle ? 100 : 200,
decoration: BoxDecoration(
color: kBackgroundColor,
border: Border.all(color: Colors.grey.shade300),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(4),
bottomRight: Radius.circular(4),
),
),
ValueListenableBuilder<String?>(
valueListenable: currentLanguage,
builder: (context, value, _) {
return ResourceInputContainer(
child: QuillEditor.basic(
controller: controller,
config: const QuillEditorConfig(
scrollable: true,
expands: false,
padding: EdgeInsets.all(8),
autoFocus: false,
),
),
),
if (widget.resourceTypes != null) ...[
const SizedBox(height: 8),
ResourceInputContainer(
label: "Ressource à afficher :",
initialValue: newValues.where((element) => element.language! == value).first.resourceId,
initialValue: translation.resourceId,
color: kPrimaryColor,
inResourceTypes: resourceTypes,
inResourceTypes: widget.resourceTypes!,
isLanguageTab: true,
onChanged: (ResourceDTO resource) {
setState(() {
if(resource.id == null) {
newValues.where((element) => element.language! == value).first.resourceId = null;
newValues.where((element) => element.language! == value).first.resource = null;
} else {
newValues.where((element) => element.language! == value).first.resourceId = resource.id;
newValues.where((element) => element.language! == value).first.resource = resource;
/*newValues.where((element) => element.language! == value).first.resourceUrl = resource.url;
newValues.where((element) => element.language! == value).first.resourceType = resource.type;*/
}
translation.resourceId =
resource.id == null ? null : resource.id;
translation.resource =
resource.id == null ? null : resource;
});
},
isSmall: true
);
}
isSmall: true,
),
],
],
),
);
}
Widget _buildLangHeader(String lang) {
return Row(
children: [
FlagDecoration(language: lang),
const SizedBox(width: 8),
Text(
lang.toUpperCase(),
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14),
),
],
) :
Container(
width: 250,
height: 120,
child: ValueListenableBuilder<String?>(
valueListenable: currentLanguage,
builder: (context, value, _) {
return ResourceInputContainer(
label: "",
initialValue: newValues.where((element) => element.language! == value).first.resourceId,
inResourceTypes: widget.resourceTypes!,
onChanged: (ResourceDTO resource) {
newValues.where((element) => element.language! == value).first.value = resource.id;
}
);
return AudioInputContainer(
initialValue: newValues.where((element) => element.language! == value).first.value,
color: kPrimaryColor,
onChanged: (ResourceDTO resource) {
newValues.where((element) => element.language! == value).first.value = resource.id;
},
);
}
),
),
),
),
QuillSimpleToolbarConfig _buildToolbarConfig() {
return QuillSimpleToolbarConfig(
showBoldButton: true,
showItalicButton: true,
showColorButton: true,
showBackgroundColorButton: true,
showListBullets: !widget.isTitle,
showListNumbers: !widget.isTitle,
showListCheck: false,
showClearFormat: true,
showUnderLineButton: false,
showStrikeThrough: false,
showInlineCode: false,
showSubscript: false,
showSuperscript: false,
showSmallButton: false,
showLineHeightButton: false,
showHeaderStyle: false,
showLink: false,
showSearchButton: false,
showQuote: false,
showCodeBlock: false,
showIndent: false,
showAlignmentButtons: false,
showLeftAlignment: false,
showCenterAlignment: false,
showRightAlignment: false,
showJustifyAlignment: false,
showDirection: false,
showUndo: false,
showRedo: false,
showClipboardCut: false,
showClipboardCopy: false,
showClipboardPaste: false,
);
}
}

View File

@ -1,257 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/Components/audio_input_container.dart';
import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart';
import 'package:provider/provider.dart';
import 'package:quill_html_editor/quill_html_editor.dart';
import 'flag_decoration.dart';
import 'message_notification.dart';
class _TranslationInputContainerState extends State<TranslationInputContainer> with TickerProviderStateMixin {
TabController? _tabController;
QuillEditorController controllerQuill = QuillEditorController();
ValueNotifier<String?>? currentLanguage;
bool isInit = false;
@override
void initState() {
super.initState();
_tabController = new TabController(length: widget.newValues.length, vsync: this);
currentLanguage = ValueNotifier<String>(widget.newValues.first.language!);
controllerQuill.onEditorLoaded(() {
isInit = true;
});
Future.delayed(Duration(milliseconds: 500), () {
controllerQuill.clear();
controllerQuill.insertText(widget.newValues[_tabController!.index].value!);
isInit = true;
});
controllerQuill.onTextChanged((p0) async {
var plainText = await controllerQuill.getPlainText();
if(widget.isTitle) {
if(plainText.length > kTitleMaxLength) {
controllerQuill.undo();
}
} else {
if(plainText.length > kDescriptionMaxLength) {
controllerQuill.undo();
}
}
});
_tabController!.addListener(() {
if (!_tabController!.indexIsChanging) {
setState(() {
currentLanguage!.value = widget.newValues[_tabController!.index].language;
if(widget.resourceTypes == null) {
print("insert try without ress");
print(widget.newValues[_tabController!.index].value!);
controllerQuill.clear();
controllerQuill.insertText(widget.newValues[_tabController!.index].value!);
}
});
}
});
}
@override
void dispose() {
super.dispose();
_tabController!.dispose();
}
getTranslation(BuildContext context, AppContext appContext, QuillEditorController controllerQuill, List<ToolBarStyle> customToolBarList, bool isTitle, List<ResourceType>? resourceTypes, List<TranslationDTO> newValues, ValueNotifier<String?> currentLanguage) {
return Padding(
padding: const EdgeInsets.all(6.0),
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Container(
width: MediaQuery.of(context).size.width *0.7,
height: widget.isTitle ? MediaQuery.of(context).size.height *0.25 : MediaQuery.of(context).size.height *0.4,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
resourceTypes == null ?
Column(
children: [
ToolBar(
toolBarColor: kSecond,
activeIconColor: kPrimaryColor,
padding: const EdgeInsets.all(8),
iconSize: 20,
toolBarConfig: customToolBarList,
controller: controllerQuill,
customButtons: [],
),
SingleChildScrollView(
child: Container(
height: widget.isTitle ? MediaQuery.of(context).size.height *0.13 : MediaQuery.of(context).size.height *0.35,
child: QuillHtmlEditor(
//text: newValues.where((element) => element.language! == currentLanguage.value).first.value!,
hintText: '',
controller: controllerQuill,
minHeight: widget.isTitle ? 80 : 240,
/*textStyle: _editorTextStyle,
hintTextStyle: _hintTextStyle,*/
hintTextAlign: TextAlign.start,
padding: const EdgeInsets.only(left: 10, right: 10, top: 5),
hintTextPadding: EdgeInsets.zero,
backgroundColor: kBackgroundColor,
ensureVisible: true,
inputAction: widget.isTitle ? InputAction.send : InputAction.newline, // don't accept enter if title
//onFocusChanged: (hasFocus) => debugPrint('has focus $hasFocus'),
//onTextChanged: (text) => debugPrint('widget text change $text'),
onTextChanged: (value) {
if(isInit) {
newValues.where((element) => element.language! == currentLanguage.value).first.value = value;
}
},
onEditorCreated: () => debugPrint('Editor has been loaded'),
onEditorResized: (height) =>
debugPrint('Editor resized $height'),
onSelectionChanged: (sel) =>
debugPrint('${sel.index},${sel.length}'),
),
),
),
],
)
/*HtmlEditor(
controller: controller,
htmlEditorOptions: HtmlEditorOptions(
hint: "Your text here...",
initialText: newValues.where((element) => element.language == language).first.value!,
shouldEnsureVisible: true,
),
htmlToolbarOptions: HtmlToolbarOptions(
toolbarPosition: ToolbarPosition.aboveEditor, //required to place toolbar anywhere!
//other options
),
otherOptions: OtherOptions(
height: 400,
),
)*/
/*TextFormInputContainer(
label: label,
color: kWhite,
isTitle: isTitle,
initialValue: newValues.where((element) => element.language == language).first.value!,
onChanged: (value) {
newValues.where((element) => element.language == language).first.value = value;
},
)*/ :
Container(
width: 250,
height: 120,
child: ValueListenableBuilder<String?>(
valueListenable: currentLanguage,
builder: (context, value, _) {
return ResourceInputContainer(
label: "",
initialValue: newValues.where((element) => element.language! == value).first.value,
inResourceTypes: widget.resourceTypes!,
onChanged: (ResourceDTO resource) {
newValues.where((element) => element.language! == value).first.value = resource.id;
}
);
/*return AudioInputContainer(
//label: "Audio :",
initialValue: newValues.where((element) => element.language! == value).first.value,
color: kPrimaryColor,
onChanged: (ResourceDTO resource) {
newValues.where((element) => element.language! == value).first.value = resource.id;
},
);*/
},
),
),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
final customToolBarList = widget.isTitle ? [
ToolBarStyle.bold,
ToolBarStyle.italic,
ToolBarStyle.color,
ToolBarStyle.background,
ToolBarStyle.clean
] : [
ToolBarStyle.bold,
ToolBarStyle.italic,
ToolBarStyle.color,
ToolBarStyle.background,
ToolBarStyle.listBullet,
ToolBarStyle.listOrdered,
ToolBarStyle.clean
];
return Container(
height: widget.isTitle ? MediaQuery.of(context).size.height *0.4 : MediaQuery.of(context).size.height *0.53,
width: MediaQuery.of(context).size.width *0.7,
constraints: BoxConstraints(
minHeight: 200,
minWidth: 300
),
child: Column(
children: [
DefaultTabController(
length: widget.newValues.length,
child: Column(
children: [
RotatedBox(
quarterTurns: 0, // Can be used to test vertical tab in case of smaller screen
child: TabBar(
indicatorColor: kPrimaryColor,
//overlayColor: MaterialStateProperty().c,
labelColor: kPrimaryColor,
unselectedLabelColor: Colors.black,
controller: _tabController,
tabs: widget.newValues.map((v) => Tab(icon: FlagDecoration(language: v.language!))).toList(), // text: v.language!.toUpperCase(),
),
),
getTranslation(context, Provider.of<AppContext>(context), controllerQuill, customToolBarList, widget.isTitle, widget.resourceTypes, widget.newValues, currentLanguage!)
],
),
),
Center(
child: Container(
width: 370,
height: 70,
child: RoundedButton(
text: "Appliquer à toutes les langues",
icon: Icons.copy,
color: kSecond,
press: () async {
var plainText = await controllerQuill.getText();
widget.newValues.forEach((language) {
language.value = plainText;
}
);
showNotification(kSuccess, kWhite, 'Le texte a été appliqué à toutes les langues', context, null);
},
fontSize: 20,
),
),
),
],
),
);
}
}
class TranslationInputContainer extends StatefulWidget {
TranslationInputContainer({
Key? key,
@ -273,3 +31,235 @@ class TranslationInputContainer extends StatefulWidget {
@override
State<TranslationInputContainer> createState() => _TranslationInputContainerState();
}
class _TranslationInputContainerState extends State<TranslationInputContainer> {
late Map<String, QuillController> _controllers;
bool _isEnforcingLimit = false;
@override
void initState() {
super.initState();
_controllers = _buildControllers();
}
static const _emptyDelta = [{'insert': '\n'}];
List<dynamic> _htmlToDeltaJson(String html) {
if (html.trim().isEmpty) return _emptyDelta;
final ops = HtmlToDelta().convert(html).toJson();
return ops.isEmpty ? _emptyDelta : ops;
}
Map<String, QuillController> _buildControllers() {
final map = <String, QuillController>{};
for (final translation in widget.newValues) {
final html = translation.value ?? '';
final controller = QuillController(
document: Document.fromJson(_htmlToDeltaJson(html)),
selection: const TextSelection.collapsed(offset: 0),
);
_setupListener(translation.language!, controller);
map[translation.language!] = controller;
}
return map;
}
void _setupListener(String lang, QuillController controller) {
controller.document.changes.listen((_) {
if (!mounted || _isEnforcingLimit) return;
final limit = widget.isTitle ? kTitleMaxLength : kDescriptionMaxLength;
final plain = controller.document.toPlainText().trimRight();
if (plain.length > limit) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && !_isEnforcingLimit) {
_isEnforcingLimit = true;
controller.undo();
_isEnforcingLimit = false;
}
});
return;
}
widget.newValues
.firstWhere((e) => e.language == lang)
.value = _controllerToHtml(controller);
});
}
String _controllerToHtml(QuillController controller) {
final ops = controller.document.toDelta().toJson();
return QuillDeltaToHtmlConverter(
List<Map<String, dynamic>>.from(ops),
).convert();
}
void _applyToAllLanguages() {
if (_controllers.isEmpty) return;
final firstLang = widget.newValues.first.language!;
final html = _controllerToHtml(_controllers[firstLang]!);
setState(() {
for (final translation in widget.newValues) {
translation.value = html;
if (translation.language != firstLang) {
_controllers[translation.language!]?.dispose();
final controller = QuillController(
document: Document.fromJson(_htmlToDeltaJson(html)),
selection: const TextSelection.collapsed(offset: 0),
);
_setupListener(translation.language!, controller);
_controllers[translation.language!] = controller;
}
}
});
showNotification(kSuccess, kWhite, 'Le texte a été appliqué à toutes les langues', context, null);
}
@override
void dispose() {
for (final c in _controllers.values) {
c.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...widget.newValues.map((t) => _buildLanguageSection(t)),
const SizedBox(height: 8),
if (widget.resourceTypes == null)
Center(
child: SizedBox(
width: 370,
height: 70,
child: RoundedButton(
text: "Appliquer à toutes les langues",
icon: Icons.copy,
color: kSecond,
press: _applyToAllLanguages,
fontSize: 20,
),
),
),
],
);
}
Widget _buildLanguageSection(TranslationDTO translation) {
final lang = translation.language!;
if (widget.resourceTypes != null) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLangHeader(lang),
const SizedBox(height: 4),
SizedBox(
width: 250,
height: 120,
child: ResourceInputContainer(
label: "",
initialValue: translation.value,
inResourceTypes: widget.resourceTypes!,
onChanged: (ResourceDTO resource) {
translation.value = resource.id;
},
),
),
],
),
);
}
final controller = _controllers[lang]!;
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLangHeader(lang),
const SizedBox(height: 4),
QuillSimpleToolbar(
controller: controller,
config: _buildToolbarConfig(),
),
Container(
height: widget.isTitle ? 100 : 200,
decoration: BoxDecoration(
color: kBackgroundColor,
border: Border.all(color: Colors.grey.shade300),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(4),
bottomRight: Radius.circular(4),
),
),
child: QuillEditor.basic(
controller: controller,
config: const QuillEditorConfig(
scrollable: true,
expands: false,
padding: EdgeInsets.all(8),
autoFocus: false,
),
),
),
],
),
);
}
Widget _buildLangHeader(String lang) {
return Row(
children: [
FlagDecoration(language: lang),
const SizedBox(width: 8),
Text(
lang.toUpperCase(),
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14),
),
],
);
}
QuillSimpleToolbarConfig _buildToolbarConfig() {
return QuillSimpleToolbarConfig(
showBoldButton: true,
showItalicButton: true,
showColorButton: true,
showBackgroundColorButton: true,
showListBullets: !widget.isTitle,
showListNumbers: !widget.isTitle,
showListCheck: false,
showClearFormat: true,
showUnderLineButton: false,
showStrikeThrough: false,
showInlineCode: false,
showSubscript: false,
showSuperscript: false,
showSmallButton: false,
showLineHeightButton: false,
showHeaderStyle: false,
showLink: false,
showSearchButton: false,
showQuote: false,
showCodeBlock: false,
showIndent: false,
showAlignmentButtons: false,
showLeftAlignment: false,
showCenterAlignment: false,
showRightAlignment: false,
showJustifyAlignment: false,
showDirection: false,
showUndo: false,
showRedo: false,
showClipboardCut: false,
showClipboardCopy: false,
showClipboardPaste: false,
);
}
}

View File

@ -4,6 +4,11 @@ import 'package:manager_app/constants.dart';
import 'package:manager_app/Components/check_input_container.dart';
import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/single_select_container.dart';
import 'package:manager_app/Components/message_notification.dart';
import 'package:provider/provider.dart';
import 'package:manager_app/app_context.dart';
import 'package:manager_app/Models/managerContext.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'showNewOrUpdateEventAgenda.dart';
class AgendaConfig extends StatefulWidget {
@ -26,11 +31,38 @@ class AgendaConfig extends StatefulWidget {
class _AgendaConfigState extends State<AgendaConfig> {
late AgendaDTO agendaDTO;
List<EventAgendaDTO> events = [];
@override
void initState() {
super.initState();
agendaDTO = widget.initialValue;
WidgetsBinding.instance.addPostFrameCallback((_) => _loadFromApi());
}
Future<void> _loadFromApi() async {
if (agendaDTO.id == null || !mounted) return;
final appContext = Provider.of<AppContext>(context, listen: false);
final api = (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionAgendaApi!;
try {
final fetched =
await api.sectionAgendaGetAllEventAgendaFromSection(agendaDTO.id!);
if (fetched == null || !mounted) return;
setState(() {
events = List.from(fetched);
});
} catch (e) {
// Silently keep empty on error
}
}
SectionAgendaApi _api(BuildContext ctx) {
final appContext = Provider.of<AppContext>(ctx, listen: false);
return (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionAgendaApi!;
}
@override
@ -51,6 +83,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildAgendaHeader(size, mapProviderIn),
if (agendaDTO.isOnlineAgenda == false) ...[
@ -67,19 +100,32 @@ class _AgendaConfigState extends State<AgendaConfig> {
ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text("Ajouter un évènement"),
onPressed: () {
onPressed: agendaDTO.id == null
? null
: () {
showNewOrUpdateEventAgenda(
context,
null,
agendaDTO.id ?? "temp",
(newEvent) {
setState(() {
agendaDTO.events = [
...(agendaDTO.events ?? []),
newEvent
];
widget.onChanged(agendaDTO);
});
agendaDTO.id!,
(newEvent) async {
try {
final created = await _api(context)
.sectionAgendaCreateEventAgenda(
agendaDTO.id!, newEvent);
if (created != null && mounted) {
setState(() => events.add(created));
showNotification(kSuccess, kWhite,
'Évènement créé avec succès', context, null);
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la création de l\'évènement',
context,
null);
rethrow;
}
},
);
},
@ -98,14 +144,14 @@ class _AgendaConfigState extends State<AgendaConfig> {
Container(
height: 600,
padding: const EdgeInsets.symmetric(vertical: 8),
child: (agendaDTO.events == null || agendaDTO.events!.isEmpty)
child: events.isEmpty
? const Center(
child: Text("Aucun évènement manuel",
style: TextStyle(fontStyle: FontStyle.italic)))
: ListView.builder(
itemCount: agendaDTO.events!.length,
itemCount: events.length,
itemBuilder: (context, index) {
final event = agendaDTO.events![index];
final event = events[index];
return Card(
elevation: 2,
margin: const EdgeInsets.symmetric(
@ -120,13 +166,13 @@ class _AgendaConfigState extends State<AgendaConfig> {
child:
const Icon(Icons.event, color: kPrimaryColor),
),
title: Text(
title: HtmlWidget(
(event.label != null && event.label!.isNotEmpty)
? (event.label!.firstWhere(
(t) => t.language == 'FR',
orElse: () => event.label![0])).value!
orElse: () => event.label![0])).value ?? "Évènement $index"
: "Évènement $index",
style: const TextStyle(fontWeight: FontWeight.bold),
textStyle: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4),
@ -143,23 +189,61 @@ class _AgendaConfigState extends State<AgendaConfig> {
showNewOrUpdateEventAgenda(
context,
event,
agendaDTO.id ?? "temp",
(updatedEvent) {
setState(() {
agendaDTO.events![index] = updatedEvent;
widget.onChanged(agendaDTO);
});
agendaDTO.id ?? "",
(updatedEvent) async {
try {
final result = await _api(context)
.sectionAgendaUpdateEventAgenda(
updatedEvent);
if (result != null && mounted) {
setState(
() => events[index] = result);
showNotification(
kSuccess,
kWhite,
'Évènement mis à jour avec succès',
context,
null);
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la mise à jour de l\'évènement',
context,
null);
rethrow;
}
},
);
},
),
IconButton(
icon: const Icon(Icons.delete, color: kError),
onPressed: () {
setState(() {
agendaDTO.events!.removeAt(index);
widget.onChanged(agendaDTO);
});
onPressed: () async {
try {
if (event.id != null) {
await _api(context)
.sectionAgendaDeleteEventAgenda(
event.id!);
}
if (mounted) {
setState(() => events.removeAt(index));
showNotification(
kSuccess,
kWhite,
'Évènement supprimé avec succès',
context,
null);
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la suppression de l\'évènement',
context,
null);
}
},
),
],

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
@ -13,7 +14,7 @@ void showNewOrUpdateEventAgenda(
Function(EventAgendaDTO) onSave,
) {
EventAgendaDTO workingEvent = event != null
? EventAgendaDTO.fromJson(event.toJson())!
? EventAgendaDTO.fromJson(jsonDecode(jsonEncode(event)))!
: EventAgendaDTO(
label: [],
description: [],
@ -74,6 +75,7 @@ void showNewOrUpdateEventAgenda(
setState(() => workingEvent.label = val),
maxLines: 1,
isTitle: true,
isHTML: true,
),
),
SizedBox(width: 20),
@ -87,6 +89,7 @@ void showNewOrUpdateEventAgenda(
() => workingEvent.description = val),
maxLines: 5,
isTitle: false,
isHTML: true,
),
),
],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
import 'package:intl/intl.dart';
@ -50,6 +51,7 @@ class _EventConfigState extends State<EventConfig> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
@ -198,11 +200,13 @@ class _EventConfigState extends State<EventConfig> {
null,
(newBlock) async {
try {
// The API expects ProgrammeBlockDTO, while eventDTO.programme is List<ProgrammeBlock>
// Usually they are structural equivalents in these generated APIs, but let's be safe.
final programmeBlockDTO =
ProgrammeBlockDTO.fromJson(newBlock.toJson());
if (programmeBlockDTO == null) return;
final programmeBlockDTO = ProgrammeBlockDTO(
id: newBlock.id,
title: newBlock.title,
description: newBlock.description,
startTime: newBlock.startTime,
endTime: newBlock.endTime,
);
final createdBlockDTO =
await (appContext.getContext() as ManagerAppContext)
@ -212,10 +216,13 @@ class _EventConfigState extends State<EventConfig> {
eventDTO.id!, programmeBlockDTO);
if (createdBlockDTO != null) {
// Convert back if necessary
final createdBlock =
ProgrammeBlock.fromJson(createdBlockDTO.toJson());
if (createdBlock != null) {
final createdBlock = ProgrammeBlock(
id: createdBlockDTO.id,
title: createdBlockDTO.title,
description: createdBlockDTO.description,
startTime: createdBlockDTO.startTime,
endTime: createdBlockDTO.endTime,
);
setState(() {
eventDTO.programme = [
...(eventDTO.programme ?? []),
@ -230,7 +237,6 @@ class _EventConfigState extends State<EventConfig> {
context,
null);
}
}
} catch (e) {
showNotification(
kError,
@ -248,7 +254,8 @@ class _EventConfigState extends State<EventConfig> {
],
),
),
Expanded(
Container(
height: 600,
child: (eventDTO.programme == null || eventDTO.programme!.isEmpty)
? Center(
child: Text("Aucun bloc de programme défini",
@ -260,16 +267,18 @@ class _EventConfigState extends State<EventConfig> {
return Card(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
title: Text(
block.title != null && block.title!.isNotEmpty
? block.title!
.firstWhere((t) => t.language == 'FR',
title: block.title != null && block.title!.isNotEmpty
? HtmlWidget(
block.title!
.firstWhere(
(t) => t.language == 'FR',
orElse: () => block.title![0])
.value ??
"Bloc ${index + 1}"
: "Bloc ${index + 1}"),
"Bloc ${index + 1}",
)
: Text("Bloc ${index + 1}"),
subtitle: Text(
"${block.startTime != null ? DateFormat('HH:mm').format(block.startTime!) : '??'} - ${block.endTime != null ? DateFormat('HH:mm').format(block.endTime!) : '??'}"),
"${block.startTime != null ? DateFormat('HH:mm').format(block.startTime!.toLocal()) : '??'} - ${block.endTime != null ? DateFormat('HH:mm').format(block.endTime!.toLocal()) : '??'}"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -284,10 +293,13 @@ class _EventConfigState extends State<EventConfig> {
block,
(updatedBlock) async {
try {
final programmeBlockDTO =
ProgrammeBlockDTO.fromJson(
updatedBlock.toJson());
if (programmeBlockDTO == null) return;
final programmeBlockDTO = ProgrammeBlockDTO(
id: updatedBlock.id,
title: updatedBlock.title,
description: updatedBlock.description,
startTime: updatedBlock.startTime,
endTime: updatedBlock.endTime,
);
final resultDTO =
await (appContext.getContext()
@ -298,9 +310,13 @@ class _EventConfigState extends State<EventConfig> {
programmeBlockDTO);
if (resultDTO != null) {
final result = ProgrammeBlock.fromJson(
resultDTO.toJson());
if (result != null) {
final result = ProgrammeBlock(
id: resultDTO.id,
title: resultDTO.title,
description: resultDTO.description,
startTime: resultDTO.startTime,
endTime: resultDTO.endTime,
);
setState(() {
eventDTO.programme![index] = result;
widget.onChanged(eventDTO);
@ -312,7 +328,6 @@ class _EventConfigState extends State<EventConfig> {
context,
null);
}
}
} catch (e) {
showNotification(
kError,

View File

@ -11,7 +11,13 @@ void showNewOrUpdateProgrammeBlock(
Function(ProgrammeBlock) onSave,
) {
ProgrammeBlock workingBlock = block != null
? ProgrammeBlock.fromJson(block.toJson())!
? ProgrammeBlock(
id: block.id,
title: List<TranslationDTO>.from(block.title ?? []),
description: List<TranslationDTO>.from(block.description ?? []),
startTime: block.startTime,
endTime: block.endTime,
)
: ProgrammeBlock(
title: [],
description: [],
@ -69,6 +75,7 @@ void showNewOrUpdateProgrammeBlock(
setState(() => workingBlock.title = val),
maxLines: 1,
isTitle: true,
isHTML: true,
),
),
SizedBox(width: 20),
@ -82,6 +89,7 @@ void showNewOrUpdateProgrammeBlock(
() => workingBlock.description = val),
maxLines: 1,
isTitle: false,
isHTML: true,
),
),
],
@ -99,7 +107,7 @@ void showNewOrUpdateProgrammeBlock(
subtitle: Text(
workingBlock.startTime != null
? DateFormat('HH:mm')
.format(workingBlock.startTime!)
.format(workingBlock.startTime!.toLocal())
: "Non définie",
style: TextStyle(
color: kPrimaryColor,
@ -109,7 +117,7 @@ void showNewOrUpdateProgrammeBlock(
TimeOfDay? time = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(
workingBlock.startTime ??
workingBlock.startTime?.toLocal() ??
DateTime.now()),
builder: (context, child) {
return Theme(
@ -148,7 +156,7 @@ void showNewOrUpdateProgrammeBlock(
subtitle: Text(
workingBlock.endTime != null
? DateFormat('HH:mm')
.format(workingBlock.endTime!)
.format(workingBlock.endTime!.toLocal())
: "Non définie",
style: TextStyle(
color: kPrimaryColor,
@ -158,7 +166,7 @@ void showNewOrUpdateProgrammeBlock(
TimeOfDay? time = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(
workingBlock.endTime ??
workingBlock.endTime?.toLocal() ??
DateTime.now()
.add(Duration(hours: 1))),
builder: (context, child) {

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart';
@ -40,15 +41,18 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
Future<void> _loadFromApi() async {
final appContext = Provider.of<AppContext>(context, listen: false);
final api = (appContext.getContext() as ManagerAppContext).clientAPI!.sectionMapApi!;
final api = (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionMapApi!;
try {
// Backend already includes steps + quiz questions via .Include()
final fetchedPaths = await api.sectionMapGetAllGuidedPathFromSection(widget.parentId);
final fetchedPaths =
await api.sectionMapGetAllGuidedPathFromSection(widget.parentId);
if (fetchedPaths == null || !mounted) return;
fetchedPaths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
setState(() {
paths = fetchedPaths;
paths = List.from(fetchedPaths);
});
} catch (e) {
// Silently keep initial value on error
@ -81,7 +85,9 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
(newPath) async {
try {
newPath.order = paths.length;
newPath.instanceId = (appContext.getContext() as ManagerAppContext).instanceId;
newPath.instanceId =
(appContext.getContext() as ManagerAppContext)
.instanceId;
if (widget.isEscapeMode) {
newPath.sectionGameId = widget.parentId;
} else if (widget.isEvent) {
@ -89,6 +95,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
} else {
newPath.sectionMapId = widget.parentId;
}
final createdPath =
await (appContext.getContext() as ManagerAppContext)
.clientAPI!
@ -97,10 +104,12 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
widget.parentId, newPath);
if (createdPath != null) {
if (mounted) {
setState(() {
paths.add(createdPath);
widget.onChanged(paths);
});
}
showNotification(kSuccess, kWhite,
'Parcours créé avec succès', context, null);
}
@ -111,6 +120,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
'Erreur lors de la création du parcours',
context,
null);
rethrow; // Important so showNewOrUpdateGuidedPath knows it failed
}
},
);
@ -169,13 +179,15 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
child: Text("${index + 1}"),
backgroundColor: kPrimaryColor,
foregroundColor: kWhite),
title: Text(path.title != null && path.title!.isNotEmpty
? path.title!
title: path.title != null && path.title!.isNotEmpty
? HtmlWidget(
path.title!
.firstWhere((t) => t.language == 'FR',
orElse: () => path.title![0])
.value ??
"Parcours sans titre"
: "Parcours sans titre"),
"Parcours sans titre",
)
: Text("Parcours sans titre"),
subtitle: Text("${path.steps?.length ?? 0} étapes"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
@ -194,19 +206,20 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
widget.isEscapeMode,
(updatedPath) async {
try {
final result =
await (appContext.getContext()
final api = (appContext.getContext()
as ManagerAppContext)
.clientAPI!
.sectionMapApi!
.sectionMapUpdateGuidedPath(
.sectionMapApi!;
final result =
await api.sectionMapUpdateGuidedPath(
updatedPath);
if (result != null) {
if (mounted) {
setState(() {
paths[index] = result;
widget.onChanged(paths);
});
}
showNotification(
kSuccess,
kWhite,
@ -221,6 +234,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
'Erreur lors de la mise à jour du parcours',
context,
null);
rethrow;
}
},
);

View File

@ -1,5 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
import 'package:manager_app/Components/rounded_button.dart';
@ -13,7 +15,7 @@ void showNewOrUpdateGuidedPath(
String parentId,
bool isEvent,
bool isEscapeMode,
Function(GuidedPathDTO) onSave,
FutureOr<void> Function(GuidedPathDTO) onSave,
) {
GuidedPathDTO workingPath = path != null
? GuidedPathDTO.fromJson(jsonDecode(jsonEncode(path)))!
@ -24,8 +26,11 @@ void showNewOrUpdateGuidedPath(
order: 0,
);
bool isSaving = false;
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
@ -76,6 +81,7 @@ void showNewOrUpdateGuidedPath(
setState(() => workingPath.title = val),
maxLines: 1,
isTitle: true,
isHTML: true,
),
),
SizedBox(width: 20),
@ -89,6 +95,7 @@ void showNewOrUpdateGuidedPath(
() => workingPath.description = val),
maxLines: 1,
isTitle: false,
isHTML: true,
),
),
],
@ -150,7 +157,7 @@ void showNewOrUpdateGuidedPath(
null,
workingPath.id ?? "temp",
isEscapeMode,
(newStep) {
(newStep) async {
setState(() {
workingPath.steps = [
...(workingPath.steps ?? []),
@ -183,7 +190,7 @@ void showNewOrUpdateGuidedPath(
return ListTile(
leading:
CircleAvatar(child: Text("${index + 1}")),
title: Text(
title: HtmlWidget(
step.title != null && step.title!.isNotEmpty
? step.title!
.firstWhere(
@ -210,7 +217,7 @@ void showNewOrUpdateGuidedPath(
step,
workingPath.id ?? "temp",
isEscapeMode,
(updatedStep) {
(updatedStep) async {
setState(() {
workingPath.steps![index] =
updatedStep;
@ -255,8 +262,11 @@ void showNewOrUpdateGuidedPath(
SizedBox(
height: 46,
child: RoundedButton(
text: "Sauvegarder",
press: () {
text: isSaving ? "Sauvegarde..." : "Sauvegarder",
icon: isSaving ? Icons.hourglass_empty : null,
press: () async {
if (isSaving) return;
setState(() => isSaving = true);
// Initialise les booleans null false
workingPath.isLinear ??= false;
workingPath.requireSuccessToAdvance ??= false;
@ -267,8 +277,12 @@ void showNewOrUpdateGuidedPath(
s.isStepTimer ??= false;
s.isStepLocked ??= false;
}
onSave(workingPath);
Navigator.pop(context);
try {
await onSave(workingPath);
if (context.mounted) Navigator.pop(context);
} catch (e) {
setState(() => isSaving = false);
}
},
color: kPrimaryColor,
fontSize: 15,

View File

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
@ -13,7 +14,7 @@ void showNewOrUpdateGuidedStep(
GuidedStepDTO? step,
String pathId,
bool isEscapeMode,
Function(GuidedStepDTO) onSave,
FutureOr<void> Function(GuidedStepDTO) onSave,
) {
// Use jsonEncode/jsonDecode for a robust deep copy that handles nested DTOs correctly
GuidedStepDTO workingStep = step != null
@ -25,8 +26,11 @@ void showNewOrUpdateGuidedStep(
order: 0,
);
bool isSaving = false;
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
@ -73,6 +77,7 @@ void showNewOrUpdateGuidedStep(
setState(() => workingStep.title = val),
maxLines: 1,
isTitle: true,
isHTML: true,
),
),
SizedBox(width: 20),
@ -86,6 +91,7 @@ void showNewOrUpdateGuidedStep(
() => workingStep.description = val),
maxLines: 1,
isTitle: false,
isHTML: true,
),
),
],
@ -231,15 +237,21 @@ void showNewOrUpdateGuidedStep(
SizedBox(
height: 46,
child: RoundedButton(
text: "Sauvegarder",
press: () {
text: isSaving ? "Sauvegarde..." : "Sauvegarder",
icon: isSaving ? Icons.hourglass_empty : null,
press: () async {
if (isSaving) return;
setState(() => isSaving = true);
// Initialise les booleans null false
// pour éviter l'erreur backend "Error converting null to Boolean"
workingStep.isHiddenInitially ??= false;
workingStep.isStepTimer ??= false;
workingStep.isStepLocked ??= false;
onSave(workingStep);
Navigator.pop(context);
try {
await onSave(workingStep);
if (context.mounted) Navigator.pop(context);
} catch (e) {
setState(() => isSaving = false);
}
},
color: kPrimaryColor,
fontSize: 15,

View File

@ -113,6 +113,7 @@ void showNewOrUpdateQuizQuestion(
}),
maxLines: 3,
isTitle: false,
isHTML: true,
),
SizedBox(height: 16),
@ -164,6 +165,7 @@ void showNewOrUpdateQuizQuestion(
}),
maxLines: 1,
isTitle: true,
isHTML: true,
);
}),
SizedBox(height: 4),
@ -252,6 +254,7 @@ void showNewOrUpdateQuizQuestion(
val)),
maxLines: 1,
isTitle: true,
isHTML: true,
),
),
// Supprimer

View File

@ -53,18 +53,40 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
late SectionDTO sectionDTO;
Object? sectionDetailDTO;
String? lastLoadedSectionId;
final GlobalKey globalKey = GlobalKey();
late Future<Object?> _sectionFuture;
Future<Object?> _loadSection() {
final appContext = Provider.of<AppContext>(context, listen: false);
return getSection(
widget.id, (appContext.getContext() as ManagerAppContext).clientAPI!);
}
@override
void initState() {
super.initState();
_sectionFuture = _loadSection();
}
@override
void didUpdateWidget(SectionDetailScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.id != widget.id) {
sectionDetailDTO = null;
lastLoadedSectionId = null;
_sectionFuture = _loadSection();
}
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
GlobalKey globalKey = new GlobalKey();
Object? rawSectionData;
return FutureBuilder(
future: getSection(widget.id,
(appContext.getContext() as ManagerAppContext).clientAPI!),
future: _sectionFuture,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
rawSectionData = snapshot.data;
@ -82,9 +104,10 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
}
return Stack(
fit: StackFit.expand,
children: [
bodySection(
rawSectionData, size, appContext, context, globalKey),
rawSectionData, size, appContext, context),
Align(
alignment: AlignmentDirectional.bottomCenter,
child: Container(
@ -110,7 +133,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
}
Widget bodySection(Object? rawSectionDTO, Size size, AppContext appContext,
BuildContext context, GlobalKey globalKey) {
BuildContext context) {
ManagerAppContext managerAppContext = appContext.getContext();
//SectionDTO? sectionDTO = SectionDTO.fromJson(rawSectionDTO);

View File

@ -1,5 +1,7 @@
import 'dart:ui';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:go_router/go_router.dart';
import 'package:manager_api_new/api.dart';
@ -50,6 +52,14 @@ Future<void> main() async {
ChangeNotifierProvider<AppContext>(
create: (_) => AppContext(managerAppContext),
child: MaterialApp(
locale: const Locale('fr'),
supportedLocales: const [Locale('fr')],
localizationsDelegates: const [
FlutterQuillLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: FutureBuilder(
future: getInstanceInfo(managerAppContext),
builder: (context, asyncSnapshot) {
@ -235,6 +245,14 @@ class _MyAppState extends State<MyApp> {
const Breakpoint(start: 1921, end: double.infinity, name: '4K'),
],
),
locale: const Locale('fr'),
supportedLocales: const [Locale('fr')],
localizationsDelegates: const [
FlutterQuillLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
scrollBehavior: MyCustomScrollBehavior(),
debugShowCheckedModeBanner: false,
title: 'MyInfoMate - Manager',

View File

@ -13,6 +13,7 @@ import just_audio
import package_info_plus
import pasteboard
import path_provider_foundation
import quill_native_bridge_macos
import sqflite
import url_launcher_macos
import video_player_avfoundation
@ -27,6 +28,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))

View File

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "0816f12bbbd9e21f72ea8592b11bce4a628d4e5cb7a81ff9f1eee4f3dc23206e"
sha256: "50e24b769bd1e725732f0aff18b806b8731c1fbcf4e8018ab98e7c4805a2a52f"
url: "https://pub.dev"
source: hosted
version: "1.3.37"
version: "1.3.57"
analyzer:
dependency: transitive
description:
@ -193,6 +193,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
url: "https://pub.dev"
source: hosted
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
@ -205,10 +213,10 @@ packages:
dependency: transitive
description:
name: chewie
sha256: "1fc84d88d3b1dc26b1fe799500e2ebcc8916af30ce62595ad802cfd965b60bc3"
sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
version: "1.7.5"
cli_launcher:
dependency: transitive
description:
@ -249,6 +257,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
url: "https://pub.dev"
source: hosted
version: "0.3.5+2"
crypto:
dependency: transitive
description:
@ -273,6 +289,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
dart_quill_delta:
dependency: transitive
description:
name: dart_quill_delta
sha256: bddb0b2948bd5b5a328f1651764486d162c59a8ccffd4c63e8b2c5e44be1dac4
url: "https://pub.dev"
source: hosted
version: "10.8.3"
dart_style:
dependency: transitive
description:
@ -297,6 +321,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.5"
diff_match_patch:
dependency: transitive
description:
name: diff_match_patch
sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4"
url: "https://pub.dev"
source: hosted
version: "0.4.1"
dio:
dependency: transitive
description:
@ -353,30 +385,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.2.1"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: fae4ab4317c2a7afb13d44ef1e3f9f28a630e10016bc5cfe761e8e6a0ed7816a
sha256: "5bba5924139e91d26446fd2601c18a6aa62c1161c768a989bb5e245dcdc20644"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.15.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb"
sha256: "8bcfad6d7033f5ea951d15b867622a824b13812178bfec0c779b9d81de011bbb"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
version: "5.4.2"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "6643fe3dbd021e6ccfb751f7882b39df355708afbdeb4130fc50f9305a9d1a3d"
sha256: eb3afccfc452b2b2075acbe0c4b27de62dd596802b4e5e19869c1e926cbb20b3
url: "https://pub.dev"
source: hosted
version: "2.17.2"
version: "2.24.0"
firebase_storage:
dependency: "direct main"
description:
@ -389,18 +437,18 @@ packages:
dependency: transitive
description:
name: firebase_storage_platform_interface
sha256: "505dbaa4bceab07216c4e26cb1480bdb69ca03ab59bb7cb63f4681e2f7d5b23e"
sha256: ef0fc49be3dc96ebaf6965ec591c124256a1d9f252db31a06ceaa580a72e07a6
url: "https://pub.dev"
source: hosted
version: "5.1.24"
version: "5.2.8"
firebase_storage_web:
dependency: transitive
description:
name: firebase_storage_web
sha256: d3e6f27be96c7ebdf38a43520f19584e55dc45c71f616aa86f135dba97d63a8e
sha256: "0b263674a6b4e9a6eeea95873e63cd96d8e7c2bd5cc7aaa57f46d906f61214cb"
url: "https://pub.dev"
source: hosted
version: "3.9.9"
version: "3.10.15"
fixnum:
dependency: transitive
description:
@ -430,6 +478,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.2"
flutter_colorpicker:
dependency: transitive
description:
name: flutter_colorpicker
sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter_compass:
dependency: transitive
description:
@ -446,6 +502,51 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
flutter_keyboard_visibility_linux:
dependency: transitive
description:
name: flutter_keyboard_visibility_linux
sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_keyboard_visibility_macos:
dependency: transitive
description:
name: flutter_keyboard_visibility_macos
sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_keyboard_visibility_platform_interface:
dependency: transitive
description:
name: flutter_keyboard_visibility_platform_interface
sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_keyboard_visibility_temp_fork:
dependency: transitive
description:
name: flutter_keyboard_visibility_temp_fork
sha256: e3d02900640fbc1129245540db16944a0898b8be81694f4bf04b6c985bed9048
url: "https://pub.dev"
source: hosted
version: "0.1.5"
flutter_keyboard_visibility_windows:
dependency: transitive
description:
name: flutter_keyboard_visibility_windows
sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_map:
dependency: "direct main"
description:
@ -478,6 +579,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.20"
flutter_quill:
dependency: "direct main"
description:
name: flutter_quill
sha256: b96bb8525afdeaaea52f5d02f525e05cc34acd176467ab6d6f35d434cf14fde2
url: "https://pub.dev"
source: hosted
version: "11.5.0"
flutter_quill_delta_from_html:
dependency: "direct main"
description:
name: flutter_quill_delta_from_html
sha256: "0eb801ea8dd498cadc057507af5da794d4c9599ce58b2569cb3d4bb53ba8bed2"
url: "https://pub.dev"
source: hosted
version: "1.5.3"
flutter_svg:
dependency: "direct main"
description:
@ -644,10 +761,18 @@ packages:
dependency: transitive
description:
name: html
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
url: "https://pub.dev"
source: hosted
version: "0.15.4"
version: "0.15.6"
html_unescape:
dependency: transitive
description:
name: html_unescape
sha256: "15362d7a18f19d7b742ef8dcb811f5fd2a2df98db9f80ea393c075189e0b61e3"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
http:
dependency: transitive
description:
@ -681,13 +806,13 @@ packages:
source: hosted
version: "4.2.0"
intl:
dependency: transitive
dependency: "direct overridden"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.19.0"
version: "0.20.2"
io:
dependency: transitive
description:
@ -807,6 +932,14 @@ packages:
relative: true
source: path
version: "1.0.0"
markdown:
dependency: transitive
description:
name: markdown
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
url: "https://pub.dev"
source: hosted
version: "7.3.0"
matcher:
dependency: transitive
description:
@ -1047,38 +1180,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointer_interceptor:
dependency: transitive
description:
name: pointer_interceptor
sha256: d0a8e660d1204eaec5bd34b34cc92174690e076d2e4f893d9d68c486a13b07c4
url: "https://pub.dev"
source: hosted
version: "0.10.1+1"
pointer_interceptor_ios:
dependency: transitive
description:
name: pointer_interceptor_ios
sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917
url: "https://pub.dev"
source: hosted
version: "0.10.1"
pointer_interceptor_platform_interface:
dependency: transitive
description:
name: pointer_interceptor_platform_interface
sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506"
url: "https://pub.dev"
source: hosted
version: "0.10.0+1"
pointer_interceptor_web:
dependency: transitive
description:
name: pointer_interceptor_web
sha256: a6237528b46c411d8d55cdfad8fcb3269fc4cbb26060b14bff94879165887d1e
url: "https://pub.dev"
source: hosted
version: "0.10.2"
pointycastle:
dependency: transitive
description:
@ -1151,14 +1252,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.0"
quill_html_editor:
dependency: "direct main"
quill_native_bridge:
dependency: transitive
description:
name: quill_html_editor
sha256: "7f46ec06a41b8ec30910afc219f1a447cd2e872614cc3a34ef16600043163f19"
name: quill_native_bridge
sha256: "76a16512e398e84216f3f659f7cb18a89ec1e141ea908e954652b4ce6cf15b18"
url: "https://pub.dev"
source: hosted
version: "2.2.8"
version: "11.1.0"
quill_native_bridge_android:
dependency: transitive
description:
name: quill_native_bridge_android
sha256: b75c7e6ede362a7007f545118e756b1f19053994144ec9eda932ce5e54a57569
url: "https://pub.dev"
source: hosted
version: "0.0.1+2"
quill_native_bridge_ios:
dependency: transitive
description:
name: quill_native_bridge_ios
sha256: d23de3cd7724d482fe2b514617f8eedc8f296e120fb297368917ac3b59d8099f
url: "https://pub.dev"
source: hosted
version: "0.0.1"
quill_native_bridge_macos:
dependency: transitive
description:
name: quill_native_bridge_macos
sha256: "1c0631bd1e2eee765a8b06017c5286a4e829778f4585736e048eb67c97af8a77"
url: "https://pub.dev"
source: hosted
version: "0.0.1"
quill_native_bridge_platform_interface:
dependency: transitive
description:
name: quill_native_bridge_platform_interface
sha256: "8264a2bdb8a294c31377a27b46c0f8717fa9f968cf113f7dc52d332ed9c84526"
url: "https://pub.dev"
source: hosted
version: "0.0.2+1"
quill_native_bridge_web:
dependency: transitive
description:
name: quill_native_bridge_web
sha256: "7c723f6824b0250d7f33e8b6c23f2f8eb0103fe48ee7ebf47ab6786b64d5c05d"
url: "https://pub.dev"
source: hosted
version: "0.0.2"
quill_native_bridge_windows:
dependency: transitive
description:
name: quill_native_bridge_windows
sha256: "3f96ced19e3206ddf4f6f7dde3eb16bdd05e10294964009ea3a806d995aa7caa"
url: "https://pub.dev"
source: hosted
version: "0.0.2"
quiver:
dependency: transitive
description:
name: quiver
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
url: "https://pub.dev"
source: hosted
version: "3.2.2"
responsive_framework:
dependency: "direct main"
description:
@ -1376,10 +1533,10 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.4.1"
url_launcher_windows:
dependency: transitive
description:
@ -1464,10 +1621,10 @@ packages:
dependency: transitive
description:
name: video_player_web
sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2
sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.3"
vm_service:
dependency: transitive
description:
@ -1476,14 +1633,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "14.3.0"
vsc_quill_delta_to_html:
dependency: "direct main"
description:
name: vsc_quill_delta_to_html
sha256: "9aca60d53ed1b700e922dabff8cd8b3490daecbf99c258f19eb45820b794fa45"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
wakelock_plus:
dependency: transitive
description:
name: wakelock_plus
sha256: "104d94837bb28c735894dcd592877e990149c380e6358b00c04398ca1426eed4"
sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.1.4"
wakelock_plus_platform_interface:
dependency: transitive
description:
@ -1504,26 +1669,26 @@ packages:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.3"
webview_flutter:
dependency: transitive
description:
@ -1608,10 +1773,10 @@ packages:
dependency: transitive
description:
name: youtube_player_iframe_web
sha256: "743e8ce2ee77b8ea6e36ae986f0ef111b38c3119ae55ae813223ecddeb56b770"
sha256: "05222a228937932e7ee7a6171e8020fee4cd23d1c7bf6b4128c569484338c593"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.1.1"
sdks:
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.29.0"
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.32.0"

View File

@ -69,7 +69,9 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
quill_html_editor: ^2.2.8
flutter_quill: ^11.5.0
vsc_quill_delta_to_html: ^1.0.5
flutter_quill_delta_from_html: ^1.5.3
responsive_framework: ^1.4.0
tab_container: ^2.0.0
flutter_widget_from_html: ^0.15.3
@ -77,6 +79,9 @@ dependencies:
firebase_core: ^3.1.0
#another_flushbar: ^1.12.30
dependency_overrides:
intl: ^0.20.2
dev_dependencies:
flutter_test:
sdk: flutter

View File

@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
#include <file_selector_windows/file_selector_windows.h>
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <firebase_storage/firebase_storage_plugin_c_api.h>
#include <geolocator_windows/geolocator_windows.h>
@ -13,6 +14,8 @@
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FirebaseStoragePluginCApiRegisterWithRegistrar(

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows
firebase_core
firebase_storage
geolocator_windows