ANTIPALSU Label template editor using flutter

editor.dart 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. import 'dart:convert';
  2. import 'dart:developer';
  3. import 'package:defer_pointer/defer_pointer.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter_canvas_editor/style/canvas_style.dart';
  6. import 'package:flutter_canvas_editor/widgets/elements.dart';
  7. import 'package:toastification/toastification.dart';
  8. import 'package:uuid/uuid.dart';
  9. import '../main.dart';
  10. var uuid = Uuid();
  11. class Editor extends ChangeNotifier {
  12. String editorVersion = '0.1m';
  13. String? selectedElmId;
  14. bool isEditing = false;
  15. final textboxResizerDeferredPointerHandlerLink = DeferredPointerHandlerLink();
  16. // ? Canvas State
  17. double canvasScale = 1.0;
  18. final CanvasProperty canvasProperty = CanvasProperty(
  19. width: 780,
  20. height: 1000,
  21. );
  22. TransformationController canvasTransformationController = TransformationController();
  23. void setCanvasTransformationInitialZoom(BuildContext context) {
  24. final deviceWidth = MediaQuery.of(context).size.width;
  25. canvasScale = deviceWidth / canvasProperty.width * 0.9;
  26. canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
  27. }
  28. void resetCanvasTransformationScale() {
  29. print('canvas scale $canvasScale');
  30. canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
  31. }
  32. void disposeCanvasTransformationController() {
  33. canvasTransformationController.dispose();
  34. }
  35. List<ElementProperty> elementProperties = [
  36. ElementProperty(
  37. id: uuid.v4(),
  38. valueController: TextEditingController(text: '{{QRCODE}}'),
  39. type: ElementType.qr,
  40. position: ElementPosition(top: 0, left: 0),
  41. width: 80,
  42. quarterTurns: 0,
  43. elementKey: GlobalKey(),
  44. qrScale: 3
  45. )
  46. ];
  47. // int _reservedIdUntil = 0;
  48. // ? udpate canvas
  49. void updateCanvasProperty(BuildContext context, double width, double height) {
  50. canvasProperty.height = height;
  51. canvasProperty.width = width;
  52. _adjustOutOfBoundElement();
  53. setCanvasTransformationInitialZoom(context);
  54. notifyListeners();
  55. }
  56. void _adjustOutOfBoundElement() {
  57. for (var element in elementProperties) {
  58. bool isOutOfBoundFromTop = (element.position.top + 10) > canvasProperty.height;
  59. bool isOutOfBoundFromLeft = (element.position.left + 10) > canvasProperty.width;
  60. // if (isOutOfBoundFromTop | isOutOfBoundFromLeft) {
  61. // element.position.top = 0;
  62. // element.position.left = 0;
  63. // }
  64. if (isOutOfBoundFromTop) {
  65. element.position.top = canvasProperty.height - (element.elementKey.currentContext?.size?.height ?? 0);
  66. }
  67. if (isOutOfBoundFromLeft) {
  68. element.position.left = canvasProperty.width - (element.elementKey.currentContext?.size?.width ?? 0);
  69. }
  70. }
  71. }
  72. void populateElement(List<ElementProperty> elementProperties) {
  73. this.elementProperties.addAll(elementProperties);
  74. // _reservedIdUntil = this.elementProperties.length - 1;
  75. notifyListeners();
  76. }
  77. void addTextElement(){
  78. String id = uuid.v4();
  79. ElementProperty element = ElementProperty(
  80. id: id,
  81. valueController: TextEditingController(text: 'Double tap to edit text'),
  82. type: ElementType.text,
  83. position: ElementPosition(top: 0, left: 0),
  84. // size: ElementSize(width: 20, height: 70),
  85. width: 20,
  86. elementKey: GlobalKey(),
  87. quarterTurns: 0
  88. );
  89. elementProperties.add(element);
  90. // _reservedIdUntil = elementProperties.length - 1;r
  91. notifyListeners();
  92. selectElmById(id);
  93. }
  94. void addTextboxElement() {
  95. String id = uuid.v4();
  96. ElementProperty element = ElementProperty(
  97. id: id,
  98. valueController: TextEditingController(text: 'Double tap to edit text'),
  99. type: ElementType.textbox,
  100. position: ElementPosition(top: 0, left: 0),
  101. width: 70,
  102. quarterTurns: 0,
  103. elementKey: GlobalKey()
  104. );
  105. elementProperties.add(element);
  106. notifyListeners();
  107. selectElmById(id);
  108. }
  109. // ? Variable Element
  110. void addProductNameElement() {
  111. String id = uuid.v4();
  112. ElementProperty element = ElementProperty(
  113. id: id,
  114. valueController: TextEditingController(text: '{{PRODUCTNAME}}'),
  115. type: ElementType.textbox,
  116. variableType: ElementVariableType.productName,
  117. position: ElementPosition(top: 0, left: 0),
  118. width: 250,
  119. quarterTurns: 0,
  120. elementKey: GlobalKey()
  121. );
  122. elementProperties.add(element);
  123. notifyListeners();
  124. selectElmById(id);
  125. }
  126. void addVariantNameElement() {
  127. String id = uuid.v4();
  128. ElementProperty element = ElementProperty(
  129. id: id,
  130. valueController: TextEditingController(text: '{{VARIANTNAME}}'),
  131. type: ElementType.textbox,
  132. variableType: ElementVariableType.variantName,
  133. position: ElementPosition(top: 0, left: 0),
  134. width: 250,
  135. quarterTurns: 0,
  136. elementKey: GlobalKey()
  137. );
  138. elementProperties.add(element);
  139. notifyListeners();
  140. selectElmById(id);
  141. }
  142. void addProductionCodeElement() {
  143. String id = uuid.v4();
  144. ElementProperty element = ElementProperty(
  145. id: id,
  146. valueController: TextEditingController(text: '{{PRODUCTIONCODE}}'),
  147. type: ElementType.textbox,
  148. variableType: ElementVariableType.productionCode,
  149. position: ElementPosition(top: 0, left: 0),
  150. width: 250,
  151. quarterTurns: 0,
  152. elementKey: GlobalKey()
  153. );
  154. elementProperties.add(element);
  155. notifyListeners();
  156. selectElmById(id);
  157. }
  158. void addProductionDateElement() {
  159. String id = uuid.v4();
  160. ElementProperty element = ElementProperty(
  161. id: id,
  162. valueController: TextEditingController(text: '{{PRODUCTIONDATE}}'),
  163. type: ElementType.text,
  164. variableType: ElementVariableType.productionDate,
  165. position: ElementPosition(top: 0, left: 0),
  166. width: 250,
  167. quarterTurns: 0,
  168. elementKey: GlobalKey()
  169. );
  170. elementProperties.add(element);
  171. notifyListeners();
  172. selectElmById(id);
  173. }
  174. void addSerialNumberElement() {
  175. String id = uuid.v4();
  176. ElementProperty element = ElementProperty(
  177. id: id,
  178. valueController: TextEditingController(text: '{{SERIALNUMBER}}'),
  179. type: ElementType.text,
  180. variableType: ElementVariableType.serialNumber,
  181. position: ElementPosition(top: 0, left: 0),
  182. width: 250,
  183. quarterTurns: 0,
  184. elementKey: GlobalKey()
  185. );
  186. elementProperties.add(element);
  187. notifyListeners();
  188. selectElmById(id);
  189. }
  190. void updateElmPosition(Offset offset) {
  191. ElementProperty? element = selectedElm;
  192. if (element == null) return;
  193. // element.position = ElementPosition(top: offset.dy, left: offset.dx);
  194. print(offset.dx);
  195. element.position.top += offset.dy.round();
  196. element.position.left += offset.dx.round();
  197. notifyListeners();
  198. }
  199. void updateElmWitdh(double width) {
  200. ElementProperty? element = selectedElm;
  201. if (element == null) return;
  202. element.width = width;
  203. notifyListeners();
  204. }
  205. void toggleLockElement() {
  206. if (selectedElm == null) return;
  207. selectedElm!.isLocked = !selectedElm!.isLocked;
  208. notifyListeners();
  209. }
  210. void selectElmById(String id) {
  211. selectedElmId = id;
  212. notifyListeners();
  213. }
  214. void enableEdit() {
  215. if (isVariableElement) return;
  216. if (selectedElm!.isLocked) {
  217. _showLockedToast('Cant modify locked element');
  218. return;
  219. }
  220. isEditing = true;
  221. notifyListeners();
  222. }
  223. void disableEdit() {
  224. isEditing = false;
  225. notifyListeners();
  226. }
  227. void unSelectElm() {
  228. selectedElmId = null;
  229. isEditing = false;
  230. notifyListeners();
  231. }
  232. bool isSelected(String id) {
  233. return selectedElmId == id;
  234. }
  235. // ? Getters
  236. String get selectedElmType {
  237. if (elementProperties.isNotEmpty && selectedElmId != null) {
  238. final selectedElm = elementProperties.firstWhere((element) => element.id == selectedElmId);
  239. return elementGetter(selectedElm.type);
  240. }
  241. return '';
  242. }
  243. ElementProperty? get selectedElm {
  244. if (elementProperties.isNotEmpty && selectedElmId != null) {
  245. return elementProperties.firstWhere((element) => element.id == selectedElmId);
  246. }
  247. return null;
  248. }
  249. GlobalKey? get selectedElmKey {
  250. if (elementProperties.isNotEmpty && selectedElmId != null) {
  251. return elementProperties.firstWhere((element) => element.id == selectedElmId).elementKey;
  252. }
  253. return null;
  254. }
  255. bool get shouldShowFontResizer {
  256. if (selectedElm == null) return false;
  257. if (![ElementType.text, ElementType.textbox].contains(selectedElm!.type)) return false;
  258. return true;
  259. }
  260. bool get shouldShowQrResizer {
  261. if (selectedElm == null) return false;
  262. if (selectedElm!.type != ElementType.qr) return false;
  263. return true;
  264. }
  265. bool get shouldShowDeleteElementButton {
  266. if (selectedElm == null) return false;
  267. if ([ElementType.qr].contains(selectedElm!.type)) return false;
  268. return true;
  269. }
  270. bool get isVariableElement {
  271. if (selectedElm == null) return false;
  272. if (selectedElm!.variableType == null) return false;
  273. return true;
  274. }
  275. // ? Element overlay
  276. bool shouldShowTextboxResizer(String elmId) {
  277. if (selectedElmId == null) return false;
  278. if (selectedElmId != elmId ) return false;
  279. if (selectedElm!.type != ElementType.textbox) return false;
  280. return true;
  281. }
  282. bool shouldShowOverlay(String elmId) {
  283. if (selectedElmId == null) return false;
  284. if (selectedElmId != elmId ) return false;
  285. if (selectedElm!.type == ElementType.qr) return false;
  286. return true;
  287. }
  288. // Toolbar
  289. bool get insertElementMode{
  290. if (selectedElm == null) return true;
  291. return false;
  292. }
  293. /// Can only rotate [ElementType.text, ElementType.textBox]
  294. void rotate() {
  295. ElementProperty? element = selectedElm;
  296. if (element == null) return;
  297. if (element.isLocked) {
  298. _showLockedToast('Cant rotate locked element');
  299. return;
  300. }
  301. if (![ElementType.text, ElementType.textbox].contains(element.type)) return;
  302. if (element.type == ElementType.text) _rotateText(element);
  303. if (element.type == ElementType.textbox) _rotateTextBox(element);
  304. // Adjust Size
  305. // double currentElementHeight = selectedElmKey!.currentContext!.size!.height;
  306. // double currentElementWidth = selectedElmKey!.currentContext!.size!.width;
  307. // selectedElmKey!.currentContext!.size!.height = currentElementWidth;
  308. // selectedElmKey!.currentContext!.size!.width = currentElementHeight;
  309. notifyListeners();
  310. }
  311. void _rotateText(ElementProperty element) {
  312. if (element.quarterTurns < 3) {
  313. element.quarterTurns += 1;
  314. } else {
  315. element.quarterTurns = 0;
  316. }
  317. }
  318. void _rotateTextBox(ElementProperty element) {
  319. if (element.quarterTurns == 0) {
  320. element.quarterTurns = 3;
  321. } else {
  322. element.quarterTurns = 0;
  323. }
  324. }
  325. // FontSize Handler
  326. void changeFontSize(int? fontSize) {
  327. if (fontSize == null) return;
  328. if (selectedElm == null) return;
  329. if (selectedElm!.isLocked) {
  330. _showLockedToast('Cant modify locked element');
  331. return;
  332. }
  333. selectedElm!.fontScale = fontSize;
  334. notifyListeners();
  335. }
  336. void incrementFontSize() {
  337. if (selectedElm == null) return;
  338. final incrementTo = selectedElm!.fontScale + 1;
  339. if (selectedElm!.isLocked) {
  340. _showLockedToast('Cant modify locked element');
  341. return;
  342. }
  343. // check if value is allowed for resize
  344. if (CanvasStyle.fontSizeMap.containsKey(incrementTo)) {
  345. selectedElm!.fontScale = incrementTo;
  346. print('kepenjet increase');
  347. } else {
  348. print('cant increment');
  349. }
  350. notifyListeners();
  351. }
  352. void decrementFontSize() {
  353. if (selectedElm == null) return;
  354. final decrementTo = selectedElm!.fontScale - 1;
  355. if (selectedElm!.isLocked) {
  356. _showLockedToast('Cant modify locked element');
  357. return;
  358. }
  359. // check if value is allowed for resize
  360. if (CanvasStyle.fontSizeMap.containsKey(decrementTo)) {
  361. selectedElm!.fontScale = decrementTo;
  362. } else {
  363. print('cant decrement');
  364. }
  365. notifyListeners();
  366. }
  367. // Qr Size Handler
  368. void changeQrSize(int? fontSize) {
  369. if (fontSize == null) return;
  370. if (selectedElm == null) return;
  371. if (selectedElm!.isLocked) {
  372. _showLockedToast('Cant modify locked element');
  373. return;
  374. }
  375. selectedElm!.qrScale = fontSize;
  376. notifyListeners();
  377. }
  378. void incrementQrSize() {
  379. if (selectedElm == null) return;
  380. if (selectedElm!.type != ElementType.qr) return;
  381. final incrementTo = selectedElm!.qrScale! + 1;
  382. if (selectedElm!.isLocked) {
  383. _showLockedToast('Cant modify locked element');
  384. return;
  385. }
  386. // check if value is allowed for resize
  387. if (CanvasStyle.qrSizeMap.containsKey(incrementTo)) {
  388. selectedElm!.qrScale = incrementTo;
  389. } else {
  390. print('cant increment');
  391. }
  392. notifyListeners();
  393. }
  394. void decrementQrSize() {
  395. if (selectedElm == null) return;
  396. if (selectedElm!.type != ElementType.qr) return;
  397. final decrementTo = selectedElm!.qrScale! - 1;
  398. if (selectedElm!.isLocked) {
  399. _showLockedToast('Cant modify locked element');
  400. return;
  401. }
  402. // check if value is allowed for resize
  403. if (CanvasStyle.qrSizeMap.containsKey(decrementTo)) {
  404. selectedElm!.qrScale = decrementTo;
  405. } else {
  406. print('cant decrement');
  407. }
  408. notifyListeners();
  409. }
  410. // Delete Element
  411. Future<void> deleteElement(BuildContext context) async {
  412. if (selectedElm == null) return;
  413. if (selectedElm!.isLocked) {
  414. _showLockedToast('Cant delete locked element');
  415. return;
  416. }
  417. final shouldDelete = await showDialog(
  418. context: context,
  419. builder: (context) {
  420. return AlertDialog(
  421. title: Text('Delete Element ?'),
  422. content: Text('Are you sure want to delete this element ?'),
  423. actions: [
  424. TextButton(
  425. onPressed: () => Navigator.pop(context, false),
  426. child: Text('Cancel')
  427. ),
  428. TextButton(
  429. onPressed: () => Navigator.pop(context, true),
  430. child: Text('Delete')
  431. ),
  432. ],
  433. );
  434. },
  435. );
  436. if (!shouldDelete) return;
  437. elementProperties.removeWhere((e) => e.id == selectedElm!.id);
  438. unSelectElm();
  439. notifyListeners();
  440. }
  441. // ? snapping element
  442. // Offset? snapToElement(Offset newPosition) {
  443. // for (var element in elementProperties) {
  444. // if ((newPosition.dx - element.))
  445. // }
  446. // }
  447. // ? helper method
  448. void _showLockedToast(String titleText) {
  449. toastification.show(
  450. title: Text(titleText),
  451. description: Text('unlock to change element property'),
  452. closeButtonShowType: CloseButtonShowType.none,
  453. style: ToastificationStyle.minimal,
  454. type: ToastificationType.warning,
  455. autoCloseDuration: const Duration(seconds: 3),
  456. alignment: Alignment.bottomCenter,
  457. dragToClose: true
  458. );
  459. }
  460. // ? Template to JSON
  461. String buildJSON() {
  462. var elementsMap = [];
  463. var canvasResultMap = {
  464. 'title': 'Test',
  465. 'width': (canvasProperty.width / 10).round(),
  466. 'height': (canvasProperty.height / 10).round(),
  467. 'content': {
  468. 'version': editorVersion,
  469. 'elements': []
  470. }
  471. };
  472. for (var element in elementProperties) {
  473. var elementMap = {
  474. 'id': uuid.v4(),
  475. 'content': element.valueController.text,
  476. 'height': element.elementKey.currentContext?.size?.height.round() ?? 0,
  477. 'width': element.elementKey.currentContext?.size?.width.round() ?? 0,
  478. 'position': {
  479. 'top': element.position.top.round(),
  480. 'left': element.position.left.round()
  481. },
  482. 'size': _getElementSizeResult(element),
  483. 'rotation': _getElementRotationResult(element.quarterTurns),
  484. 'type': _getElementTypeResult(element)
  485. };
  486. elementsMap.add(elementMap);
  487. }
  488. (canvasResultMap['content'] as Map<String, dynamic>)['elements'] = elementsMap;
  489. final templateJsonResult = jsonEncode(canvasResultMap);
  490. print(templateJsonResult);
  491. log(templateJsonResult);
  492. return templateJsonResult;
  493. }
  494. String _getElementTypeResult(ElementProperty element) {
  495. switch (element.type) {
  496. case ElementType.text:
  497. return 'text';
  498. case ElementType.textbox:
  499. return 'textbox';
  500. case ElementType.qr:
  501. return 'qrcode';
  502. default:
  503. return '';
  504. }
  505. }
  506. int _getElementRotationResult(int quarterTurns) {
  507. switch (quarterTurns) {
  508. case 1:
  509. return 90;
  510. case 2:
  511. return 180;
  512. case 3:
  513. return -90;
  514. default:
  515. return 0;
  516. }
  517. }
  518. int _getElementSizeResult(ElementProperty element) {
  519. if (element.type == ElementType.qr) {
  520. return element.qrScale ?? 1;
  521. }
  522. return element.fontScale;
  523. }
  524. }