ANTIPALSU Label template editor using flutter

editor.dart 26KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  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/history.dart';
  6. import 'package:flutter_canvas_editor/style/canvas_style.dart';
  7. import 'package:flutter_canvas_editor/widgets/elements.dart';
  8. import 'package:toastification/toastification.dart';
  9. import 'package:uuid/uuid.dart';
  10. import 'package:collection/collection.dart';
  11. import '../main.dart';
  12. var uuid = Uuid();
  13. class Editor extends ChangeNotifier {
  14. String editorVersion = '0.1m';
  15. String? selectedElmId;
  16. bool isEditing = false;
  17. final textboxResizerDeferredPointerHandlerLink = DeferredPointerHandlerLink();
  18. String? valueOnStartEditing;
  19. // ? Canvas State
  20. double canvasScale = 1.0;
  21. final CanvasProperty canvasProperty = CanvasProperty(
  22. width: 780,
  23. height: 1000,
  24. );
  25. TransformationController canvasTransformationController = TransformationController();
  26. void setCanvasTransformationInitialZoom(BuildContext context) {
  27. final deviceWidth = MediaQuery.of(context).size.width;
  28. canvasScale = deviceWidth / canvasProperty.width * 0.9;
  29. canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
  30. }
  31. void resetCanvasTransformationScale() {
  32. print('canvas scale $canvasScale');
  33. canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
  34. }
  35. void disposeCanvasTransformationController() {
  36. canvasTransformationController.dispose();
  37. }
  38. // List<ElementProperty> elementProperties = [
  39. // ElementProperty(
  40. // id: uuid.v4(),
  41. // valueController: TextEditingController(text: '{{QRCODE}}'),
  42. // type: ElementType.qr,
  43. // position: ElementPosition(top: 0, left: 0),
  44. // width: 80,
  45. // quarterTurns: 0,
  46. // elementKey: GlobalKey(),
  47. // qrScale: 3
  48. // )
  49. // ];
  50. // This list store all changes history, and currentCanvasState (stackState.last)
  51. List<List<ElementState>> stateStack = [
  52. // ? default state
  53. [
  54. ElementState(
  55. id: uuid.v4(),
  56. valueController: TextEditingController(text: '{{QRCODE}}'),
  57. type: ElementType.qr,
  58. position: ElementPosition(top: 0, left: 0),
  59. width: 80,
  60. quarterTurns: 0,
  61. elementKey: GlobalKey(),
  62. qrScale: 3
  63. )
  64. ]
  65. ];
  66. // current canvas state
  67. List<ElementState> get currentElementsState => stateStack.last;
  68. // ? History stack
  69. // List<CanvasHistory> undoStack = [];
  70. List<List<ElementState>> redoStack = [];
  71. void setNewElementsState(List<ElementState> newElementsState) {
  72. _setNewStateTextEdit(newElementsState);
  73. stateStack.add(newElementsState);
  74. redoStack.clear();
  75. notifyListeners();
  76. }
  77. void undo() {
  78. addRedoEntry(stateStack.last);
  79. // apply to current state
  80. // if (undoStack.last.type == CanvasHistoryModifyType.textEdit && undoStack.last.getState != elementProperties) {
  81. // undoStack.removeLast();
  82. // }
  83. // elementProperties = undoStack.last.getState;
  84. stateStack.removeLast();
  85. // unselect element
  86. if (currentElementsState.firstWhereOrNull((e) => e.id == selectedElmId) == null) {
  87. unSelectElm();
  88. }
  89. notifyListeners();
  90. }
  91. void addRedoEntry(List<ElementState> elementProperties) {
  92. redoStack.add(_cloneElementsState(elementProperties));
  93. notifyListeners();
  94. }
  95. void redo() {
  96. // undoStack.add(CanvasHistory(CanvasHistoryModifyType.redo, _cloneCurrentState(elementProperties)));
  97. stateStack.add(_cloneElementsState(redoStack.last));
  98. // apply to current state
  99. // elementProperties = redoStack.last;
  100. redoStack.removeLast();
  101. notifyListeners();
  102. }
  103. List<ElementState> _cloneElementsState(List<ElementState> elementProperties) {
  104. List<ElementState> clonedElementProperties = elementProperties.map((elementProperty) {
  105. return ElementState(
  106. id: elementProperty.id,
  107. valueController: TextEditingController(text: elementProperty.valueController.text),
  108. type: elementProperty.type,
  109. position: ElementPosition(top: elementProperty.position.top, left: elementProperty.position.left),
  110. width: elementProperty.width,
  111. quarterTurns: elementProperty.quarterTurns,
  112. elementKey: elementProperty.elementKey,
  113. qrScale: elementProperty.qrScale,
  114. fontScale: elementProperty.fontScale,
  115. variableType: elementProperty.variableType,
  116. isLocked: elementProperty.isLocked
  117. );
  118. }).toList();
  119. return clonedElementProperties;
  120. }
  121. // ? udpate canvas
  122. void updateCanvasProperty(BuildContext context, double width, double height) {
  123. canvasProperty.height = height;
  124. canvasProperty.width = width;
  125. // undoStack.clear();
  126. stateStack = [currentElementsState];
  127. redoStack.clear();
  128. _adjustOutOfBoundElement();
  129. setCanvasTransformationInitialZoom(context);
  130. notifyListeners();
  131. }
  132. void _adjustOutOfBoundElement() {
  133. for (var element in currentElementsState) {
  134. bool isOutOfBoundFromTop = (element.position.top + 10) > canvasProperty.height;
  135. bool isOutOfBoundFromLeft = (element.position.left + 10) > canvasProperty.width;
  136. // if (isOutOfBoundFromTop | isOutOfBoundFromLeft) {
  137. // element.position.top = 0;
  138. // element.position.left = 0;
  139. // }
  140. if (isOutOfBoundFromTop) {
  141. element.position.top = canvasProperty.height - (element.elementKey.currentContext?.size?.height ?? 0);
  142. }
  143. if (isOutOfBoundFromLeft) {
  144. element.position.left = canvasProperty.width - (element.elementKey.currentContext?.size?.width ?? 0);
  145. }
  146. }
  147. }
  148. // ? Primitive Element
  149. void addTextElement(){
  150. String id = uuid.v4();
  151. ElementState newElement = ElementState(
  152. id: id,
  153. valueController: TextEditingController(text: 'Double tap to edit text'),
  154. type: ElementType.text,
  155. position: ElementPosition(top: 0, left: 0),
  156. // size: ElementSize(width: 20, height: 70),
  157. width: 20,
  158. elementKey: GlobalKey(),
  159. quarterTurns: 0
  160. );
  161. // Set State
  162. // List<ElementState> newElementsState = [..._cloneElementsState(currentElementsState), newElement];
  163. // setNewElementsState(newElementsState);
  164. _setAddNewElementState(newElement);
  165. notifyListeners();
  166. selectElmById(id);
  167. }
  168. void addTextboxElement() {
  169. String id = uuid.v4();
  170. ElementState newElement = ElementState(
  171. id: id,
  172. valueController: TextEditingController(text: 'Double tap to edit text'),
  173. type: ElementType.textbox,
  174. position: ElementPosition(top: 0, left: 0),
  175. width: 70,
  176. quarterTurns: 0,
  177. elementKey: GlobalKey()
  178. );
  179. // Set State
  180. _setAddNewElementState(newElement);
  181. notifyListeners();
  182. selectElmById(id);
  183. }
  184. // ? Variable Element
  185. void addProductNameElement() {
  186. String id = uuid.v4();
  187. ElementState newElement = ElementState(
  188. id: id,
  189. valueController: TextEditingController(text: '{{PRODUCTNAME}}'),
  190. type: ElementType.textbox,
  191. variableType: ElementVariableType.productName,
  192. position: ElementPosition(top: 0, left: 0),
  193. width: 250,
  194. quarterTurns: 0,
  195. elementKey: GlobalKey()
  196. );
  197. // Set State
  198. _setAddNewElementState(newElement);
  199. notifyListeners();
  200. selectElmById(id);
  201. }
  202. void addVariantNameElement() {
  203. String id = uuid.v4();
  204. ElementState newElement = ElementState(
  205. id: id,
  206. valueController: TextEditingController(text: '{{VARIANTNAME}}'),
  207. type: ElementType.textbox,
  208. variableType: ElementVariableType.variantName,
  209. position: ElementPosition(top: 0, left: 0),
  210. width: 250,
  211. quarterTurns: 0,
  212. elementKey: GlobalKey()
  213. );
  214. // Set State
  215. _setAddNewElementState(newElement);
  216. notifyListeners();
  217. selectElmById(id);
  218. }
  219. void addProductionCodeElement() {
  220. String id = uuid.v4();
  221. ElementState newElement = ElementState(
  222. id: id,
  223. valueController: TextEditingController(text: '{{PRODUCTIONCODE}}'),
  224. type: ElementType.textbox,
  225. variableType: ElementVariableType.productionCode,
  226. position: ElementPosition(top: 0, left: 0),
  227. width: 250,
  228. quarterTurns: 0,
  229. elementKey: GlobalKey()
  230. );
  231. // Set State
  232. _setAddNewElementState(newElement);
  233. notifyListeners();
  234. selectElmById(id);
  235. }
  236. void addProductionDateElement() {
  237. String id = uuid.v4();
  238. ElementState newElement = ElementState(
  239. id: id,
  240. valueController: TextEditingController(text: '{{PRODUCTIONDATE}}'),
  241. type: ElementType.text,
  242. variableType: ElementVariableType.productionDate,
  243. position: ElementPosition(top: 0, left: 0),
  244. width: 250,
  245. quarterTurns: 0,
  246. elementKey: GlobalKey()
  247. );
  248. // Save History
  249. _setAddNewElementState(newElement);
  250. notifyListeners();
  251. selectElmById(id);
  252. }
  253. void addSerialNumberElement() {
  254. String id = uuid.v4();
  255. ElementState newElement = ElementState(
  256. id: id,
  257. valueController: TextEditingController(text: '{{SERIALNUMBER}}'),
  258. type: ElementType.text,
  259. variableType: ElementVariableType.serialNumber,
  260. position: ElementPosition(top: 0, left: 0),
  261. width: 250,
  262. quarterTurns: 0,
  263. elementKey: GlobalKey()
  264. );
  265. // Set State
  266. _setAddNewElementState(newElement);
  267. notifyListeners();
  268. selectElmById(id);
  269. }
  270. void updateElmPosition(Offset offset, {double? fixedTop, double? fixedLeft}) {
  271. ElementState? element = selectedElm;
  272. if (element == null) return;
  273. // element.position = ElementPosition(top: offset.dy, left: offset.dx);
  274. print(offset.dx);
  275. if (fixedTop != null) {
  276. element.position.top = fixedTop;
  277. } else {
  278. element.position.top += offset.dy.round();
  279. }
  280. if (fixedLeft != null) {
  281. print('fixed left: $fixedLeft');
  282. element.position.left = fixedLeft;
  283. } else {
  284. element.position.left += offset.dx.round();
  285. }
  286. notifyListeners();
  287. }
  288. // Reset position after drag end
  289. void resetElmPosition(ElementPosition? elementPosition) {
  290. ElementState? element = selectedElm;
  291. if (element == null) return;
  292. if (elementPosition == null) return;
  293. element.position = elementPosition;
  294. }
  295. void moveElement(Offset offset) {
  296. log('[MOVING ELEMENT]');
  297. ElementState? element = selectedElm;
  298. if (element == null) return;
  299. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  300. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  301. newElement.position.top += offset.dy.round();
  302. newElement.position.left += offset.dx.round();
  303. setNewElementsState(newElementsState);
  304. }
  305. ElementPosition getClonedElementPosition(ElementState element) {
  306. return ElementPosition(
  307. top: element.position.top,
  308. left: element.position.left
  309. );
  310. }
  311. void updateElmWitdh(double width) {
  312. ElementState? element = selectedElm;
  313. if (element == null) return;
  314. element.width = width;
  315. notifyListeners();
  316. }
  317. void commitTextboxResize(double width, double textboxDragStartWidth) {
  318. if (selectedElm == null) return;
  319. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  320. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  321. newElement.width = width;
  322. // reset previous element
  323. _resetPreviousTextboxWidth(selectedElm!, textboxDragStartWidth);
  324. setNewElementsState(newElementsState);
  325. }
  326. void _resetPreviousTextboxWidth(ElementState previousElement, double textboxDragStartWidth) {
  327. if (selectedElm == null) return;
  328. previousElement.width = textboxDragStartWidth;
  329. }
  330. void toggleLockElement() {
  331. if (selectedElm == null) return;
  332. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  333. var selectedNewElement = newElementsState.firstWhere((e) => e.id == selectedElm!.id);
  334. selectedNewElement.isLocked = !selectedNewElement.isLocked;
  335. setNewElementsState(newElementsState);
  336. // selectedElm!.isLocked = !selectedElm!.isLocked;
  337. notifyListeners();
  338. }
  339. bool shouldIgnoreTouch(String elementId) {
  340. if (elementId == selectedElmId) return false;
  341. if (selectedElmId == null) return false;
  342. return true;
  343. }
  344. void selectElmById(String id) {
  345. selectedElmId = id;
  346. valueOnStartEditing = currentElementsState.firstWhere((e) => e.id == selectedElmId).valueController.text;
  347. notifyListeners();
  348. }
  349. void enableEdit() {
  350. if (isVariableElement) return;
  351. if (selectedElm!.isLocked) {
  352. _showLockedToast('Cant modify locked element');
  353. return;
  354. }
  355. isEditing = true;
  356. notifyListeners();
  357. }
  358. void disableEdit() {
  359. isEditing = false;
  360. notifyListeners();
  361. }
  362. void unSelectElm() {
  363. selectedElmId = null;
  364. isEditing = false;
  365. valueOnStartEditing = null;
  366. notifyListeners();
  367. }
  368. bool isSelected(String id) {
  369. return selectedElmId == id;
  370. }
  371. // ? Getters
  372. String get selectedElmType {
  373. if (currentElementsState.isNotEmpty && selectedElmId != null) {
  374. final selectedElm = currentElementsState.firstWhere((element) => element.id == selectedElmId);
  375. return elementGetter(selectedElm.type);
  376. }
  377. return '';
  378. }
  379. ElementState? get selectedElm {
  380. if (currentElementsState.isNotEmpty && selectedElmId != null) {
  381. return currentElementsState.firstWhereOrNull((element) => element.id == selectedElmId);
  382. }
  383. return null;
  384. }
  385. GlobalKey? get selectedElmKey {
  386. if (currentElementsState.isNotEmpty && selectedElmId != null) {
  387. return currentElementsState.firstWhere((element) => element.id == selectedElmId).elementKey;
  388. }
  389. return null;
  390. }
  391. bool get shouldShowFontResizer {
  392. if (selectedElm == null) return false;
  393. if (![ElementType.text, ElementType.textbox].contains(selectedElm!.type)) return false;
  394. return true;
  395. }
  396. bool get shouldShowQrResizer {
  397. if (selectedElm == null) return false;
  398. if (selectedElm!.type != ElementType.qr) return false;
  399. return true;
  400. }
  401. bool get shouldShowDeleteElementButton {
  402. if (selectedElm == null) return false;
  403. if ([ElementType.qr].contains(selectedElm!.type)) return false;
  404. return true;
  405. }
  406. bool get isVariableElement {
  407. if (selectedElm == null) return false;
  408. if (selectedElm!.variableType == null) return false;
  409. return true;
  410. }
  411. // ? Element overlay
  412. bool shouldShowTextboxResizer(String elmId) {
  413. if (selectedElmId == null) return false;
  414. if (selectedElmId != elmId ) return false;
  415. if (selectedElm!.type != ElementType.textbox) return false;
  416. return true;
  417. }
  418. bool shouldShowOverlay(String elmId) {
  419. if (selectedElmId == null) return false;
  420. if (selectedElmId != elmId ) return false;
  421. if (selectedElm!.type == ElementType.qr) return false;
  422. return true;
  423. }
  424. // Toolbar
  425. bool get insertElementMode{
  426. if (selectedElm == null) return true;
  427. return false;
  428. }
  429. /// Can only rotate [ElementType.text, ElementType.textBox]
  430. void rotate() {
  431. if (selectedElm == null) return;
  432. if (selectedElm!.isLocked) {
  433. _showLockedToast('Cant rotate locked element');
  434. return;
  435. }
  436. if (![ElementType.text, ElementType.textbox].contains(selectedElm!.type)) return;
  437. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  438. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  439. if (newElement.type == ElementType.text) _rotateText(newElement);
  440. if (newElement.type == ElementType.textbox) _rotateTextBox(newElement);
  441. setNewElementsState(newElementsState);
  442. // Adjust Size
  443. // double currentElementHeight = selectedElmKey!.currentContext!.size!.height;
  444. // double currentElementWidth = selectedElmKey!.currentContext!.size!.width;
  445. // selectedElmKey!.currentContext!.size!.height = currentElementWidth;
  446. // selectedElmKey!.currentContext!.size!.width = currentElementHeight;
  447. notifyListeners();
  448. }
  449. void editText(TextEditingController controller) {
  450. if (selectedElm == null) return;
  451. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  452. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  453. newElement.valueController.text = controller.text;
  454. // newElement.valueController.selection = TextSelection.collapsed(offset: controller.selection.base.offset);
  455. setNewElementsState(newElementsState);
  456. }
  457. void setValueOnStartEditing(String text) {
  458. valueOnStartEditing = text;
  459. }
  460. // FontSize Handler
  461. void changeFontSize(int? fontSize) {
  462. if (fontSize == null) return;
  463. if (selectedElm == null) return;
  464. if (selectedElm!.isLocked) {
  465. _showLockedToast('Cant modify locked element');
  466. return;
  467. }
  468. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  469. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  470. newElement.fontScale = fontSize;
  471. setNewElementsState(newElementsState);
  472. notifyListeners();
  473. }
  474. void incrementFontSize() {
  475. if (selectedElm == null) return;
  476. final incrementTo = selectedElm!.fontScale + 1;
  477. if (selectedElm!.isLocked) {
  478. _showLockedToast('Cant modify locked element');
  479. return;
  480. }
  481. // check if value is allowed for resize
  482. if (CanvasStyle.fontSizeMap.containsKey(incrementTo)) {
  483. // // ? Save History
  484. // setNewElementsState(CanvasHistoryModifyType.resize, elementProperties);
  485. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  486. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  487. // selectedElm!.fontScale = incrementTo;
  488. newElement.fontScale = incrementTo;
  489. setNewElementsState(newElementsState);
  490. print('kepenjet increase');
  491. } else {
  492. print('cant increment');
  493. }
  494. notifyListeners();
  495. }
  496. void decrementFontSize() {
  497. if (selectedElm == null) return;
  498. final decrementTo = selectedElm!.fontScale - 1;
  499. if (selectedElm!.isLocked) {
  500. _showLockedToast('Cant modify locked element');
  501. return;
  502. }
  503. // check if value is allowed for resize
  504. if (CanvasStyle.fontSizeMap.containsKey(decrementTo)) {
  505. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  506. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  507. newElement.fontScale = decrementTo;
  508. setNewElementsState(newElementsState);
  509. } else {
  510. print('cant decrement');
  511. }
  512. }
  513. // Qr Size Handler
  514. void changeQrSize(int? fontSize) {
  515. if (fontSize == null) return;
  516. if (selectedElm == null) return;
  517. if (selectedElm!.isLocked) {
  518. _showLockedToast('Cant modify locked element');
  519. return;
  520. }
  521. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  522. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  523. // selectedElm!.qrScale = fontSize;
  524. newElement.qrScale = fontSize;
  525. setNewElementsState(newElementsState);
  526. }
  527. void incrementQrSize() {
  528. if (selectedElm == null) return;
  529. if (selectedElm!.type != ElementType.qr) return;
  530. final incrementTo = selectedElm!.qrScale! + 1;
  531. if (selectedElm!.isLocked) {
  532. _showLockedToast('Cant modify locked element');
  533. return;
  534. }
  535. // check if value is allowed for resize
  536. if (CanvasStyle.qrSizeMap.containsKey(incrementTo)) {
  537. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  538. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  539. // selectedElm!.qrScale = incrementTo;
  540. newElement.qrScale = incrementTo;
  541. setNewElementsState(newElementsState);
  542. } else {
  543. print('cant increment');
  544. }
  545. }
  546. void decrementQrSize() {
  547. if (selectedElm == null) return;
  548. if (selectedElm!.type != ElementType.qr) return;
  549. final decrementTo = selectedElm!.qrScale! - 1;
  550. if (selectedElm!.isLocked) {
  551. _showLockedToast('Cant modify locked element');
  552. return;
  553. }
  554. // check if value is allowed for resize
  555. if (CanvasStyle.qrSizeMap.containsKey(decrementTo)) {
  556. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  557. var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
  558. // selectedElm!.qrScale = decrementTo;
  559. newElement.qrScale = decrementTo;
  560. setNewElementsState(newElementsState);
  561. } else {
  562. print('cant decrement');
  563. }
  564. notifyListeners();
  565. }
  566. // Delete Element
  567. Future<void> deleteElement(BuildContext context) async {
  568. if (selectedElm == null) return;
  569. if (selectedElm!.isLocked) {
  570. _showLockedToast('Cant delete locked element');
  571. return;
  572. }
  573. final shouldDelete = await showDialog(
  574. context: context,
  575. builder: (context) {
  576. return AlertDialog(
  577. title: Text('Delete Element ?'),
  578. content: Text('Are you sure want to delete this element ?'),
  579. actions: [
  580. TextButton(
  581. onPressed: () => Navigator.pop(context, false),
  582. child: Text('Cancel')
  583. ),
  584. TextButton(
  585. onPressed: () => Navigator.pop(context, true),
  586. child: Text('Delete')
  587. ),
  588. ],
  589. );
  590. },
  591. );
  592. if (!shouldDelete) return;
  593. // // ? Save History
  594. // setNewElementsState(CanvasHistoryModifyType.remove, elementProperties);
  595. // elementProperties.removeWhere((e) => e.id == selectedElm!.id);
  596. List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
  597. log('selectedElmId: ${selectedElm!.id}');
  598. for (var i = 0; i < newElementsState.length; i++) {
  599. log('element[$i]: ${newElementsState[i].id}');
  600. }
  601. newElementsState.removeWhere((e) => e.id == selectedElm!.id);
  602. setNewElementsState(newElementsState);
  603. unSelectElm();
  604. notifyListeners();
  605. }
  606. // ? snapping element
  607. // Offset? snapToElement(Offset newPosition) {
  608. // for (var element in elementProperties) {
  609. // if ((newPosition.dx - element.))
  610. // }
  611. // }
  612. // ? Template to JSON
  613. String buildJSON() {
  614. var elementsMap = [];
  615. var canvasResultMap = {
  616. 'title': 'Test',
  617. 'width': (canvasProperty.width / 10).round(),
  618. 'height': (canvasProperty.height / 10).round(),
  619. 'content': {
  620. 'version': editorVersion,
  621. 'elements': []
  622. }
  623. };
  624. for (var element in currentElementsState) {
  625. var elementMap = {
  626. 'id': element.id,
  627. 'content': element.valueController.text,
  628. 'height': element.elementKey.currentContext?.size?.height.round() ?? 0,
  629. 'width': element.elementKey.currentContext?.size?.width.round() ?? 0,
  630. 'position': {
  631. 'top': element.position.top.round(),
  632. 'left': element.position.left.round()
  633. },
  634. 'size': _getElementSizeResult(element),
  635. 'rotation': _getElementRotationResult(element.quarterTurns),
  636. 'type': _getElementTypeResult(element)
  637. };
  638. elementsMap.add(elementMap);
  639. }
  640. (canvasResultMap['content'] as Map<String, dynamic>)['elements'] = elementsMap;
  641. final templateJsonResult = jsonEncode(canvasResultMap);
  642. print(templateJsonResult);
  643. log(templateJsonResult);
  644. return templateJsonResult;
  645. }
  646. String _getElementTypeResult(ElementState element) {
  647. switch (element.type) {
  648. case ElementType.text:
  649. return 'text';
  650. case ElementType.textbox:
  651. return 'textbox';
  652. case ElementType.qr:
  653. return 'qrcode';
  654. default:
  655. return '';
  656. }
  657. }
  658. int _getElementRotationResult(int quarterTurns) {
  659. switch (quarterTurns) {
  660. case 1:
  661. return 90;
  662. case 2:
  663. return 180;
  664. case 3:
  665. return -90;
  666. default:
  667. return 0;
  668. }
  669. }
  670. int _getElementSizeResult(ElementState element) {
  671. if (element.type == ElementType.qr) {
  672. return element.qrScale ?? 1;
  673. }
  674. return element.fontScale;
  675. }
  676. // ? Helper Method
  677. void _setAddNewElementState(ElementState newElement) {
  678. List<ElementState> newElementsState = [..._cloneElementsState(currentElementsState), newElement];
  679. setNewElementsState(newElementsState);
  680. }
  681. void _rotateText(ElementState element) {
  682. if (element.quarterTurns < 3) {
  683. element.quarterTurns += 1;
  684. } else {
  685. element.quarterTurns = 0;
  686. }
  687. }
  688. void _rotateTextBox(ElementState element) {
  689. if (element.quarterTurns == 0) {
  690. element.quarterTurns = 3;
  691. } else {
  692. element.quarterTurns = 0;
  693. }
  694. }
  695. void _showLockedToast(String titleText) {
  696. toastification.show(
  697. title: Text(titleText),
  698. description: Text('unlock to change element property'),
  699. closeButtonShowType: CloseButtonShowType.none,
  700. style: ToastificationStyle.minimal,
  701. type: ToastificationType.warning,
  702. autoCloseDuration: const Duration(seconds: 3),
  703. alignment: Alignment.bottomCenter,
  704. dragToClose: true
  705. );
  706. }
  707. void _setNewStateTextEdit(List<ElementState> newElementsState) {
  708. log(selectedElm?.id ?? 'null');
  709. if (selectedElm == null) return;
  710. var newElement = newElementsState.firstWhereOrNull((e) => e.id == selectedElmId);
  711. if (newElement != null && [ElementType.text, ElementType.textbox].contains(newElement.type)) {
  712. newElement.valueController.selection = TextSelection.collapsed(offset: selectedElm!.valueController.selection.base.offset);
  713. }
  714. }
  715. // canvas alignment line helper
  716. bool shouldShowHorizontalCenterLine = false;
  717. void updateShouldShowHorizontalCenterLine(bool value) {
  718. shouldShowHorizontalCenterLine = value;
  719. notifyListeners();
  720. }
  721. bool shouldShowVerticalCenterLine = false;
  722. void updateShouldShowVerticalCenterLine(bool value) {
  723. shouldShowVerticalCenterLine = value;
  724. notifyListeners();
  725. }
  726. }
  727. enum SetActionType { add, remove, move, resize, lock, rotate, textEdit, redo }