Geometry update, update quill editor, fix save in multiple new features + MISC
This commit is contained in:
parent
de3f89e038
commit
3520d2d3d2
@ -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,
|
||||
|
||||
@ -11,59 +11,71 @@ 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)
|
||||
),
|
||||
actions: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Container(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Annuler",
|
||||
icon: Icons.undo,
|
||||
color: kSecond,
|
||||
press: () {
|
||||
onGetResult(values);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
fontSize: 20,
|
||||
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),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Valider",
|
||||
icon: Icons.check,
|
||||
color: kPrimaryColor,
|
||||
textColor: kWhite,
|
||||
press: () {
|
||||
Function deepEq = const DeepCollectionEquality().equals;
|
||||
if (!deepEq(values, newValues)) {
|
||||
if(isMandatory && newValues.any((label) => label.value == null || label.value!.trim() == "")) {
|
||||
showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null);
|
||||
} else {
|
||||
onGetResult(newValues);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
fontSize: 20,
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Annuler",
|
||||
icon: Icons.undo,
|
||||
color: kSecond,
|
||||
press: () {
|
||||
onGetResult(values);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Valider",
|
||||
icon: Icons.check,
|
||||
color: kPrimaryColor,
|
||||
textColor: kWhite,
|
||||
press: () {
|
||||
Function deepEq = const DeepCollectionEquality().equals;
|
||||
if (!deepEq(values, newValues)) {
|
||||
if(isMandatory && newValues.any((label) => label.value == null || label.value!.trim() == "")) {
|
||||
showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null);
|
||||
} else {
|
||||
onGetResult(newValues);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}, context: context
|
||||
);
|
||||
@ -73,59 +85,71 @@ 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)
|
||||
),
|
||||
actions: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Container(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Annuler",
|
||||
icon: Icons.undo,
|
||||
color: kSecond,
|
||||
press: () {
|
||||
onGetResult(values);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
fontSize: 20,
|
||||
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),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Valider",
|
||||
icon: Icons.check,
|
||||
color: kPrimaryColor,
|
||||
textColor: kWhite,
|
||||
press: () {
|
||||
Function deepEq = const DeepCollectionEquality().equals;
|
||||
if (!deepEq(values, newValues)) {
|
||||
if(newValues.any((label) => label.value == null || label.value!.trim() == "")) {
|
||||
showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null);
|
||||
} else {
|
||||
onGetResult(newValues);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
fontSize: 20,
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Annuler",
|
||||
icon: Icons.undo,
|
||||
color: kSecond,
|
||||
press: () {
|
||||
onGetResult(values);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 180,
|
||||
height: 70,
|
||||
child: RoundedButton(
|
||||
text: "Valider",
|
||||
icon: Icons.check,
|
||||
color: kPrimaryColor,
|
||||
textColor: kWhite,
|
||||
press: () {
|
||||
Function deepEq = const DeepCollectionEquality().equals;
|
||||
if (!deepEq(values, newValues)) {
|
||||
if(newValues.any((label) => label.value == null || label.value!.trim() == "")) {
|
||||
showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null);
|
||||
} else {
|
||||
onGetResult(newValues);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}, context: context
|
||||
);
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
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';
|
||||
|
||||
class TranslationInputAndResourceContainer extends StatefulWidget {
|
||||
class TranslationInputAndResourceContainer extends StatefulWidget {
|
||||
TranslationInputAndResourceContainer({
|
||||
Key? key,
|
||||
required this.isTitle,
|
||||
@ -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();
|
||||
_controllers = _buildControllers();
|
||||
}
|
||||
|
||||
_tabController = new TabController(length: widget.newValues.length, vsync: this);
|
||||
currentLanguage = ValueNotifier<String>(widget.newValues.first.language!);
|
||||
static const _emptyDelta = [{'insert': '\n'}];
|
||||
|
||||
controllerQuill.onEditorLoaded(() {
|
||||
isInit = true;
|
||||
});
|
||||
List<dynamic> _htmlToDeltaJson(String html) {
|
||||
if (html.trim().isEmpty) return _emptyDelta;
|
||||
final ops = HtmlToDelta().convert(html).toJson();
|
||||
return ops.isEmpty ? _emptyDelta : ops;
|
||||
}
|
||||
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
controllerQuill.clear();
|
||||
controllerQuill.insertText(widget.newValues[_tabController!.index].value!);
|
||||
isInit = true;
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
controllerQuill.onTextChanged((p0) async {
|
||||
var plainText = await controllerQuill.getPlainText();
|
||||
if(widget.isTitle) {
|
||||
if(plainText.length > kTitleMaxLength) {
|
||||
controllerQuill.undo();
|
||||
}
|
||||
} else {
|
||||
if(plainText.length > kDescriptionMaxLength) {
|
||||
print("to much text description au dessus");
|
||||
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!);
|
||||
//}
|
||||
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();
|
||||
}
|
||||
|
||||
@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!)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.resourceTypes != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
ResourceInputContainer(
|
||||
label: "Ressource à afficher :",
|
||||
initialValue: translation.resourceId,
|
||||
color: kPrimaryColor,
|
||||
inResourceTypes: widget.resourceTypes!,
|
||||
isLanguageTab: true,
|
||||
onChanged: (ResourceDTO resource) {
|
||||
setState(() {
|
||||
translation.resourceId =
|
||||
resource.id == null ? null : resource.id;
|
||||
translation.resource =
|
||||
resource.id == null ? null : resource;
|
||||
});
|
||||
},
|
||||
isSmall: true,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getTranslation(BuildContext context, AppContext appContext, QuillEditorController controllerQuill, List<ToolBarStyle> customToolBarList, bool isTitle, List<ResourceType>? resourceTypes, List<TranslationAndResourceDTO> 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,
|
||||
//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(
|
||||
child: Column(
|
||||
children: [
|
||||
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}'),
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder<String?>(
|
||||
valueListenable: currentLanguage,
|
||||
builder: (context, value, _) {
|
||||
return ResourceInputContainer(
|
||||
label: "Ressource à afficher :",
|
||||
initialValue: newValues.where((element) => element.language! == value).first.resourceId,
|
||||
color: kPrimaryColor,
|
||||
inResourceTypes: 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;*/
|
||||
}
|
||||
});
|
||||
},
|
||||
isSmall: true
|
||||
);
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
) :
|
||||
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;
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,258 +1,16 @@
|
||||
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 {
|
||||
class TranslationInputContainer extends StatefulWidget {
|
||||
TranslationInputContainer({
|
||||
Key? key,
|
||||
required this.isTitle,
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,22 +100,35 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text("Ajouter un évènement"),
|
||||
onPressed: () {
|
||||
showNewOrUpdateEventAgenda(
|
||||
context,
|
||||
null,
|
||||
agendaDTO.id ?? "temp",
|
||||
(newEvent) {
|
||||
setState(() {
|
||||
agendaDTO.events = [
|
||||
...(agendaDTO.events ?? []),
|
||||
newEvent
|
||||
];
|
||||
widget.onChanged(agendaDTO);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
onPressed: agendaDTO.id == null
|
||||
? null
|
||||
: () {
|
||||
showNewOrUpdateEventAgenda(
|
||||
context,
|
||||
null,
|
||||
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;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: kSuccess,
|
||||
foregroundColor: kWhite,
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -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,24 +216,26 @@ class _EventConfigState extends State<EventConfig> {
|
||||
eventDTO.id!, programmeBlockDTO);
|
||||
|
||||
if (createdBlockDTO != null) {
|
||||
// Convert back if necessary
|
||||
final createdBlock =
|
||||
ProgrammeBlock.fromJson(createdBlockDTO.toJson());
|
||||
if (createdBlock != null) {
|
||||
setState(() {
|
||||
eventDTO.programme = [
|
||||
...(eventDTO.programme ?? []),
|
||||
createdBlock
|
||||
];
|
||||
widget.onChanged(eventDTO);
|
||||
});
|
||||
showNotification(
|
||||
kSuccess,
|
||||
kWhite,
|
||||
'Bloc de programme créé avec succès',
|
||||
context,
|
||||
null);
|
||||
}
|
||||
final createdBlock = ProgrammeBlock(
|
||||
id: createdBlockDTO.id,
|
||||
title: createdBlockDTO.title,
|
||||
description: createdBlockDTO.description,
|
||||
startTime: createdBlockDTO.startTime,
|
||||
endTime: createdBlockDTO.endTime,
|
||||
);
|
||||
setState(() {
|
||||
eventDTO.programme = [
|
||||
...(eventDTO.programme ?? []),
|
||||
createdBlock
|
||||
];
|
||||
widget.onChanged(eventDTO);
|
||||
});
|
||||
showNotification(
|
||||
kSuccess,
|
||||
kWhite,
|
||||
'Bloc de programme créé avec succès',
|
||||
context,
|
||||
null);
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification(
|
||||
@ -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,20 +310,23 @@ class _EventConfigState extends State<EventConfig> {
|
||||
programmeBlockDTO);
|
||||
|
||||
if (resultDTO != null) {
|
||||
final result = ProgrammeBlock.fromJson(
|
||||
resultDTO.toJson());
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
eventDTO.programme![index] = result;
|
||||
widget.onChanged(eventDTO);
|
||||
});
|
||||
showNotification(
|
||||
kSuccess,
|
||||
kWhite,
|
||||
'Bloc mis à jour avec succès',
|
||||
context,
|
||||
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);
|
||||
});
|
||||
showNotification(
|
||||
kSuccess,
|
||||
kWhite,
|
||||
'Bloc mis à jour avec succès',
|
||||
context,
|
||||
null);
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification(
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
setState(() {
|
||||
paths.add(createdPath);
|
||||
widget.onChanged(paths);
|
||||
});
|
||||
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!
|
||||
.firstWhere((t) => t.language == 'FR',
|
||||
orElse: () => path.title![0])
|
||||
.value ??
|
||||
"Parcours sans titre"
|
||||
: "Parcours sans titre"),
|
||||
title: path.title != null && path.title!.isNotEmpty
|
||||
? HtmlWidget(
|
||||
path.title!
|
||||
.firstWhere((t) => t.language == 'FR',
|
||||
orElse: () => path.title![0])
|
||||
.value ??
|
||||
"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 api = (appContext.getContext()
|
||||
as ManagerAppContext)
|
||||
.clientAPI!
|
||||
.sectionMapApi!;
|
||||
final result =
|
||||
await (appContext.getContext()
|
||||
as ManagerAppContext)
|
||||
.clientAPI!
|
||||
.sectionMapApi!
|
||||
.sectionMapUpdateGuidedPath(
|
||||
updatedPath);
|
||||
|
||||
await api.sectionMapUpdateGuidedPath(
|
||||
updatedPath);
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
paths[index] = result;
|
||||
widget.onChanged(paths);
|
||||
});
|
||||
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;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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"))
|
||||
|
||||
309
pubspec.lock
309
pubspec.lock
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
firebase_core
|
||||
firebase_storage
|
||||
geolocator_windows
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user