ANTIPALSU Label template editor using flutter

editor.dart 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. import 'dart:developer';
  2. import 'package:defer_pointer/defer_pointer.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_canvas_editor/style/canvas_style.dart';
  5. import 'package:flutter_canvas_editor/widgets/elements.dart';
  6. import 'package:toastification/toastification.dart';
  7. import 'package:uuid/uuid.dart';
  8. import '../main.dart';
  9. var uuid = Uuid();
  10. class Editor extends ChangeNotifier {
  11. String? selectedElmId;
  12. bool isEditing = false;
  13. final textboxResizerDeferredPointerHandlerLink = DeferredPointerHandlerLink();
  14. // ? Canvas State
  15. double canvasScale = 1.0;
  16. final CanvasProperty canvasProperty = CanvasProperty(
  17. width: 780,
  18. height: 1000,
  19. );
  20. TransformationController canvasTransformationController = TransformationController();
  21. void setCanvasTransformationInitialZoom(BuildContext context) {
  22. final deviceWidth = MediaQuery.of(context).size.width;
  23. canvasScale = deviceWidth / canvasProperty.width * 0.9;
  24. canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
  25. }
  26. void resetCanvasTransformationScale() {
  27. print('canvas scale $canvasScale');
  28. canvasTransformationController.value = Matrix4.identity()..scale(canvasScale);
  29. }
  30. void disposeCanvasTransformationController() {
  31. canvasTransformationController.dispose();
  32. }
  33. List<ElementProperty> elementProperties = [
  34. ElementProperty(
  35. id: uuid.v4(),
  36. valueController: TextEditingController(text: '{{QRCODE}}'),
  37. type: ElementType.qr,
  38. position: ElementPosition(top: 0, left: 0),
  39. width: 80,
  40. quarterTurns: 0,
  41. elementKey: GlobalKey(),
  42. qrScale: 3
  43. )
  44. ];
  45. // int _reservedIdUntil = 0;
  46. // ? udpate canvas
  47. void updateCanvasProperty(BuildContext context, double width, double height) {
  48. canvasProperty.height = height;
  49. canvasProperty.width = width;
  50. _adjustOutOfBoundElement();
  51. setCanvasTransformationInitialZoom(context);
  52. notifyListeners();
  53. }
  54. void _adjustOutOfBoundElement() {
  55. for (var element in elementProperties) {
  56. bool isOutOfBoundFromTop = (element.position.top + 10) > canvasProperty.height;
  57. bool isOutOfBoundFromLeft = (element.position.left + 10) > canvasProperty.width;
  58. // if (isOutOfBoundFromTop | isOutOfBoundFromLeft) {
  59. // element.position.top = 0;
  60. // element.position.left = 0;
  61. // }
  62. if (isOutOfBoundFromTop) {
  63. element.position.top = canvasProperty.height - (element.elementKey.currentContext?.size?.height ?? 0);
  64. }
  65. if (isOutOfBoundFromLeft) {
  66. element.position.left = canvasProperty.width - (element.elementKey.currentContext?.size?.width ?? 0);
  67. }
  68. }
  69. }
  70. void populateElement(List<ElementProperty> elementProperties) {
  71. this.elementProperties.addAll(elementProperties);
  72. // _reservedIdUntil = this.elementProperties.length - 1;
  73. notifyListeners();
  74. }
  75. void addTextElement(){
  76. String id = uuid.v4();
  77. ElementProperty element = ElementProperty(
  78. id: id,
  79. valueController: TextEditingController(text: 'Double tap to edit text'),
  80. type: ElementType.text,
  81. position: ElementPosition(top: 0, left: 0),
  82. // size: ElementSize(width: 20, height: 70),
  83. width: 20,
  84. elementKey: GlobalKey(),
  85. quarterTurns: 0
  86. );
  87. elementProperties.add(element);
  88. // _reservedIdUntil = elementProperties.length - 1;r
  89. notifyListeners();
  90. selectElmById(id);
  91. }
  92. void addTextboxElement() {
  93. String id = uuid.v4();
  94. ElementProperty element = ElementProperty(
  95. id: id,
  96. valueController: TextEditingController(text: 'Double tap to edit text'),
  97. type: ElementType.textbox,
  98. position: ElementPosition(top: 0, left: 0),
  99. width: 70,
  100. quarterTurns: 0,
  101. elementKey: GlobalKey()
  102. );
  103. elementProperties.add(element);
  104. notifyListeners();
  105. selectElmById(id);
  106. }
  107. void updateElmPosition(Offset offset) {
  108. ElementProperty? element = selectedElm;
  109. if (element == null) return;
  110. // element.position = ElementPosition(top: offset.dy, left: offset.dx);
  111. print(offset.dx);
  112. element.position.top += offset.dy.round();
  113. element.position.left += offset.dx.round();
  114. notifyListeners();
  115. }
  116. void updateElmWitdh(double width) {
  117. ElementProperty? element = selectedElm;
  118. if (element == null) return;
  119. element.width = width;
  120. notifyListeners();
  121. }
  122. void toggleLockElement() {
  123. if (selectedElm == null) return;
  124. selectedElm!.isLocked = !selectedElm!.isLocked;
  125. notifyListeners();
  126. }
  127. void selectElmById(String id) {
  128. selectedElmId = id;
  129. notifyListeners();
  130. }
  131. void enableEdit() {
  132. if (selectedElm!.isLocked) {
  133. _showLockedToast('Cant modify locked element');
  134. return;
  135. }
  136. isEditing = true;
  137. notifyListeners();
  138. }
  139. void disableEdit() {
  140. isEditing = false;
  141. notifyListeners();
  142. }
  143. void unSelectElm() {
  144. selectedElmId = null;
  145. isEditing = false;
  146. notifyListeners();
  147. }
  148. bool isSelected(String id) {
  149. return selectedElmId == id;
  150. }
  151. // ? Getters
  152. String get selectedElmType {
  153. if (elementProperties.isNotEmpty && selectedElmId != null) {
  154. final selectedElm = elementProperties.firstWhere((element) => element.id == selectedElmId);
  155. return elementGetter(selectedElm.type);
  156. }
  157. return '';
  158. }
  159. ElementProperty? get selectedElm {
  160. if (elementProperties.isNotEmpty && selectedElmId != null) {
  161. return elementProperties.firstWhere((element) => element.id == selectedElmId);
  162. }
  163. return null;
  164. }
  165. GlobalKey? get selectedElmKey {
  166. if (elementProperties.isNotEmpty && selectedElmId != null) {
  167. return elementProperties.firstWhere((element) => element.id == selectedElmId).elementKey;
  168. }
  169. return null;
  170. }
  171. bool get shouldShowFontResizer {
  172. if (selectedElm == null) return false;
  173. if (![ElementType.text, ElementType.textbox].contains(selectedElm!.type)) return false;
  174. return true;
  175. }
  176. bool get shouldShowQrResizer {
  177. if (selectedElm == null) return false;
  178. if (selectedElm!.type != ElementType.qr) return false;
  179. return true;
  180. }
  181. bool get shouldShowDeleteElementButton {
  182. if (selectedElm == null) return false;
  183. if ([ElementType.qr].contains(selectedElm!.type)) return false;
  184. return true;
  185. }
  186. // ? Element overlay
  187. bool shouldShowTextboxResizer(String elmId) {
  188. if (selectedElmId == null) return false;
  189. if (selectedElmId != elmId ) return false;
  190. if (selectedElm!.type != ElementType.textbox) return false;
  191. return true;
  192. }
  193. bool shouldShowOverlay(String elmId) {
  194. if (selectedElmId == null) return false;
  195. if (selectedElmId != elmId ) return false;
  196. if (selectedElm!.type == ElementType.qr) return false;
  197. return true;
  198. }
  199. // Toolbar
  200. bool get insertElementMode{
  201. if (selectedElm == null) return true;
  202. return false;
  203. }
  204. /// Can only rotate [ElementType.text, ElementType.textBox]
  205. void rotate() {
  206. ElementProperty? element = selectedElm;
  207. if (element == null) return;
  208. if (element.isLocked) {
  209. _showLockedToast('Cant rotate locked element');
  210. return;
  211. }
  212. if (![ElementType.text, ElementType.textbox].contains(element.type)) return;
  213. if (element.type == ElementType.text) _rotateText(element);
  214. if (element.type == ElementType.textbox) _rotateTextBox(element);
  215. // Adjust Size
  216. // double currentElementHeight = selectedElmKey!.currentContext!.size!.height;
  217. // double currentElementWidth = selectedElmKey!.currentContext!.size!.width;
  218. // selectedElmKey!.currentContext!.size!.height = currentElementWidth;
  219. // selectedElmKey!.currentContext!.size!.width = currentElementHeight;
  220. notifyListeners();
  221. }
  222. void _rotateText(ElementProperty element) {
  223. if (element.quarterTurns < 3) {
  224. element.quarterTurns += 1;
  225. } else {
  226. element.quarterTurns = 0;
  227. }
  228. }
  229. void _rotateTextBox(ElementProperty element) {
  230. if (element.quarterTurns == 0) {
  231. element.quarterTurns = 3;
  232. } else {
  233. element.quarterTurns = 0;
  234. }
  235. }
  236. // FontSize Handler
  237. void changeFontSize(int? fontSize) {
  238. if (fontSize == null) return;
  239. if (selectedElm == null) return;
  240. if (selectedElm!.isLocked) {
  241. _showLockedToast('Cant modify locked element');
  242. return;
  243. }
  244. selectedElm!.fontScale = fontSize;
  245. notifyListeners();
  246. }
  247. void incrementFontSize() {
  248. if (selectedElm == null) return;
  249. final incrementTo = selectedElm!.fontScale + 1;
  250. if (selectedElm!.isLocked) {
  251. _showLockedToast('Cant modify locked element');
  252. return;
  253. }
  254. // check if value is allowed for resize
  255. if (CanvasStyle.fontSizeMap.containsKey(incrementTo)) {
  256. selectedElm!.fontScale = incrementTo;
  257. print('kepenjet increase');
  258. } else {
  259. print('cant increment');
  260. }
  261. notifyListeners();
  262. }
  263. void decrementFontSize() {
  264. if (selectedElm == null) return;
  265. final decrementTo = selectedElm!.fontScale - 1;
  266. if (selectedElm!.isLocked) {
  267. _showLockedToast('Cant modify locked element');
  268. return;
  269. }
  270. // check if value is allowed for resize
  271. if (CanvasStyle.fontSizeMap.containsKey(decrementTo)) {
  272. selectedElm!.fontScale = decrementTo;
  273. } else {
  274. print('cant decrement');
  275. }
  276. notifyListeners();
  277. }
  278. // Qr Size Handler
  279. void changeQrSize(int? fontSize) {
  280. if (fontSize == null) return;
  281. if (selectedElm == null) return;
  282. if (selectedElm!.isLocked) {
  283. _showLockedToast('Cant modify locked element');
  284. return;
  285. }
  286. selectedElm!.qrScale = fontSize;
  287. notifyListeners();
  288. }
  289. void incrementQrSize() {
  290. if (selectedElm == null) return;
  291. if (selectedElm!.type != ElementType.qr) return;
  292. final incrementTo = selectedElm!.qrScale! + 1;
  293. if (selectedElm!.isLocked) {
  294. _showLockedToast('Cant modify locked element');
  295. return;
  296. }
  297. // check if value is allowed for resize
  298. if (CanvasStyle.qrSizeMap.containsKey(incrementTo)) {
  299. selectedElm!.qrScale = incrementTo;
  300. } else {
  301. print('cant increment');
  302. }
  303. notifyListeners();
  304. }
  305. void decrementQrSize() {
  306. if (selectedElm == null) return;
  307. if (selectedElm!.type != ElementType.qr) return;
  308. final decrementTo = selectedElm!.qrScale! - 1;
  309. if (selectedElm!.isLocked) {
  310. _showLockedToast('Cant modify locked element');
  311. return;
  312. }
  313. // check if value is allowed for resize
  314. if (CanvasStyle.qrSizeMap.containsKey(decrementTo)) {
  315. selectedElm!.qrScale = decrementTo;
  316. } else {
  317. print('cant decrement');
  318. }
  319. notifyListeners();
  320. }
  321. // Delete Element
  322. Future<void> deleteElement(BuildContext context) async {
  323. if (selectedElm == null) return;
  324. if (selectedElm!.isLocked) {
  325. _showLockedToast('Cant delete locked element');
  326. return;
  327. }
  328. final shouldDelete = await showDialog(
  329. context: context,
  330. builder: (context) {
  331. return AlertDialog(
  332. title: Text('Delete Element ?'),
  333. content: Text('Are you sure want to delete this element ?'),
  334. actions: [
  335. TextButton(
  336. onPressed: () => Navigator.pop(context, false),
  337. child: Text('Cancel')
  338. ),
  339. TextButton(
  340. onPressed: () => Navigator.pop(context, true),
  341. child: Text('Delete')
  342. ),
  343. ],
  344. );
  345. },
  346. );
  347. if (!shouldDelete) return;
  348. elementProperties.removeWhere((e) => e.id == selectedElm!.id);
  349. unSelectElm();
  350. notifyListeners();
  351. }
  352. // ? snapping element
  353. // Offset? snapToElement(Offset newPosition) {
  354. // for (var element in elementProperties) {
  355. // if ((newPosition.dx - element.))
  356. // }
  357. // }
  358. // ? helper method
  359. void _showLockedToast(String titleText) {
  360. toastification.show(
  361. title: Text(titleText),
  362. description: Text('unlock to change element property'),
  363. closeButtonShowType: CloseButtonShowType.none,
  364. style: ToastificationStyle.minimal,
  365. type: ToastificationType.warning,
  366. autoCloseDuration: const Duration(seconds: 3),
  367. alignment: Alignment.bottomCenter,
  368. dragToClose: true
  369. );
  370. }
  371. }