import 'dart:convert'; import 'dart:developer'; import 'package:defer_pointer/defer_pointer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_canvas_editor/style/canvas_style.dart'; import 'package:flutter_canvas_editor/widgets/elements.dart'; import 'package:toastification/toastification.dart'; import 'package:uuid/uuid.dart'; import '../main.dart'; var uuid = Uuid(); class Editor extends ChangeNotifier { String editorVersion = '0.1m'; String? selectedElmId; bool isEditing = false; final textboxResizerDeferredPointerHandlerLink = DeferredPointerHandlerLink(); // ? Canvas State double canvasScale = 1.0; final CanvasProperty canvasProperty = CanvasProperty( width: 780, height: 1000, ); TransformationController canvasTransformationController = TransformationController(); void setCanvasTransformationInitialZoom(BuildContext context) { final deviceWidth = MediaQuery.of(context).size.width; canvasScale = deviceWidth / canvasProperty.width * 0.9; canvasTransformationController.value = Matrix4.identity()..scale(canvasScale); } void resetCanvasTransformationScale() { print('canvas scale $canvasScale'); canvasTransformationController.value = Matrix4.identity()..scale(canvasScale); } void disposeCanvasTransformationController() { canvasTransformationController.dispose(); } List elementProperties = [ ElementProperty( id: uuid.v4(), valueController: TextEditingController(text: '{{QRCODE}}'), type: ElementType.qr, position: ElementPosition(top: 0, left: 0), width: 80, quarterTurns: 0, elementKey: GlobalKey(), qrScale: 3 ) ]; // int _reservedIdUntil = 0; // ? udpate canvas void updateCanvasProperty(BuildContext context, double width, double height) { canvasProperty.height = height; canvasProperty.width = width; _adjustOutOfBoundElement(); setCanvasTransformationInitialZoom(context); notifyListeners(); } void _adjustOutOfBoundElement() { for (var element in elementProperties) { bool isOutOfBoundFromTop = (element.position.top + 10) > canvasProperty.height; bool isOutOfBoundFromLeft = (element.position.left + 10) > canvasProperty.width; // if (isOutOfBoundFromTop | isOutOfBoundFromLeft) { // element.position.top = 0; // element.position.left = 0; // } if (isOutOfBoundFromTop) { element.position.top = canvasProperty.height - (element.elementKey.currentContext?.size?.height ?? 0); } if (isOutOfBoundFromLeft) { element.position.left = canvasProperty.width - (element.elementKey.currentContext?.size?.width ?? 0); } } } void populateElement(List elementProperties) { this.elementProperties.addAll(elementProperties); // _reservedIdUntil = this.elementProperties.length - 1; notifyListeners(); } void addTextElement(){ String id = uuid.v4(); ElementProperty element = ElementProperty( id: id, valueController: TextEditingController(text: 'Double tap to edit text'), type: ElementType.text, position: ElementPosition(top: 0, left: 0), // size: ElementSize(width: 20, height: 70), width: 20, elementKey: GlobalKey(), quarterTurns: 0 ); elementProperties.add(element); // _reservedIdUntil = elementProperties.length - 1;r notifyListeners(); selectElmById(id); } void addTextboxElement() { String id = uuid.v4(); ElementProperty element = ElementProperty( id: id, valueController: TextEditingController(text: 'Double tap to edit text'), type: ElementType.textbox, position: ElementPosition(top: 0, left: 0), width: 70, quarterTurns: 0, elementKey: GlobalKey() ); elementProperties.add(element); notifyListeners(); selectElmById(id); } // ? Variable Element void addProductNameElement() { String id = uuid.v4(); ElementProperty element = ElementProperty( id: id, valueController: TextEditingController(text: '{{PRODUCTNAME}}'), type: ElementType.textbox, variableType: ElementVariableType.productName, position: ElementPosition(top: 0, left: 0), width: 250, quarterTurns: 0, elementKey: GlobalKey() ); elementProperties.add(element); notifyListeners(); selectElmById(id); } void addVariantNameElement() { String id = uuid.v4(); ElementProperty element = ElementProperty( id: id, valueController: TextEditingController(text: '{{VARIANTNAME}}'), type: ElementType.textbox, variableType: ElementVariableType.variantName, position: ElementPosition(top: 0, left: 0), width: 250, quarterTurns: 0, elementKey: GlobalKey() ); elementProperties.add(element); notifyListeners(); selectElmById(id); } void addProductionCodeElement() { String id = uuid.v4(); ElementProperty element = ElementProperty( id: id, valueController: TextEditingController(text: '{{PRODUCTIONCODE}}'), type: ElementType.textbox, variableType: ElementVariableType.productionCode, position: ElementPosition(top: 0, left: 0), width: 250, quarterTurns: 0, elementKey: GlobalKey() ); elementProperties.add(element); notifyListeners(); selectElmById(id); } void addProductionDateElement() { String id = uuid.v4(); ElementProperty element = ElementProperty( id: id, valueController: TextEditingController(text: '{{PRODUCTIONDATE}}'), type: ElementType.text, variableType: ElementVariableType.productionDate, position: ElementPosition(top: 0, left: 0), width: 250, quarterTurns: 0, elementKey: GlobalKey() ); elementProperties.add(element); notifyListeners(); selectElmById(id); } void addSerialNumberElement() { String id = uuid.v4(); ElementProperty element = ElementProperty( id: id, valueController: TextEditingController(text: '{{SERIALNUMBER}}'), type: ElementType.text, variableType: ElementVariableType.serialNumber, position: ElementPosition(top: 0, left: 0), width: 250, quarterTurns: 0, elementKey: GlobalKey() ); elementProperties.add(element); notifyListeners(); selectElmById(id); } void updateElmPosition(Offset offset) { ElementProperty? element = selectedElm; if (element == null) return; // element.position = ElementPosition(top: offset.dy, left: offset.dx); print(offset.dx); element.position.top += offset.dy.round(); element.position.left += offset.dx.round(); notifyListeners(); } void updateElmWitdh(double width) { ElementProperty? element = selectedElm; if (element == null) return; element.width = width; notifyListeners(); } void toggleLockElement() { if (selectedElm == null) return; selectedElm!.isLocked = !selectedElm!.isLocked; notifyListeners(); } void selectElmById(String id) { selectedElmId = id; notifyListeners(); } void enableEdit() { if (isVariableElement) return; if (selectedElm!.isLocked) { _showLockedToast('Cant modify locked element'); return; } isEditing = true; notifyListeners(); } void disableEdit() { isEditing = false; notifyListeners(); } void unSelectElm() { selectedElmId = null; isEditing = false; notifyListeners(); } bool isSelected(String id) { return selectedElmId == id; } // ? Getters String get selectedElmType { if (elementProperties.isNotEmpty && selectedElmId != null) { final selectedElm = elementProperties.firstWhere((element) => element.id == selectedElmId); return elementGetter(selectedElm.type); } return ''; } ElementProperty? get selectedElm { if (elementProperties.isNotEmpty && selectedElmId != null) { return elementProperties.firstWhere((element) => element.id == selectedElmId); } return null; } GlobalKey? get selectedElmKey { if (elementProperties.isNotEmpty && selectedElmId != null) { return elementProperties.firstWhere((element) => element.id == selectedElmId).elementKey; } return null; } bool get shouldShowFontResizer { if (selectedElm == null) return false; if (![ElementType.text, ElementType.textbox].contains(selectedElm!.type)) return false; return true; } bool get shouldShowQrResizer { if (selectedElm == null) return false; if (selectedElm!.type != ElementType.qr) return false; return true; } bool get shouldShowDeleteElementButton { if (selectedElm == null) return false; if ([ElementType.qr].contains(selectedElm!.type)) return false; return true; } bool get isVariableElement { if (selectedElm == null) return false; if (selectedElm!.variableType == null) return false; return true; } // ? Element overlay bool shouldShowTextboxResizer(String elmId) { if (selectedElmId == null) return false; if (selectedElmId != elmId ) return false; if (selectedElm!.type != ElementType.textbox) return false; return true; } bool shouldShowOverlay(String elmId) { if (selectedElmId == null) return false; if (selectedElmId != elmId ) return false; if (selectedElm!.type == ElementType.qr) return false; return true; } // Toolbar bool get insertElementMode{ if (selectedElm == null) return true; return false; } /// Can only rotate [ElementType.text, ElementType.textBox] void rotate() { ElementProperty? element = selectedElm; if (element == null) return; if (element.isLocked) { _showLockedToast('Cant rotate locked element'); return; } if (![ElementType.text, ElementType.textbox].contains(element.type)) return; if (element.type == ElementType.text) _rotateText(element); if (element.type == ElementType.textbox) _rotateTextBox(element); // Adjust Size // double currentElementHeight = selectedElmKey!.currentContext!.size!.height; // double currentElementWidth = selectedElmKey!.currentContext!.size!.width; // selectedElmKey!.currentContext!.size!.height = currentElementWidth; // selectedElmKey!.currentContext!.size!.width = currentElementHeight; notifyListeners(); } void _rotateText(ElementProperty element) { if (element.quarterTurns < 3) { element.quarterTurns += 1; } else { element.quarterTurns = 0; } } void _rotateTextBox(ElementProperty element) { if (element.quarterTurns == 0) { element.quarterTurns = 3; } else { element.quarterTurns = 0; } } // FontSize Handler void changeFontSize(int? fontSize) { if (fontSize == null) return; if (selectedElm == null) return; if (selectedElm!.isLocked) { _showLockedToast('Cant modify locked element'); return; } selectedElm!.fontScale = fontSize; notifyListeners(); } void incrementFontSize() { if (selectedElm == null) return; final incrementTo = selectedElm!.fontScale + 1; if (selectedElm!.isLocked) { _showLockedToast('Cant modify locked element'); return; } // check if value is allowed for resize if (CanvasStyle.fontSizeMap.containsKey(incrementTo)) { selectedElm!.fontScale = incrementTo; print('kepenjet increase'); } else { print('cant increment'); } notifyListeners(); } void decrementFontSize() { if (selectedElm == null) return; final decrementTo = selectedElm!.fontScale - 1; if (selectedElm!.isLocked) { _showLockedToast('Cant modify locked element'); return; } // check if value is allowed for resize if (CanvasStyle.fontSizeMap.containsKey(decrementTo)) { selectedElm!.fontScale = decrementTo; } else { print('cant decrement'); } notifyListeners(); } // Qr Size Handler void changeQrSize(int? fontSize) { if (fontSize == null) return; if (selectedElm == null) return; if (selectedElm!.isLocked) { _showLockedToast('Cant modify locked element'); return; } selectedElm!.qrScale = fontSize; notifyListeners(); } void incrementQrSize() { if (selectedElm == null) return; if (selectedElm!.type != ElementType.qr) return; final incrementTo = selectedElm!.qrScale! + 1; if (selectedElm!.isLocked) { _showLockedToast('Cant modify locked element'); return; } // check if value is allowed for resize if (CanvasStyle.qrSizeMap.containsKey(incrementTo)) { selectedElm!.qrScale = incrementTo; } else { print('cant increment'); } notifyListeners(); } void decrementQrSize() { if (selectedElm == null) return; if (selectedElm!.type != ElementType.qr) return; final decrementTo = selectedElm!.qrScale! - 1; if (selectedElm!.isLocked) { _showLockedToast('Cant modify locked element'); return; } // check if value is allowed for resize if (CanvasStyle.qrSizeMap.containsKey(decrementTo)) { selectedElm!.qrScale = decrementTo; } else { print('cant decrement'); } notifyListeners(); } // Delete Element Future deleteElement(BuildContext context) async { if (selectedElm == null) return; if (selectedElm!.isLocked) { _showLockedToast('Cant delete locked element'); return; } final shouldDelete = await showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Delete Element ?'), content: Text('Are you sure want to delete this element ?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text('Cancel') ), TextButton( onPressed: () => Navigator.pop(context, true), child: Text('Delete') ), ], ); }, ); if (!shouldDelete) return; elementProperties.removeWhere((e) => e.id == selectedElm!.id); unSelectElm(); notifyListeners(); } // ? snapping element // Offset? snapToElement(Offset newPosition) { // for (var element in elementProperties) { // if ((newPosition.dx - element.)) // } // } // ? helper method void _showLockedToast(String titleText) { toastification.show( title: Text(titleText), description: Text('unlock to change element property'), closeButtonShowType: CloseButtonShowType.none, style: ToastificationStyle.minimal, type: ToastificationType.warning, autoCloseDuration: const Duration(seconds: 3), alignment: Alignment.bottomCenter, dragToClose: true ); } // ? Template to JSON String buildJSON() { var elementsMap = []; var canvasResultMap = { 'title': 'Test', 'width': (canvasProperty.width / 10).round(), 'height': (canvasProperty.height / 10).round(), 'content': { 'version': editorVersion, 'elements': [] } }; for (var element in elementProperties) { var elementMap = { 'id': uuid.v4(), 'content': element.valueController.text, 'height': element.elementKey.currentContext?.size?.height.round() ?? 0, 'width': element.elementKey.currentContext?.size?.width.round() ?? 0, 'position': { 'top': element.position.top.round(), 'left': element.position.left.round() }, 'size': _getElementSizeResult(element), 'rotation': _getElementRotationResult(element.quarterTurns), 'type': _getElementTypeResult(element) }; elementsMap.add(elementMap); } (canvasResultMap['content'] as Map)['elements'] = elementsMap; final templateJsonResult = jsonEncode(canvasResultMap); print(templateJsonResult); log(templateJsonResult); return templateJsonResult; } String _getElementTypeResult(ElementProperty element) { switch (element.type) { case ElementType.text: return 'text'; case ElementType.textbox: return 'textbox'; case ElementType.qr: return 'qrcode'; default: return ''; } } int _getElementRotationResult(int quarterTurns) { switch (quarterTurns) { case 1: return 90; case 2: return 180; case 3: return -90; default: return 0; } } int _getElementSizeResult(ElementProperty element) { if (element.type == ElementType.qr) { return element.qrScale ?? 1; } return element.fontScale; } }