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") {
|
if (currentType == "Point") {
|
||||||
var coords = widget.initialGeometry!.coordinates as List<dynamic>;
|
var coords = widget.initialGeometry!.coordinates as List<dynamic>;
|
||||||
points = [LatLng(coords[0].toDouble(), coords[1].toDouble())];
|
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>;
|
var list = widget.initialGeometry!.coordinates as List<dynamic>;
|
||||||
points = list.map((e) {
|
points = list.map((e) {
|
||||||
var pair = e as List<dynamic>;
|
var pair = e as List<dynamic>;
|
||||||
return LatLng(pair[0].toDouble(), pair[1].toDouble());
|
return LatLng(pair[0].toDouble(), pair[1].toDouble());
|
||||||
}).toList();
|
}).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) {
|
} catch (e) {
|
||||||
print("Error parsing geometry: $e");
|
print("Error parsing geometry: $e");
|
||||||
@ -87,6 +99,12 @@ class _MapGeometryPickerState extends State<MapGeometryPicker> {
|
|||||||
? [points[0].latitude, points[0].longitude]
|
? [points[0].latitude, points[0].longitude]
|
||||||
: null,
|
: 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 {
|
} else {
|
||||||
return GeometryDTO(
|
return GeometryDTO(
|
||||||
type: currentType,
|
type: currentType,
|
||||||
|
|||||||
@ -11,59 +11,71 @@ showMultiStringInputHTML (String label, String modalLabel, bool isTitle, List<Tr
|
|||||||
showDialog(
|
showDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return Dialog(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20.0))
|
borderRadius: BorderRadius.all(Radius.circular(20.0))
|
||||||
),
|
),
|
||||||
title: Center(child: Text(modalLabel)),
|
insetPadding: const EdgeInsets.symmetric(horizontal: 60, vertical: 24),
|
||||||
content: SingleChildScrollView(
|
child: ConstrainedBox(
|
||||||
child: TranslationInputContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes)
|
constraints: const BoxConstraints(maxWidth: 800),
|
||||||
),
|
child: Padding(
|
||||||
actions: <Widget>[
|
padding: const EdgeInsets.fromLTRB(24, 20, 24, 16),
|
||||||
Row(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))),
|
||||||
width: 180,
|
const SizedBox(height: 16),
|
||||||
height: 70,
|
SingleChildScrollView(
|
||||||
child: RoundedButton(
|
child: TranslationInputContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes),
|
||||||
text: "Annuler",
|
|
||||||
icon: Icons.undo,
|
|
||||||
color: kSecond,
|
|
||||||
press: () {
|
|
||||||
onGetResult(values);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
Container(
|
Row(
|
||||||
width: 180,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
height: 70,
|
children: [
|
||||||
child: RoundedButton(
|
SizedBox(
|
||||||
text: "Valider",
|
width: 180,
|
||||||
icon: Icons.check,
|
height: 70,
|
||||||
color: kPrimaryColor,
|
child: RoundedButton(
|
||||||
textColor: kWhite,
|
text: "Annuler",
|
||||||
press: () {
|
icon: Icons.undo,
|
||||||
Function deepEq = const DeepCollectionEquality().equals;
|
color: kSecond,
|
||||||
if (!deepEq(values, newValues)) {
|
press: () {
|
||||||
if(isMandatory && newValues.any((label) => label.value == null || label.value!.trim() == "")) {
|
onGetResult(values);
|
||||||
showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null);
|
Navigator.of(context).pop();
|
||||||
} else {
|
},
|
||||||
onGetResult(newValues);
|
fontSize: 20,
|
||||||
Navigator.of(context).pop();
|
),
|
||||||
}
|
),
|
||||||
} else {
|
SizedBox(
|
||||||
Navigator.of(context).pop();
|
width: 180,
|
||||||
}
|
height: 70,
|
||||||
},
|
child: RoundedButton(
|
||||||
fontSize: 20,
|
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
|
}, context: context
|
||||||
);
|
);
|
||||||
@ -73,59 +85,71 @@ showMultiStringInputAndResourceHTML (String label, String modalLabel, bool isTit
|
|||||||
showDialog(
|
showDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return Dialog(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20.0))
|
borderRadius: BorderRadius.all(Radius.circular(20.0))
|
||||||
),
|
),
|
||||||
title: Center(child: Text(modalLabel)),
|
insetPadding: const EdgeInsets.symmetric(horizontal: 60, vertical: 24),
|
||||||
content: SingleChildScrollView(
|
child: ConstrainedBox(
|
||||||
child: TranslationInputAndResourceContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes)
|
constraints: const BoxConstraints(maxWidth: 800),
|
||||||
),
|
child: Padding(
|
||||||
actions: <Widget>[
|
padding: const EdgeInsets.fromLTRB(24, 20, 24, 16),
|
||||||
Row(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))),
|
||||||
width: 180,
|
const SizedBox(height: 16),
|
||||||
height: 70,
|
SingleChildScrollView(
|
||||||
child: RoundedButton(
|
child: TranslationInputAndResourceContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes),
|
||||||
text: "Annuler",
|
|
||||||
icon: Icons.undo,
|
|
||||||
color: kSecond,
|
|
||||||
press: () {
|
|
||||||
onGetResult(values);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
Container(
|
Row(
|
||||||
width: 180,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
height: 70,
|
children: [
|
||||||
child: RoundedButton(
|
SizedBox(
|
||||||
text: "Valider",
|
width: 180,
|
||||||
icon: Icons.check,
|
height: 70,
|
||||||
color: kPrimaryColor,
|
child: RoundedButton(
|
||||||
textColor: kWhite,
|
text: "Annuler",
|
||||||
press: () {
|
icon: Icons.undo,
|
||||||
Function deepEq = const DeepCollectionEquality().equals;
|
color: kSecond,
|
||||||
if (!deepEq(values, newValues)) {
|
press: () {
|
||||||
if(newValues.any((label) => label.value == null || label.value!.trim() == "")) {
|
onGetResult(values);
|
||||||
showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null);
|
Navigator.of(context).pop();
|
||||||
} else {
|
},
|
||||||
onGetResult(newValues);
|
fontSize: 20,
|
||||||
Navigator.of(context).pop();
|
),
|
||||||
}
|
),
|
||||||
} else {
|
SizedBox(
|
||||||
Navigator.of(context).pop();
|
width: 180,
|
||||||
}
|
height: 70,
|
||||||
},
|
child: RoundedButton(
|
||||||
fontSize: 20,
|
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
|
}, context: context
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
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_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/resource_input_container.dart';
|
||||||
import 'package:manager_app/app_context.dart';
|
|
||||||
import 'package:manager_app/constants.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 'flag_decoration.dart';
|
||||||
|
|
||||||
class TranslationInputAndResourceContainer extends StatefulWidget {
|
class TranslationInputAndResourceContainer extends StatefulWidget {
|
||||||
TranslationInputAndResourceContainer({
|
TranslationInputAndResourceContainer({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.isTitle,
|
required this.isTitle,
|
||||||
@ -28,225 +27,224 @@ class TranslationInputAndResourceContainer extends StatefulWidget {
|
|||||||
List<ResourceType>? resourceTypes;
|
List<ResourceType>? resourceTypes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TranslationInputAndResourceContainer> createState() => _TranslationInputAndResourceContainerState();
|
State<TranslationInputAndResourceContainer> createState() =>
|
||||||
|
_TranslationInputAndResourceContainerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TranslationInputAndResourceContainerState extends State<TranslationInputAndResourceContainer> with TickerProviderStateMixin {
|
class _TranslationInputAndResourceContainerState
|
||||||
TabController? _tabController;
|
extends State<TranslationInputAndResourceContainer> {
|
||||||
QuillEditorController controllerQuill = QuillEditorController();
|
late Map<String, QuillController> _controllers;
|
||||||
ValueNotifier<String?>? currentLanguage;
|
bool _isEnforcingLimit = false;
|
||||||
bool isInit = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_controllers = _buildControllers();
|
||||||
|
}
|
||||||
|
|
||||||
_tabController = new TabController(length: widget.newValues.length, vsync: this);
|
static const _emptyDelta = [{'insert': '\n'}];
|
||||||
currentLanguage = ValueNotifier<String>(widget.newValues.first.language!);
|
|
||||||
|
|
||||||
controllerQuill.onEditorLoaded(() {
|
List<dynamic> _htmlToDeltaJson(String html) {
|
||||||
isInit = true;
|
if (html.trim().isEmpty) return _emptyDelta;
|
||||||
});
|
final ops = HtmlToDelta().convert(html).toJson();
|
||||||
|
return ops.isEmpty ? _emptyDelta : ops;
|
||||||
|
}
|
||||||
|
|
||||||
Future.delayed(Duration(milliseconds: 500), () {
|
Map<String, QuillController> _buildControllers() {
|
||||||
controllerQuill.clear();
|
final map = <String, QuillController>{};
|
||||||
controllerQuill.insertText(widget.newValues[_tabController!.index].value!);
|
for (final translation in widget.newValues) {
|
||||||
isInit = true;
|
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 {
|
void _setupListener(String lang, QuillController controller) {
|
||||||
var plainText = await controllerQuill.getPlainText();
|
controller.document.changes.listen((_) {
|
||||||
if(widget.isTitle) {
|
if (!mounted || _isEnforcingLimit) return;
|
||||||
if(plainText.length > kTitleMaxLength) {
|
final limit = widget.isTitle ? kTitleMaxLength : kDescriptionMaxLength;
|
||||||
controllerQuill.undo();
|
final plain = controller.document.toPlainText().trimRight();
|
||||||
}
|
if (plain.length > limit) {
|
||||||
} else {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if(plainText.length > kDescriptionMaxLength) {
|
if (mounted && !_isEnforcingLimit) {
|
||||||
print("to much text description au dessus");
|
_isEnforcingLimit = true;
|
||||||
controllerQuill.undo();
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
for (final c in _controllers.values) {
|
||||||
|
c.dispose();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_tabController!.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final customToolBarList = widget.isTitle ? [
|
return Column(
|
||||||
ToolBarStyle.bold,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
ToolBarStyle.italic,
|
children: widget.newValues.map((t) => _buildLanguageSection(t)).toList(),
|
||||||
ToolBarStyle.color,
|
);
|
||||||
ToolBarStyle.background,
|
}
|
||||||
ToolBarStyle.clean
|
|
||||||
] : [
|
|
||||||
ToolBarStyle.bold,
|
|
||||||
ToolBarStyle.italic,
|
|
||||||
ToolBarStyle.color,
|
|
||||||
ToolBarStyle.background,
|
|
||||||
ToolBarStyle.listBullet,
|
|
||||||
ToolBarStyle.listOrdered,
|
|
||||||
ToolBarStyle.clean
|
|
||||||
];
|
|
||||||
|
|
||||||
return Container(
|
Widget _buildLanguageSection(TranslationAndResourceDTO translation) {
|
||||||
height: widget.isTitle ? MediaQuery.of(context).size.height *0.45 : MediaQuery.of(context).size.height *0.5,
|
final lang = translation.language!;
|
||||||
//color: Colors.orange,
|
|
||||||
width: MediaQuery.of(context).size.width *0.7,
|
// Resource-only mode (no text editor)
|
||||||
constraints: BoxConstraints(
|
if (widget.resourceTypes != null && _controllers[lang] == null) {
|
||||||
minHeight: 300,
|
return Padding(
|
||||||
minWidth: 300
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
),
|
|
||||||
child: DefaultTabController(
|
|
||||||
length: widget.newValues.length,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
RotatedBox(
|
_buildLangHeader(lang),
|
||||||
quarterTurns: 0, // Can be used to test vertical tab in case of smaller screen
|
const SizedBox(height: 4),
|
||||||
child: TabBar(
|
SizedBox(
|
||||||
indicatorColor: kPrimaryColor,
|
width: 250,
|
||||||
//overlayColor: MaterialStateProperty().c,
|
height: 120,
|
||||||
labelColor: kPrimaryColor,
|
child: ResourceInputContainer(
|
||||||
unselectedLabelColor: Colors.black,
|
label: "",
|
||||||
controller: _tabController,
|
initialValue: translation.resourceId,
|
||||||
tabs: widget.newValues.map((v) => Tab(icon: FlagDecoration(language: v.language!))).toList(), // text: v.language!.toUpperCase(),
|
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) {
|
Widget _buildLangHeader(String lang) {
|
||||||
return Padding(
|
return Row(
|
||||||
padding: const EdgeInsets.all(6.0),
|
children: [
|
||||||
child: Padding(
|
FlagDecoration(language: lang),
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
const SizedBox(width: 8),
|
||||||
child: Container(
|
Text(
|
||||||
width: MediaQuery.of(context).size.width *0.7,
|
lang.toUpperCase(),
|
||||||
//color: Colors.blueAccent,
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14),
|
||||||
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;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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/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_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/resource_input_container.dart';
|
||||||
import 'package:manager_app/Components/rounded_button.dart';
|
import 'package:manager_app/Components/rounded_button.dart';
|
||||||
import 'package:manager_app/app_context.dart';
|
|
||||||
import 'package:manager_app/constants.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 'flag_decoration.dart';
|
||||||
import 'message_notification.dart';
|
import 'message_notification.dart';
|
||||||
|
|
||||||
class _TranslationInputContainerState extends State<TranslationInputContainer> with TickerProviderStateMixin {
|
class TranslationInputContainer extends StatefulWidget {
|
||||||
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({
|
TranslationInputContainer({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.isTitle,
|
required this.isTitle,
|
||||||
@ -273,3 +31,235 @@ class TranslationInputContainer extends StatefulWidget {
|
|||||||
@override
|
@override
|
||||||
State<TranslationInputContainer> createState() => _TranslationInputContainerState();
|
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/check_input_container.dart';
|
||||||
import 'package:manager_app/Components/multi_string_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/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';
|
import 'showNewOrUpdateEventAgenda.dart';
|
||||||
|
|
||||||
class AgendaConfig extends StatefulWidget {
|
class AgendaConfig extends StatefulWidget {
|
||||||
@ -26,11 +31,38 @@ class AgendaConfig extends StatefulWidget {
|
|||||||
|
|
||||||
class _AgendaConfigState extends State<AgendaConfig> {
|
class _AgendaConfigState extends State<AgendaConfig> {
|
||||||
late AgendaDTO agendaDTO;
|
late AgendaDTO agendaDTO;
|
||||||
|
List<EventAgendaDTO> events = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
agendaDTO = widget.initialValue;
|
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
|
@override
|
||||||
@ -51,6 +83,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildAgendaHeader(size, mapProviderIn),
|
_buildAgendaHeader(size, mapProviderIn),
|
||||||
if (agendaDTO.isOnlineAgenda == false) ...[
|
if (agendaDTO.isOnlineAgenda == false) ...[
|
||||||
@ -67,22 +100,35 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text("Ajouter un évènement"),
|
label: const Text("Ajouter un évènement"),
|
||||||
onPressed: () {
|
onPressed: agendaDTO.id == null
|
||||||
showNewOrUpdateEventAgenda(
|
? null
|
||||||
context,
|
: () {
|
||||||
null,
|
showNewOrUpdateEventAgenda(
|
||||||
agendaDTO.id ?? "temp",
|
context,
|
||||||
(newEvent) {
|
null,
|
||||||
setState(() {
|
agendaDTO.id!,
|
||||||
agendaDTO.events = [
|
(newEvent) async {
|
||||||
...(agendaDTO.events ?? []),
|
try {
|
||||||
newEvent
|
final created = await _api(context)
|
||||||
];
|
.sectionAgendaCreateEventAgenda(
|
||||||
widget.onChanged(agendaDTO);
|
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(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: kSuccess,
|
backgroundColor: kSuccess,
|
||||||
foregroundColor: kWhite,
|
foregroundColor: kWhite,
|
||||||
@ -98,14 +144,14 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
Container(
|
Container(
|
||||||
height: 600,
|
height: 600,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: (agendaDTO.events == null || agendaDTO.events!.isEmpty)
|
child: events.isEmpty
|
||||||
? const Center(
|
? const Center(
|
||||||
child: Text("Aucun évènement manuel",
|
child: Text("Aucun évènement manuel",
|
||||||
style: TextStyle(fontStyle: FontStyle.italic)))
|
style: TextStyle(fontStyle: FontStyle.italic)))
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
itemCount: agendaDTO.events!.length,
|
itemCount: events.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final event = agendaDTO.events![index];
|
final event = events[index];
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
@ -120,13 +166,13 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
child:
|
child:
|
||||||
const Icon(Icons.event, color: kPrimaryColor),
|
const Icon(Icons.event, color: kPrimaryColor),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: HtmlWidget(
|
||||||
(event.label != null && event.label!.isNotEmpty)
|
(event.label != null && event.label!.isNotEmpty)
|
||||||
? (event.label!.firstWhere(
|
? (event.label!.firstWhere(
|
||||||
(t) => t.language == 'FR',
|
(t) => t.language == 'FR',
|
||||||
orElse: () => event.label![0])).value!
|
orElse: () => event.label![0])).value ?? "Évènement $index"
|
||||||
: "Évènement $index",
|
: "Évènement $index",
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
textStyle: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
@ -143,23 +189,61 @@ class _AgendaConfigState extends State<AgendaConfig> {
|
|||||||
showNewOrUpdateEventAgenda(
|
showNewOrUpdateEventAgenda(
|
||||||
context,
|
context,
|
||||||
event,
|
event,
|
||||||
agendaDTO.id ?? "temp",
|
agendaDTO.id ?? "",
|
||||||
(updatedEvent) {
|
(updatedEvent) async {
|
||||||
setState(() {
|
try {
|
||||||
agendaDTO.events![index] = updatedEvent;
|
final result = await _api(context)
|
||||||
widget.onChanged(agendaDTO);
|
.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(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete, color: kError),
|
icon: const Icon(Icons.delete, color: kError),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
setState(() {
|
try {
|
||||||
agendaDTO.events!.removeAt(index);
|
if (event.id != null) {
|
||||||
widget.onChanged(agendaDTO);
|
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:flutter/material.dart';
|
||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
import 'package:manager_app/constants.dart';
|
import 'package:manager_app/constants.dart';
|
||||||
@ -13,7 +14,7 @@ void showNewOrUpdateEventAgenda(
|
|||||||
Function(EventAgendaDTO) onSave,
|
Function(EventAgendaDTO) onSave,
|
||||||
) {
|
) {
|
||||||
EventAgendaDTO workingEvent = event != null
|
EventAgendaDTO workingEvent = event != null
|
||||||
? EventAgendaDTO.fromJson(event.toJson())!
|
? EventAgendaDTO.fromJson(jsonDecode(jsonEncode(event)))!
|
||||||
: EventAgendaDTO(
|
: EventAgendaDTO(
|
||||||
label: [],
|
label: [],
|
||||||
description: [],
|
description: [],
|
||||||
@ -74,6 +75,7 @@ void showNewOrUpdateEventAgenda(
|
|||||||
setState(() => workingEvent.label = val),
|
setState(() => workingEvent.label = val),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -87,6 +89,7 @@ void showNewOrUpdateEventAgenda(
|
|||||||
() => workingEvent.description = val),
|
() => workingEvent.description = val),
|
||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
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_api_new/api.dart';
|
||||||
import 'package:manager_app/constants.dart';
|
import 'package:manager_app/constants.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@ -50,6 +51,7 @@ class _EventConfigState extends State<EventConfig> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@ -198,11 +200,13 @@ class _EventConfigState extends State<EventConfig> {
|
|||||||
null,
|
null,
|
||||||
(newBlock) async {
|
(newBlock) async {
|
||||||
try {
|
try {
|
||||||
// The API expects ProgrammeBlockDTO, while eventDTO.programme is List<ProgrammeBlock>
|
final programmeBlockDTO = ProgrammeBlockDTO(
|
||||||
// Usually they are structural equivalents in these generated APIs, but let's be safe.
|
id: newBlock.id,
|
||||||
final programmeBlockDTO =
|
title: newBlock.title,
|
||||||
ProgrammeBlockDTO.fromJson(newBlock.toJson());
|
description: newBlock.description,
|
||||||
if (programmeBlockDTO == null) return;
|
startTime: newBlock.startTime,
|
||||||
|
endTime: newBlock.endTime,
|
||||||
|
);
|
||||||
|
|
||||||
final createdBlockDTO =
|
final createdBlockDTO =
|
||||||
await (appContext.getContext() as ManagerAppContext)
|
await (appContext.getContext() as ManagerAppContext)
|
||||||
@ -212,24 +216,26 @@ class _EventConfigState extends State<EventConfig> {
|
|||||||
eventDTO.id!, programmeBlockDTO);
|
eventDTO.id!, programmeBlockDTO);
|
||||||
|
|
||||||
if (createdBlockDTO != null) {
|
if (createdBlockDTO != null) {
|
||||||
// Convert back if necessary
|
final createdBlock = ProgrammeBlock(
|
||||||
final createdBlock =
|
id: createdBlockDTO.id,
|
||||||
ProgrammeBlock.fromJson(createdBlockDTO.toJson());
|
title: createdBlockDTO.title,
|
||||||
if (createdBlock != null) {
|
description: createdBlockDTO.description,
|
||||||
setState(() {
|
startTime: createdBlockDTO.startTime,
|
||||||
eventDTO.programme = [
|
endTime: createdBlockDTO.endTime,
|
||||||
...(eventDTO.programme ?? []),
|
);
|
||||||
createdBlock
|
setState(() {
|
||||||
];
|
eventDTO.programme = [
|
||||||
widget.onChanged(eventDTO);
|
...(eventDTO.programme ?? []),
|
||||||
});
|
createdBlock
|
||||||
showNotification(
|
];
|
||||||
kSuccess,
|
widget.onChanged(eventDTO);
|
||||||
kWhite,
|
});
|
||||||
'Bloc de programme créé avec succès',
|
showNotification(
|
||||||
context,
|
kSuccess,
|
||||||
null);
|
kWhite,
|
||||||
}
|
'Bloc de programme créé avec succès',
|
||||||
|
context,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showNotification(
|
showNotification(
|
||||||
@ -248,7 +254,8 @@ class _EventConfigState extends State<EventConfig> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Container(
|
||||||
|
height: 600,
|
||||||
child: (eventDTO.programme == null || eventDTO.programme!.isEmpty)
|
child: (eventDTO.programme == null || eventDTO.programme!.isEmpty)
|
||||||
? Center(
|
? Center(
|
||||||
child: Text("Aucun bloc de programme défini",
|
child: Text("Aucun bloc de programme défini",
|
||||||
@ -260,16 +267,18 @@ class _EventConfigState extends State<EventConfig> {
|
|||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(
|
title: block.title != null && block.title!.isNotEmpty
|
||||||
block.title != null && block.title!.isNotEmpty
|
? HtmlWidget(
|
||||||
? block.title!
|
block.title!
|
||||||
.firstWhere((t) => t.language == 'FR',
|
.firstWhere(
|
||||||
|
(t) => t.language == 'FR',
|
||||||
orElse: () => block.title![0])
|
orElse: () => block.title![0])
|
||||||
.value ??
|
.value ??
|
||||||
"Bloc ${index + 1}"
|
"Bloc ${index + 1}",
|
||||||
: "Bloc ${index + 1}"),
|
)
|
||||||
|
: Text("Bloc ${index + 1}"),
|
||||||
subtitle: Text(
|
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(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -284,10 +293,13 @@ class _EventConfigState extends State<EventConfig> {
|
|||||||
block,
|
block,
|
||||||
(updatedBlock) async {
|
(updatedBlock) async {
|
||||||
try {
|
try {
|
||||||
final programmeBlockDTO =
|
final programmeBlockDTO = ProgrammeBlockDTO(
|
||||||
ProgrammeBlockDTO.fromJson(
|
id: updatedBlock.id,
|
||||||
updatedBlock.toJson());
|
title: updatedBlock.title,
|
||||||
if (programmeBlockDTO == null) return;
|
description: updatedBlock.description,
|
||||||
|
startTime: updatedBlock.startTime,
|
||||||
|
endTime: updatedBlock.endTime,
|
||||||
|
);
|
||||||
|
|
||||||
final resultDTO =
|
final resultDTO =
|
||||||
await (appContext.getContext()
|
await (appContext.getContext()
|
||||||
@ -298,20 +310,23 @@ class _EventConfigState extends State<EventConfig> {
|
|||||||
programmeBlockDTO);
|
programmeBlockDTO);
|
||||||
|
|
||||||
if (resultDTO != null) {
|
if (resultDTO != null) {
|
||||||
final result = ProgrammeBlock.fromJson(
|
final result = ProgrammeBlock(
|
||||||
resultDTO.toJson());
|
id: resultDTO.id,
|
||||||
if (result != null) {
|
title: resultDTO.title,
|
||||||
setState(() {
|
description: resultDTO.description,
|
||||||
eventDTO.programme![index] = result;
|
startTime: resultDTO.startTime,
|
||||||
widget.onChanged(eventDTO);
|
endTime: resultDTO.endTime,
|
||||||
});
|
);
|
||||||
showNotification(
|
setState(() {
|
||||||
kSuccess,
|
eventDTO.programme![index] = result;
|
||||||
kWhite,
|
widget.onChanged(eventDTO);
|
||||||
'Bloc mis à jour avec succès',
|
});
|
||||||
context,
|
showNotification(
|
||||||
null);
|
kSuccess,
|
||||||
}
|
kWhite,
|
||||||
|
'Bloc mis à jour avec succès',
|
||||||
|
context,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showNotification(
|
showNotification(
|
||||||
|
|||||||
@ -11,7 +11,13 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
Function(ProgrammeBlock) onSave,
|
Function(ProgrammeBlock) onSave,
|
||||||
) {
|
) {
|
||||||
ProgrammeBlock workingBlock = block != null
|
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(
|
: ProgrammeBlock(
|
||||||
title: [],
|
title: [],
|
||||||
description: [],
|
description: [],
|
||||||
@ -69,6 +75,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
setState(() => workingBlock.title = val),
|
setState(() => workingBlock.title = val),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -82,6 +89,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
() => workingBlock.description = val),
|
() => workingBlock.description = val),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -99,7 +107,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
workingBlock.startTime != null
|
workingBlock.startTime != null
|
||||||
? DateFormat('HH:mm')
|
? DateFormat('HH:mm')
|
||||||
.format(workingBlock.startTime!)
|
.format(workingBlock.startTime!.toLocal())
|
||||||
: "Non définie",
|
: "Non définie",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: kPrimaryColor,
|
color: kPrimaryColor,
|
||||||
@ -109,7 +117,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
TimeOfDay? time = await showTimePicker(
|
TimeOfDay? time = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialTime: TimeOfDay.fromDateTime(
|
initialTime: TimeOfDay.fromDateTime(
|
||||||
workingBlock.startTime ??
|
workingBlock.startTime?.toLocal() ??
|
||||||
DateTime.now()),
|
DateTime.now()),
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return Theme(
|
return Theme(
|
||||||
@ -148,7 +156,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
workingBlock.endTime != null
|
workingBlock.endTime != null
|
||||||
? DateFormat('HH:mm')
|
? DateFormat('HH:mm')
|
||||||
.format(workingBlock.endTime!)
|
.format(workingBlock.endTime!.toLocal())
|
||||||
: "Non définie",
|
: "Non définie",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: kPrimaryColor,
|
color: kPrimaryColor,
|
||||||
@ -158,7 +166,7 @@ void showNewOrUpdateProgrammeBlock(
|
|||||||
TimeOfDay? time = await showTimePicker(
|
TimeOfDay? time = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialTime: TimeOfDay.fromDateTime(
|
initialTime: TimeOfDay.fromDateTime(
|
||||||
workingBlock.endTime ??
|
workingBlock.endTime?.toLocal() ??
|
||||||
DateTime.now()
|
DateTime.now()
|
||||||
.add(Duration(hours: 1))),
|
.add(Duration(hours: 1))),
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
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_api_new/api.dart';
|
||||||
import 'package:manager_app/constants.dart';
|
import 'package:manager_app/constants.dart';
|
||||||
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.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 {
|
Future<void> _loadFromApi() async {
|
||||||
final appContext = Provider.of<AppContext>(context, listen: false);
|
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 {
|
try {
|
||||||
// Backend already includes steps + quiz questions via .Include()
|
// 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;
|
if (fetchedPaths == null || !mounted) return;
|
||||||
|
|
||||||
fetchedPaths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
|
fetchedPaths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
|
||||||
setState(() {
|
setState(() {
|
||||||
paths = fetchedPaths;
|
paths = List.from(fetchedPaths);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silently keep initial value on error
|
// Silently keep initial value on error
|
||||||
@ -81,7 +85,9 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
|
|||||||
(newPath) async {
|
(newPath) async {
|
||||||
try {
|
try {
|
||||||
newPath.order = paths.length;
|
newPath.order = paths.length;
|
||||||
newPath.instanceId = (appContext.getContext() as ManagerAppContext).instanceId;
|
newPath.instanceId =
|
||||||
|
(appContext.getContext() as ManagerAppContext)
|
||||||
|
.instanceId;
|
||||||
if (widget.isEscapeMode) {
|
if (widget.isEscapeMode) {
|
||||||
newPath.sectionGameId = widget.parentId;
|
newPath.sectionGameId = widget.parentId;
|
||||||
} else if (widget.isEvent) {
|
} else if (widget.isEvent) {
|
||||||
@ -89,6 +95,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
|
|||||||
} else {
|
} else {
|
||||||
newPath.sectionMapId = widget.parentId;
|
newPath.sectionMapId = widget.parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
final createdPath =
|
final createdPath =
|
||||||
await (appContext.getContext() as ManagerAppContext)
|
await (appContext.getContext() as ManagerAppContext)
|
||||||
.clientAPI!
|
.clientAPI!
|
||||||
@ -97,10 +104,12 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
|
|||||||
widget.parentId, newPath);
|
widget.parentId, newPath);
|
||||||
|
|
||||||
if (createdPath != null) {
|
if (createdPath != null) {
|
||||||
setState(() {
|
if (mounted) {
|
||||||
paths.add(createdPath);
|
setState(() {
|
||||||
widget.onChanged(paths);
|
paths.add(createdPath);
|
||||||
});
|
widget.onChanged(paths);
|
||||||
|
});
|
||||||
|
}
|
||||||
showNotification(kSuccess, kWhite,
|
showNotification(kSuccess, kWhite,
|
||||||
'Parcours créé avec succès', context, null);
|
'Parcours créé avec succès', context, null);
|
||||||
}
|
}
|
||||||
@ -111,6 +120,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
|
|||||||
'Erreur lors de la création du parcours',
|
'Erreur lors de la création du parcours',
|
||||||
context,
|
context,
|
||||||
null);
|
null);
|
||||||
|
rethrow; // Important so showNewOrUpdateGuidedPath knows it failed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -169,13 +179,15 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
|
|||||||
child: Text("${index + 1}"),
|
child: Text("${index + 1}"),
|
||||||
backgroundColor: kPrimaryColor,
|
backgroundColor: kPrimaryColor,
|
||||||
foregroundColor: kWhite),
|
foregroundColor: kWhite),
|
||||||
title: Text(path.title != null && path.title!.isNotEmpty
|
title: path.title != null && path.title!.isNotEmpty
|
||||||
? path.title!
|
? HtmlWidget(
|
||||||
.firstWhere((t) => t.language == 'FR',
|
path.title!
|
||||||
orElse: () => path.title![0])
|
.firstWhere((t) => t.language == 'FR',
|
||||||
.value ??
|
orElse: () => path.title![0])
|
||||||
"Parcours sans titre"
|
.value ??
|
||||||
: "Parcours sans titre"),
|
"Parcours sans titre",
|
||||||
|
)
|
||||||
|
: Text("Parcours sans titre"),
|
||||||
subtitle: Text("${path.steps?.length ?? 0} étapes"),
|
subtitle: Text("${path.steps?.length ?? 0} étapes"),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -194,19 +206,20 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
|
|||||||
widget.isEscapeMode,
|
widget.isEscapeMode,
|
||||||
(updatedPath) async {
|
(updatedPath) async {
|
||||||
try {
|
try {
|
||||||
|
final api = (appContext.getContext()
|
||||||
|
as ManagerAppContext)
|
||||||
|
.clientAPI!
|
||||||
|
.sectionMapApi!;
|
||||||
final result =
|
final result =
|
||||||
await (appContext.getContext()
|
await api.sectionMapUpdateGuidedPath(
|
||||||
as ManagerAppContext)
|
updatedPath);
|
||||||
.clientAPI!
|
|
||||||
.sectionMapApi!
|
|
||||||
.sectionMapUpdateGuidedPath(
|
|
||||||
updatedPath);
|
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
setState(() {
|
if (mounted) {
|
||||||
paths[index] = result;
|
setState(() {
|
||||||
widget.onChanged(paths);
|
paths[index] = result;
|
||||||
});
|
widget.onChanged(paths);
|
||||||
|
});
|
||||||
|
}
|
||||||
showNotification(
|
showNotification(
|
||||||
kSuccess,
|
kSuccess,
|
||||||
kWhite,
|
kWhite,
|
||||||
@ -221,6 +234,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
|
|||||||
'Erreur lors de la mise à jour du parcours',
|
'Erreur lors de la mise à jour du parcours',
|
||||||
context,
|
context,
|
||||||
null);
|
null);
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
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_api_new/api.dart';
|
||||||
import 'package:manager_app/constants.dart';
|
import 'package:manager_app/constants.dart';
|
||||||
import 'package:manager_app/Components/rounded_button.dart';
|
import 'package:manager_app/Components/rounded_button.dart';
|
||||||
@ -13,7 +15,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
String parentId,
|
String parentId,
|
||||||
bool isEvent,
|
bool isEvent,
|
||||||
bool isEscapeMode,
|
bool isEscapeMode,
|
||||||
Function(GuidedPathDTO) onSave,
|
FutureOr<void> Function(GuidedPathDTO) onSave,
|
||||||
) {
|
) {
|
||||||
GuidedPathDTO workingPath = path != null
|
GuidedPathDTO workingPath = path != null
|
||||||
? GuidedPathDTO.fromJson(jsonDecode(jsonEncode(path)))!
|
? GuidedPathDTO.fromJson(jsonDecode(jsonEncode(path)))!
|
||||||
@ -24,8 +26,11 @@ void showNewOrUpdateGuidedPath(
|
|||||||
order: 0,
|
order: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool isSaving = false;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (context, setState) {
|
builder: (context, setState) {
|
||||||
@ -76,6 +81,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
setState(() => workingPath.title = val),
|
setState(() => workingPath.title = val),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -89,6 +95,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
() => workingPath.description = val),
|
() => workingPath.description = val),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -150,7 +157,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
null,
|
null,
|
||||||
workingPath.id ?? "temp",
|
workingPath.id ?? "temp",
|
||||||
isEscapeMode,
|
isEscapeMode,
|
||||||
(newStep) {
|
(newStep) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
workingPath.steps = [
|
workingPath.steps = [
|
||||||
...(workingPath.steps ?? []),
|
...(workingPath.steps ?? []),
|
||||||
@ -183,7 +190,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
leading:
|
leading:
|
||||||
CircleAvatar(child: Text("${index + 1}")),
|
CircleAvatar(child: Text("${index + 1}")),
|
||||||
title: Text(
|
title: HtmlWidget(
|
||||||
step.title != null && step.title!.isNotEmpty
|
step.title != null && step.title!.isNotEmpty
|
||||||
? step.title!
|
? step.title!
|
||||||
.firstWhere(
|
.firstWhere(
|
||||||
@ -210,7 +217,7 @@ void showNewOrUpdateGuidedPath(
|
|||||||
step,
|
step,
|
||||||
workingPath.id ?? "temp",
|
workingPath.id ?? "temp",
|
||||||
isEscapeMode,
|
isEscapeMode,
|
||||||
(updatedStep) {
|
(updatedStep) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
workingPath.steps![index] =
|
workingPath.steps![index] =
|
||||||
updatedStep;
|
updatedStep;
|
||||||
@ -255,8 +262,11 @@ void showNewOrUpdateGuidedPath(
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 46,
|
height: 46,
|
||||||
child: RoundedButton(
|
child: RoundedButton(
|
||||||
text: "Sauvegarder",
|
text: isSaving ? "Sauvegarde..." : "Sauvegarder",
|
||||||
press: () {
|
icon: isSaving ? Icons.hourglass_empty : null,
|
||||||
|
press: () async {
|
||||||
|
if (isSaving) return;
|
||||||
|
setState(() => isSaving = true);
|
||||||
// Initialise les booleans null → false
|
// Initialise les booleans null → false
|
||||||
workingPath.isLinear ??= false;
|
workingPath.isLinear ??= false;
|
||||||
workingPath.requireSuccessToAdvance ??= false;
|
workingPath.requireSuccessToAdvance ??= false;
|
||||||
@ -267,8 +277,12 @@ void showNewOrUpdateGuidedPath(
|
|||||||
s.isStepTimer ??= false;
|
s.isStepTimer ??= false;
|
||||||
s.isStepLocked ??= false;
|
s.isStepLocked ??= false;
|
||||||
}
|
}
|
||||||
onSave(workingPath);
|
try {
|
||||||
Navigator.pop(context);
|
await onSave(workingPath);
|
||||||
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => isSaving = false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
color: kPrimaryColor,
|
color: kPrimaryColor,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
@ -13,7 +14,7 @@ void showNewOrUpdateGuidedStep(
|
|||||||
GuidedStepDTO? step,
|
GuidedStepDTO? step,
|
||||||
String pathId,
|
String pathId,
|
||||||
bool isEscapeMode,
|
bool isEscapeMode,
|
||||||
Function(GuidedStepDTO) onSave,
|
FutureOr<void> Function(GuidedStepDTO) onSave,
|
||||||
) {
|
) {
|
||||||
// Use jsonEncode/jsonDecode for a robust deep copy that handles nested DTOs correctly
|
// Use jsonEncode/jsonDecode for a robust deep copy that handles nested DTOs correctly
|
||||||
GuidedStepDTO workingStep = step != null
|
GuidedStepDTO workingStep = step != null
|
||||||
@ -25,8 +26,11 @@ void showNewOrUpdateGuidedStep(
|
|||||||
order: 0,
|
order: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool isSaving = false;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (context, setState) {
|
builder: (context, setState) {
|
||||||
@ -73,6 +77,7 @@ void showNewOrUpdateGuidedStep(
|
|||||||
setState(() => workingStep.title = val),
|
setState(() => workingStep.title = val),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
@ -86,6 +91,7 @@ void showNewOrUpdateGuidedStep(
|
|||||||
() => workingStep.description = val),
|
() => workingStep.description = val),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -231,15 +237,21 @@ void showNewOrUpdateGuidedStep(
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 46,
|
height: 46,
|
||||||
child: RoundedButton(
|
child: RoundedButton(
|
||||||
text: "Sauvegarder",
|
text: isSaving ? "Sauvegarde..." : "Sauvegarder",
|
||||||
press: () {
|
icon: isSaving ? Icons.hourglass_empty : null,
|
||||||
|
press: () async {
|
||||||
|
if (isSaving) return;
|
||||||
|
setState(() => isSaving = true);
|
||||||
// Initialise les booleans null → false
|
// Initialise les booleans null → false
|
||||||
// pour éviter l'erreur backend "Error converting null to Boolean"
|
|
||||||
workingStep.isHiddenInitially ??= false;
|
workingStep.isHiddenInitially ??= false;
|
||||||
workingStep.isStepTimer ??= false;
|
workingStep.isStepTimer ??= false;
|
||||||
workingStep.isStepLocked ??= false;
|
workingStep.isStepLocked ??= false;
|
||||||
onSave(workingStep);
|
try {
|
||||||
Navigator.pop(context);
|
await onSave(workingStep);
|
||||||
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => isSaving = false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
color: kPrimaryColor,
|
color: kPrimaryColor,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
|
|||||||
@ -113,6 +113,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
}),
|
}),
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
isTitle: false,
|
isTitle: false,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
|
|
||||||
@ -164,6 +165,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
}),
|
}),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
isHTML: true,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
SizedBox(height: 4),
|
SizedBox(height: 4),
|
||||||
@ -252,6 +254,7 @@ void showNewOrUpdateQuizQuestion(
|
|||||||
val)),
|
val)),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
isTitle: true,
|
isTitle: true,
|
||||||
|
isHTML: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Supprimer
|
// Supprimer
|
||||||
|
|||||||
@ -53,18 +53,40 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
|
|||||||
late SectionDTO sectionDTO;
|
late SectionDTO sectionDTO;
|
||||||
Object? sectionDetailDTO;
|
Object? sectionDetailDTO;
|
||||||
String? lastLoadedSectionId;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appContext = Provider.of<AppContext>(context);
|
final appContext = Provider.of<AppContext>(context);
|
||||||
Size size = MediaQuery.of(context).size;
|
Size size = MediaQuery.of(context).size;
|
||||||
|
|
||||||
GlobalKey globalKey = new GlobalKey();
|
|
||||||
Object? rawSectionData;
|
Object? rawSectionData;
|
||||||
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: getSection(widget.id,
|
future: _sectionFuture,
|
||||||
(appContext.getContext() as ManagerAppContext).clientAPI!),
|
|
||||||
builder: (context, AsyncSnapshot<dynamic> snapshot) {
|
builder: (context, AsyncSnapshot<dynamic> snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
rawSectionData = snapshot.data;
|
rawSectionData = snapshot.data;
|
||||||
@ -82,9 +104,10 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
bodySection(
|
bodySection(
|
||||||
rawSectionData, size, appContext, context, globalKey),
|
rawSectionData, size, appContext, context),
|
||||||
Align(
|
Align(
|
||||||
alignment: AlignmentDirectional.bottomCenter,
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
child: Container(
|
child: Container(
|
||||||
@ -110,7 +133,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget bodySection(Object? rawSectionDTO, Size size, AppContext appContext,
|
Widget bodySection(Object? rawSectionDTO, Size size, AppContext appContext,
|
||||||
BuildContext context, GlobalKey globalKey) {
|
BuildContext context) {
|
||||||
ManagerAppContext managerAppContext = appContext.getContext();
|
ManagerAppContext managerAppContext = appContext.getContext();
|
||||||
//SectionDTO? sectionDTO = SectionDTO.fromJson(rawSectionDTO);
|
//SectionDTO? sectionDTO = SectionDTO.fromJson(rawSectionDTO);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'dart:ui';
|
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:firebase_core/firebase_core.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
@ -50,6 +52,14 @@ Future<void> main() async {
|
|||||||
ChangeNotifierProvider<AppContext>(
|
ChangeNotifierProvider<AppContext>(
|
||||||
create: (_) => AppContext(managerAppContext),
|
create: (_) => AppContext(managerAppContext),
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
|
locale: const Locale('fr'),
|
||||||
|
supportedLocales: const [Locale('fr')],
|
||||||
|
localizationsDelegates: const [
|
||||||
|
FlutterQuillLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
home: FutureBuilder(
|
home: FutureBuilder(
|
||||||
future: getInstanceInfo(managerAppContext),
|
future: getInstanceInfo(managerAppContext),
|
||||||
builder: (context, asyncSnapshot) {
|
builder: (context, asyncSnapshot) {
|
||||||
@ -235,6 +245,14 @@ class _MyAppState extends State<MyApp> {
|
|||||||
const Breakpoint(start: 1921, end: double.infinity, name: '4K'),
|
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(),
|
scrollBehavior: MyCustomScrollBehavior(),
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'MyInfoMate - Manager',
|
title: 'MyInfoMate - Manager',
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import just_audio
|
|||||||
import package_info_plus
|
import package_info_plus
|
||||||
import pasteboard
|
import pasteboard
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import quill_native_bridge_macos
|
||||||
import sqflite
|
import sqflite
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_player_avfoundation
|
import video_player_avfoundation
|
||||||
@ -27,6 +28,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
|
|||||||
309
pubspec.lock
309
pubspec.lock
@ -13,10 +13,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _flutterfire_internals
|
name: _flutterfire_internals
|
||||||
sha256: "0816f12bbbd9e21f72ea8592b11bce4a628d4e5cb7a81ff9f1eee4f3dc23206e"
|
sha256: "50e24b769bd1e725732f0aff18b806b8731c1fbcf4e8018ab98e7c4805a2a52f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.37"
|
version: "1.3.57"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -193,6 +193,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -205,10 +213,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: chewie
|
name: chewie
|
||||||
sha256: "1fc84d88d3b1dc26b1fe799500e2ebcc8916af30ce62595ad802cfd965b60bc3"
|
sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.7.5"
|
||||||
cli_launcher:
|
cli_launcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -249,6 +257,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
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:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -273,6 +289,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
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:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -297,6 +321,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5"
|
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:
|
dio:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -353,30 +385,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
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:
|
firebase_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_core
|
name: firebase_core
|
||||||
sha256: fae4ab4317c2a7afb13d44ef1e3f9f28a630e10016bc5cfe761e8e6a0ed7816a
|
sha256: "5bba5924139e91d26446fd2601c18a6aa62c1161c768a989bb5e245dcdc20644"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.15.0"
|
||||||
firebase_core_platform_interface:
|
firebase_core_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_platform_interface
|
name: firebase_core_platform_interface
|
||||||
sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb"
|
sha256: "8bcfad6d7033f5ea951d15b867622a824b13812178bfec0c779b9d81de011bbb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.0"
|
version: "5.4.2"
|
||||||
firebase_core_web:
|
firebase_core_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_web
|
name: firebase_core_web
|
||||||
sha256: "6643fe3dbd021e6ccfb751f7882b39df355708afbdeb4130fc50f9305a9d1a3d"
|
sha256: eb3afccfc452b2b2075acbe0c4b27de62dd596802b4e5e19869c1e926cbb20b3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.17.2"
|
version: "2.24.0"
|
||||||
firebase_storage:
|
firebase_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -389,18 +437,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_storage_platform_interface
|
name: firebase_storage_platform_interface
|
||||||
sha256: "505dbaa4bceab07216c4e26cb1480bdb69ca03ab59bb7cb63f4681e2f7d5b23e"
|
sha256: ef0fc49be3dc96ebaf6965ec591c124256a1d9f252db31a06ceaa580a72e07a6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.24"
|
version: "5.2.8"
|
||||||
firebase_storage_web:
|
firebase_storage_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_storage_web
|
name: firebase_storage_web
|
||||||
sha256: d3e6f27be96c7ebdf38a43520f19584e55dc45c71f616aa86f135dba97d63a8e
|
sha256: "0b263674a6b4e9a6eeea95873e63cd96d8e7c2bd5cc7aaa57f46d906f61214cb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.9.9"
|
version: "3.10.15"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -430,6 +478,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.2"
|
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:
|
flutter_compass:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -446,6 +502,51 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
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:
|
flutter_map:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -478,6 +579,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.20"
|
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:
|
flutter_svg:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -644,10 +761,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: html
|
name: html
|
||||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -681,13 +806,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "4.2.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.20.2"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -807,6 +932,14 @@ packages:
|
|||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: markdown
|
||||||
|
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1047,38 +1180,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
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:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1151,14 +1252,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
quill_html_editor:
|
quill_native_bridge:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: quill_html_editor
|
name: quill_native_bridge
|
||||||
sha256: "7f46ec06a41b8ec30910afc219f1a447cd2e872614cc3a34ef16600043163f19"
|
sha256: "76a16512e398e84216f3f659f7cb18a89ec1e141ea908e954652b4ce6cf15b18"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
responsive_framework:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1376,10 +1533,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
|
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.4.1"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1464,10 +1621,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_web
|
name: video_player_web
|
||||||
sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2
|
sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.3"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1476,14 +1633,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.0"
|
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:
|
wakelock_plus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: wakelock_plus
|
name: wakelock_plus
|
||||||
sha256: "104d94837bb28c735894dcd592877e990149c380e6358b00c04398ca1426eed4"
|
sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.1.4"
|
||||||
wakelock_plus_platform_interface:
|
wakelock_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1504,26 +1669,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "1.1.1"
|
||||||
web_socket:
|
web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket
|
name: web_socket
|
||||||
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5"
|
version: "1.0.1"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.3"
|
||||||
webview_flutter:
|
webview_flutter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1608,10 +1773,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: youtube_player_iframe_web
|
name: youtube_player_iframe_web
|
||||||
sha256: "743e8ce2ee77b8ea6e36ae986f0ef111b38c3119ae55ae813223ecddeb56b770"
|
sha256: "05222a228937932e7ee7a6171e8020fee4cd23d1c7bf6b4128c569484338c593"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.0-0 <4.0.0"
|
dart: ">=3.8.0 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.32.0"
|
||||||
|
|||||||
@ -69,7 +69,9 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.6
|
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
|
responsive_framework: ^1.4.0
|
||||||
tab_container: ^2.0.0
|
tab_container: ^2.0.0
|
||||||
flutter_widget_from_html: ^0.15.3
|
flutter_widget_from_html: ^0.15.3
|
||||||
@ -77,6 +79,9 @@ dependencies:
|
|||||||
firebase_core: ^3.1.0
|
firebase_core: ^3.1.0
|
||||||
#another_flushbar: ^1.12.30
|
#another_flushbar: ^1.12.30
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
intl: ^0.20.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
#include <firebase_storage/firebase_storage_plugin_c_api.h>
|
#include <firebase_storage/firebase_storage_plugin_c_api.h>
|
||||||
#include <geolocator_windows/geolocator_windows.h>
|
#include <geolocator_windows/geolocator_windows.h>
|
||||||
@ -13,6 +14,8 @@
|
|||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
FirebaseStoragePluginCApiRegisterWithRegistrar(
|
FirebaseStoragePluginCApiRegisterWithRegistrar(
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
file_selector_windows
|
||||||
firebase_core
|
firebase_core
|
||||||
firebase_storage
|
firebase_storage
|
||||||
geolocator_windows
|
geolocator_windows
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user