Procházet zdrojové kódy

feat: history-undo-redo (refactored-stable)

Raihan Rizal před 4 měsíci
rodič
revize
56f0bd690d

+ 31 - 2
lib/canvas_setup_page.dart

@@ -51,23 +51,52 @@ class _CanvasSetupPageState extends State<CanvasSetupPage> {
51 51
 
52 52
               TextFormField(
53 53
                 controller: _widthController,
54
-                decoration: InputDecoration(labelText: 'Width'),
54
+                decoration: InputDecoration(labelText: 'Width (mm)'),
55 55
                 keyboardType: TextInputType.number,
56 56
                 validator: (value) {
57 57
                   if (value == null || value.isEmpty) {
58 58
                     return 'Please enter width';
59 59
                   }
60
+
61
+                  double? parsedValue = double.tryParse(value);
62
+
63
+                  if (parsedValue is! double) {
64
+                    return 'Not a valid input';
65
+                  }
66
+
67
+                  if (parsedValue < 30) {
68
+                    return 'Width should be >= 30';
69
+                  }
70
+
71
+                  if (parsedValue > 1000) {
72
+                    return 'Width should be > 1000';
73
+                  }
74
+
60 75
                   return null;
61 76
                 },
62 77
               ),
63 78
               TextFormField(
64 79
                 controller: _heightController,
65
-                decoration: InputDecoration(labelText: 'Height'),
80
+                decoration: InputDecoration(labelText: 'Height (mm)'),
66 81
                 keyboardType: TextInputType.number,
67 82
                 validator: (value) {
68 83
                   if (value == null || value.isEmpty) {
69 84
                     return 'Please enter height';
70 85
                   }
86
+
87
+                  double? parsedValue = double.tryParse(value);
88
+
89
+                  if (parsedValue is! double) {
90
+                    return 'Not a valid input';
91
+                  }
92
+
93
+                  if (parsedValue < 15) {
94
+                    return 'Width should be >= 15';
95
+                  }
96
+
97
+                  if (parsedValue > 1000) {
98
+                    return 'Width should be >= 1000';
99
+                  }
71 100
                   return null;
72 101
                 },
73 102
               ),

+ 2 - 2
lib/history.dart

@@ -5,9 +5,9 @@ enum CanvasHistoryModifyType { add, remove, move, resize, lock, rotate, textEdit
5 5
 
6 6
 class CanvasHistory {
7 7
   final CanvasHistoryModifyType type;
8
-  List<ElementProperty> elementPropeties;
8
+  List<ElementState> elementPropeties;
9 9
 
10 10
   CanvasHistory(this.type, this.elementPropeties);
11 11
 
12
-  List<ElementProperty> get getState => elementPropeties;
12
+  List<ElementState> get getState => elementPropeties;
13 13
 }

+ 2 - 2
lib/main.dart

@@ -107,7 +107,7 @@ class _HomePageState extends State<HomePage> {
107 107
           actions: [
108 108
 
109 109
             IconButton(
110
-              onPressed: Provider.of<Editor>(context, listen: false).undoStack.isEmpty ? null : () {
110
+              onPressed: Provider.of<Editor>(context, listen: false).stateStack.length <= 1 ? null : () {
111 111
                 Provider.of<Editor>(context, listen: false).undo();
112 112
               },
113 113
               icon: Icon(Icons.undo)
@@ -165,7 +165,7 @@ class _HomePageState extends State<HomePage> {
165 165
                             width: Provider.of<Editor>(context).canvasProperty.width,
166 166
                             child: Stack(
167 167
                               children: [
168
-                                for (ElementProperty elementProperty in Provider.of<Editor>(context).elementProperties) ... [
168
+                                for (ElementState elementProperty in Provider.of<Editor>(context).currentElementsState) ... [
169 169
                                   ElementWidget(
170 170
                                     elementProperty: elementProperty,
171 171
                                     canvasProperty: Provider.of<Editor>(context).canvasProperty,

+ 313 - 148
lib/providers/editor.dart

@@ -23,6 +23,7 @@ class Editor extends ChangeNotifier {
23 23
 
24 24
   final textboxResizerDeferredPointerHandlerLink = DeferredPointerHandlerLink();
25 25
 
26
+  String? valueOnStartEditing;
26 27
 
27 28
 
28 29
   // ? Canvas State
@@ -55,29 +56,51 @@ class Editor extends ChangeNotifier {
55 56
 
56 57
 
57 58
 
58
-  List<ElementProperty> elementProperties = [
59
-    ElementProperty(
60
-      id: uuid.v4(), 
61
-      valueController: TextEditingController(text: '{{QRCODE}}'), 
62
-      type: ElementType.qr, 
63
-      position: ElementPosition(top: 0, left: 0), 
64
-      width: 80, 
65
-      quarterTurns: 0, 
66
-      elementKey: GlobalKey(),
67
-      qrScale: 3
68
-    )
59
+  // List<ElementProperty> elementProperties = [
60
+  //   ElementProperty(
61
+  //     id: uuid.v4(), 
62
+  //     valueController: TextEditingController(text: '{{QRCODE}}'), 
63
+  //     type: ElementType.qr, 
64
+  //     position: ElementPosition(top: 0, left: 0), 
65
+  //     width: 80, 
66
+  //     quarterTurns: 0, 
67
+  //     elementKey: GlobalKey(),
68
+  //     qrScale: 3
69
+  //   )
70
+  // ];
71
+
72
+  // This list store all changes history, and currentCanvasState (stackState.last)
73
+  List<List<ElementState>> stateStack = [
74
+    // ? default state
75
+    [
76
+      ElementState(
77
+        id: uuid.v4(), 
78
+        valueController: TextEditingController(text: '{{QRCODE}}'), 
79
+        type: ElementType.qr, 
80
+        position: ElementPosition(top: 0, left: 0), 
81
+        width: 80, 
82
+        quarterTurns: 0, 
83
+        elementKey: GlobalKey(),
84
+        qrScale: 3
85
+      )
86
+    ]
69 87
   ];
70 88
 
89
+  // current canvas state
90
+  List<ElementState> get currentElementsState => stateStack.last;
91
+
71 92
 
72 93
 
73 94
 
74 95
   // ? History stack
75
-  List<CanvasHistory> undoStack = [];
76
-  List<List<ElementProperty>> redoStack = [];
96
+  // List<CanvasHistory> undoStack = [];
97
+  List<List<ElementState>> redoStack = [];
98
+
77 99
 
100
+  void setNewElementsState(List<ElementState> newElementsState) {
101
+    _setNewStateTextEdit(newElementsState);
78 102
 
79
-  void addUndoEntry(CanvasHistoryModifyType canvasHistoryType, List<ElementProperty> elementProperties) {
80
-    undoStack.add(CanvasHistory(canvasHistoryType, _cloneCurrentState(elementProperties)));
103
+    stateStack.add(newElementsState);
81 104
 
82 105
     redoStack.clear();
83 106
 
@@ -85,46 +108,47 @@ class Editor extends ChangeNotifier {
85 108
   }
86 109
 
87 110
   void undo() {
88
-    addRedoEntry(elementProperties);
111
+    addRedoEntry(stateStack.last);
89 112
 
90 113
     // apply to current state
91
-    if (undoStack.last.type == CanvasHistoryModifyType.textEdit && undoStack.last.getState != elementProperties) {
92
-      undoStack.removeLast();
93
-    }
114
+    // if (undoStack.last.type == CanvasHistoryModifyType.textEdit && undoStack.last.getState != elementProperties) {
115
+    //   undoStack.removeLast();
116
+    // }
94 117
 
95
-    elementProperties = undoStack.last.getState;
118
+    // elementProperties = undoStack.last.getState;
119
+
120
+    stateStack.removeLast();
96 121
 
97 122
     // unselect element
98
-    if (elementProperties.firstWhereOrNull((e) => e.id == selectedElmId) == null) {
123
+    if (currentElementsState.firstWhereOrNull((e) => e.id == selectedElmId) == null) {
99 124
       unSelectElm();
100 125
     }
101 126
 
102
-    undoStack.removeLast();
103
-
104 127
     notifyListeners();
105 128
   }
106 129
 
107 130
 
108
-  void addRedoEntry(List<ElementProperty> elementProperties) {
109
-    redoStack.add(_cloneCurrentState(elementProperties));
131
+  void addRedoEntry(List<ElementState> elementProperties) {
132
+    redoStack.add(_cloneElementsState(elementProperties));
110 133
 
111 134
     notifyListeners();
112 135
   }
113 136
 
114 137
   void redo() {
115
-    undoStack.add(CanvasHistory(CanvasHistoryModifyType.redo, _cloneCurrentState(elementProperties)));
138
+    // undoStack.add(CanvasHistory(CanvasHistoryModifyType.redo, _cloneCurrentState(elementProperties)));
139
+    stateStack.add(_cloneElementsState(redoStack.last));
116 140
      
117 141
     // apply to current state
118
-    elementProperties = redoStack.last;
142
+    // elementProperties = redoStack.last;
119 143
 
120 144
     redoStack.removeLast();
121 145
 
122 146
     notifyListeners();
123 147
   }
124 148
 
125
-  List<ElementProperty> _cloneCurrentState(List<ElementProperty> elementProperties) {
126
-    List<ElementProperty> clonedElementProperties = elementProperties.map((elementProperty) {
127
-      return ElementProperty(
149
+  List<ElementState> _cloneElementsState(List<ElementState> elementProperties) {
150
+    List<ElementState> clonedElementProperties = elementProperties.map((elementProperty) {
151
+      return ElementState(
128 152
         id: elementProperty.id,
129 153
         valueController: TextEditingController(text: elementProperty.valueController.text),
130 154
         type: elementProperty.type,
@@ -150,7 +174,8 @@ class Editor extends ChangeNotifier {
150 174
     canvasProperty.height = height;
151 175
     canvasProperty.width = width;
152 176
     
153
-    undoStack.clear();
177
+    // undoStack.clear();
178
+    stateStack = [currentElementsState];
154 179
     redoStack.clear();
155 180
 
156 181
     _adjustOutOfBoundElement();
@@ -161,7 +186,7 @@ class Editor extends ChangeNotifier {
161 186
   }
162 187
 
163 188
   void _adjustOutOfBoundElement() {
164
-    for (var element in elementProperties) {
189
+    for (var element in currentElementsState) {
165 190
       bool isOutOfBoundFromTop = (element.position.top + 10) > canvasProperty.height;
166 191
       bool isOutOfBoundFromLeft = (element.position.left + 10) > canvasProperty.width;
167 192
 
@@ -182,16 +207,16 @@ class Editor extends ChangeNotifier {
182 207
 
183 208
   }
184 209
 
185
-  void populateElement(List<ElementProperty> elementProperties) {
186
-    this.elementProperties.addAll(elementProperties);
187
-    // _reservedIdUntil = this.elementProperties.length - 1;
188
-    notifyListeners();
189
-  }
190 210
 
211
+
212
+
213
+
214
+
215
+  // ? Primitive Element
191 216
   void addTextElement(){
192 217
     String id = uuid.v4();
193 218
 
194
-    ElementProperty element = ElementProperty(
219
+    ElementState newElement = ElementState(
195 220
       id: id, 
196 221
       valueController: TextEditingController(text: 'Double tap to edit text'), 
197 222
       type: ElementType.text, 
@@ -203,21 +228,20 @@ class Editor extends ChangeNotifier {
203 228
     );
204 229
 
205 230
 
206
-    // Save History
207
-    addUndoEntry(CanvasHistoryModifyType.add, elementProperties);
231
+    // Set State
232
+    // List<ElementState> newElementsState = [..._cloneElementsState(currentElementsState), newElement];
233
+    // setNewElementsState(newElementsState);
234
+    _setAddNewElementState(newElement);
208 235
 
209 236
 
210
-    elementProperties.add(element);
211
-    // _reservedIdUntil = elementProperties.length - 1;r
212 237
     notifyListeners();
213
-
214 238
     selectElmById(id);
215 239
   } 
216 240
 
217 241
   void addTextboxElement() {
218 242
     String id = uuid.v4();
219 243
 
220
-    ElementProperty element = ElementProperty(
244
+    ElementState newElement = ElementState(
221 245
       id: id, 
222 246
       valueController: TextEditingController(text: 'Double tap to edit text'), 
223 247
       type: ElementType.textbox, 
@@ -227,21 +251,23 @@ class Editor extends ChangeNotifier {
227 251
       elementKey: GlobalKey()
228 252
     );
229 253
 
230
-    // Save History
231
-    addUndoEntry(CanvasHistoryModifyType.add, elementProperties);
232 254
 
233
-    elementProperties.add(element);
234
-    notifyListeners();
255
+    // Set State
256
+    _setAddNewElementState(newElement);
235 257
 
258
+    notifyListeners();
236 259
     selectElmById(id);
237 260
   }
238 261
 
239
-  // ? Variable Element
240 262
 
263
+
264
+
265
+
266
+  // ? Variable Element
241 267
   void addProductNameElement() {
242 268
     String id = uuid.v4();
243 269
 
244
-    ElementProperty element = ElementProperty(
270
+    ElementState newElement = ElementState(
245 271
       id: id, 
246 272
       valueController: TextEditingController(text: '{{PRODUCTNAME}}'), 
247 273
       type: ElementType.textbox,
@@ -252,19 +278,17 @@ class Editor extends ChangeNotifier {
252 278
       elementKey: GlobalKey()
253 279
     );
254 280
 
255
-    // Save History
256
-    addUndoEntry(CanvasHistoryModifyType.add, elementProperties);
281
+    // Set State
282
+    _setAddNewElementState(newElement);
257 283
 
258
-    elementProperties.add(element);
259 284
     notifyListeners();
260
-
261 285
     selectElmById(id);
262 286
   }
263 287
 
264 288
   void addVariantNameElement() {
265 289
     String id = uuid.v4();
266 290
 
267
-    ElementProperty element = ElementProperty(
291
+    ElementState newElement = ElementState(
268 292
       id: id, 
269 293
       valueController: TextEditingController(text: '{{VARIANTNAME}}'), 
270 294
       type: ElementType.textbox,
@@ -275,19 +299,17 @@ class Editor extends ChangeNotifier {
275 299
       elementKey: GlobalKey()
276 300
     );
277 301
 
278
-    // Save History
279
-    addUndoEntry(CanvasHistoryModifyType.add, elementProperties);
302
+    // Set State
303
+    _setAddNewElementState(newElement);
280 304
 
281
-    elementProperties.add(element);
282 305
     notifyListeners();
283
-
284 306
     selectElmById(id);
285 307
   }
286 308
 
287 309
   void addProductionCodeElement() {
288 310
     String id = uuid.v4();
289 311
 
290
-    ElementProperty element = ElementProperty(
312
+    ElementState newElement = ElementState(
291 313
       id: id, 
292 314
       valueController: TextEditingController(text: '{{PRODUCTIONCODE}}'), 
293 315
       type: ElementType.textbox,
@@ -298,19 +320,17 @@ class Editor extends ChangeNotifier {
298 320
       elementKey: GlobalKey()
299 321
     );
300 322
 
301
-    // Save History
302
-    addUndoEntry(CanvasHistoryModifyType.add, elementProperties);
323
+    // Set State
324
+    _setAddNewElementState(newElement);
303 325
 
304
-    elementProperties.add(element);
305 326
     notifyListeners();
306
-
307 327
     selectElmById(id);
308 328
   }
309 329
 
310 330
   void addProductionDateElement() {
311 331
     String id = uuid.v4();
312 332
 
313
-    ElementProperty element = ElementProperty(
333
+    ElementState newElement = ElementState(
314 334
       id: id, 
315 335
       valueController: TextEditingController(text: '{{PRODUCTIONDATE}}'), 
316 336
       type: ElementType.text,
@@ -322,18 +342,16 @@ class Editor extends ChangeNotifier {
322 342
     );
323 343
 
324 344
     // Save History
325
-    addUndoEntry(CanvasHistoryModifyType.add, elementProperties);
345
+    _setAddNewElementState(newElement);
326 346
 
327
-    elementProperties.add(element);
328 347
     notifyListeners();
329
-
330 348
     selectElmById(id);
331 349
   }
332 350
 
333 351
   void addSerialNumberElement() {
334 352
     String id = uuid.v4();
335 353
 
336
-    ElementProperty element = ElementProperty(
354
+    ElementState newElement = ElementState(
337 355
       id: id, 
338 356
       valueController: TextEditingController(text: '{{SERIALNUMBER}}'), 
339 357
       type: ElementType.text,
@@ -344,21 +362,19 @@ class Editor extends ChangeNotifier {
344 362
       elementKey: GlobalKey()
345 363
     );
346 364
 
347
-    // Save History
348
-    addUndoEntry(CanvasHistoryModifyType.add, elementProperties);
365
+    // Set State
366
+    _setAddNewElementState(newElement);
349 367
 
350
-    elementProperties.add(element);
351 368
     notifyListeners();
352
-
353 369
     selectElmById(id);
354 370
   }
355
-  
371
+
356 372
 
357 373
 
358 374
 
359 375
 
360 376
   void updateElmPosition(Offset offset) {
361
-    ElementProperty? element = selectedElm;
377
+    ElementState? element = selectedElm;
362 378
 
363 379
     if (element == null) return;
364 380
 
@@ -369,8 +385,42 @@ class Editor extends ChangeNotifier {
369 385
     notifyListeners();
370 386
   }
371 387
 
388
+  // Reset position after drag end
389
+  void resetElmPosition(ElementPosition? elementPosition) {
390
+    ElementState? element = selectedElm;
391
+
392
+    if (element == null) return;
393
+
394
+    if (elementPosition == null) return;
395
+
396
+    element.position = elementPosition;
397
+  }
398
+
399
+  void moveElement(Offset offset) {
400
+    log('[MOVING ELEMENT]');
401
+    ElementState? element = selectedElm;
402
+
403
+    if (element == null) return;
404
+
405
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
406
+    var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
407
+
408
+    newElement.position.top += offset.dy.round();
409
+    newElement.position.left += offset.dx.round();
410
+
411
+    setNewElementsState(newElementsState);
412
+  }
413
+
414
+  ElementPosition getClonedElementPosition(ElementState element) {
415
+    return ElementPosition(
416
+      top: element.position.top, 
417
+      left: element.position.left
418
+    );
419
+  }
420
+
421
+
372 422
   void updateElmWitdh(double width) {
373
-    ElementProperty? element = selectedElm;
423
+    ElementState? element = selectedElm;
374 424
 
375 425
     if (element == null) return;
376 426
 
@@ -379,19 +429,51 @@ class Editor extends ChangeNotifier {
379 429
     notifyListeners();
380 430
   }
381 431
 
432
+  void commitTextboxResize(double width, double textboxDragStartWidth) {
433
+    if (selectedElm == null) return;
434
+
435
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
436
+    var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
437
+
438
+    newElement.width = width;
439
+
440
+    // reset previous element
441
+    _resetPreviousTextboxWidth(selectedElm!, textboxDragStartWidth);
442
+
443
+    setNewElementsState(newElementsState);
444
+  }
445
+
446
+  void _resetPreviousTextboxWidth(ElementState previousElement, double textboxDragStartWidth) {
447
+    if (selectedElm == null) return;
448
+
449
+    previousElement.width = textboxDragStartWidth;
450
+  }
451
+
382 452
   void toggleLockElement() {
383 453
     if (selectedElm == null) return;
384 454
 
385
-    // ? Save History
386
-    addUndoEntry(CanvasHistoryModifyType.lock, elementProperties);
455
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
456
+    var selectedNewElement = newElementsState.firstWhere((e) => e.id == selectedElm!.id);
457
+    
458
+    selectedNewElement.isLocked = !selectedNewElement.isLocked;
387 459
 
388
-    selectedElm!.isLocked = !selectedElm!.isLocked;
460
+    setNewElementsState(newElementsState);
461
+
462
+    // selectedElm!.isLocked = !selectedElm!.isLocked;
389 463
     notifyListeners();
390 464
   }
391 465
 
466
+  bool shouldIgnoreTouch(String elementId) {
467
+    if (elementId == selectedElmId) return false;
468
+
469
+    if (selectedElmId == null) return false;
470
+
471
+    return true;
472
+  }
392 473
 
393 474
   void selectElmById(String id) {
394 475
     selectedElmId = id;
476
+    valueOnStartEditing = currentElementsState.firstWhere((e) => e.id == selectedElmId).valueController.text;
395 477
     notifyListeners();
396 478
   }
397 479
 
@@ -415,6 +497,7 @@ class Editor extends ChangeNotifier {
415 497
   void unSelectElm() {
416 498
     selectedElmId = null;
417 499
     isEditing = false;
500
+    valueOnStartEditing = null;
418 501
     notifyListeners();
419 502
   }
420 503
 
@@ -424,25 +507,25 @@ class Editor extends ChangeNotifier {
424 507
 
425 508
   // ? Getters
426 509
   String get selectedElmType {
427
-    if (elementProperties.isNotEmpty && selectedElmId != null) {
428
-      final selectedElm = elementProperties.firstWhere((element) => element.id == selectedElmId);
510
+    if (currentElementsState.isNotEmpty && selectedElmId != null) {
511
+      final selectedElm = currentElementsState.firstWhere((element) => element.id == selectedElmId);
429 512
       return elementGetter(selectedElm.type);
430 513
     }
431 514
 
432 515
     return '';
433 516
   }
434 517
 
435
-  ElementProperty? get selectedElm {
436
-    if (elementProperties.isNotEmpty && selectedElmId != null) {
437
-      return elementProperties.firstWhere((element) => element.id == selectedElmId);
518
+  ElementState? get selectedElm {
519
+    if (currentElementsState.isNotEmpty && selectedElmId != null) {
520
+      return currentElementsState.firstWhereOrNull((element) => element.id == selectedElmId);
438 521
     }
439 522
 
440 523
     return null;
441 524
   }
442 525
 
443 526
   GlobalKey? get selectedElmKey {
444
-    if (elementProperties.isNotEmpty && selectedElmId != null) {
445
-      return elementProperties.firstWhere((element) => element.id == selectedElmId).elementKey;
527
+    if (currentElementsState.isNotEmpty && selectedElmId != null) {
528
+      return currentElementsState.firstWhere((element) => element.id == selectedElmId).elementKey;
446 529
     }
447 530
 
448 531
     return null;
@@ -513,23 +596,25 @@ class Editor extends ChangeNotifier {
513 596
 
514 597
   /// Can only rotate [ElementType.text, ElementType.textBox]
515 598
   void rotate() {
516
-    ElementProperty? element = selectedElm;
517
-
518
-    if (element == null) return;
599
+    if (selectedElm == null) return;
519 600
 
520
-    if (element.isLocked) {
601
+    if (selectedElm!.isLocked) {
521 602
       _showLockedToast('Cant rotate locked element');
522 603
       return;
523 604
     }
524 605
     
525
-    if (![ElementType.text, ElementType.textbox].contains(element.type)) return;
606
+    if (![ElementType.text, ElementType.textbox].contains(selectedElm!.type)) return;
526 607
     
527
-    // ? Save history
528
-    addUndoEntry(CanvasHistoryModifyType.rotate, elementProperties);
608
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
609
+    var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
610
+
611
+
612
+    if (newElement.type == ElementType.text) _rotateText(newElement);
613
+
614
+    if (newElement.type == ElementType.textbox) _rotateTextBox(newElement);
529 615
 
530
-    if (element.type == ElementType.text) _rotateText(element);
531 616
 
532
-    if (element.type == ElementType.textbox) _rotateTextBox(element);
617
+    setNewElementsState(newElementsState);
533 618
 
534 619
 
535 620
     // Adjust Size
@@ -544,22 +629,26 @@ class Editor extends ChangeNotifier {
544 629
 
545 630
   }
546 631
 
547
-  void _rotateText(ElementProperty element) {
548
-    if (element.quarterTurns < 3) {
549
-      element.quarterTurns += 1;
550
-    } else {
551
-      element.quarterTurns = 0;
552
-    }
632
+  
633
+
634
+
635
+  void editText(TextEditingController controller) {
636
+    if (selectedElm == null) return;
637
+
638
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
639
+    var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
640
+
641
+    newElement.valueController.text = controller.text;
642
+    // newElement.valueController.selection = TextSelection.collapsed(offset: controller.selection.base.offset);
643
+    
644
+    setNewElementsState(newElementsState);
553 645
   }
554 646
 
555
-  void _rotateTextBox(ElementProperty element) {
556
-    if (element.quarterTurns == 0) {
557
-      element.quarterTurns = 3;
558
-    } else {
559
-      element.quarterTurns = 0;
560
-    }
647
+  void setValueOnStartEditing(String text) {
648
+    valueOnStartEditing = text;
561 649
   }
562 650
 
651
+
563 652
   // FontSize Handler
564 653
   void changeFontSize(int? fontSize) {
565 654
     if (fontSize == null) return;
@@ -571,10 +660,13 @@ class Editor extends ChangeNotifier {
571 660
       return;
572 661
     }
573 662
 
574
-    // ? Save History
575
-    addUndoEntry(CanvasHistoryModifyType.resize, elementProperties);
663
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
664
+    var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
665
+
666
+    newElement.fontScale = fontSize;
667
+
668
+    setNewElementsState(newElementsState);
576 669
 
577
-    selectedElm!.fontScale = fontSize;
578 670
     notifyListeners();
579 671
   }
580 672
   
@@ -589,10 +681,16 @@ class Editor extends ChangeNotifier {
589 681
     }
590 682
     // check if value is allowed for resize
591 683
     if (CanvasStyle.fontSizeMap.containsKey(incrementTo)) {
592
-      // ? Save History
593
-      addUndoEntry(CanvasHistoryModifyType.resize, elementProperties);
684
+      // // ? Save History
685
+      // setNewElementsState(CanvasHistoryModifyType.resize, elementProperties);
686
+
687
+      List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
688
+      var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
689
+
690
+      // selectedElm!.fontScale = incrementTo;
691
+      newElement.fontScale = incrementTo;
594 692
 
595
-      selectedElm!.fontScale = incrementTo;
693
+      setNewElementsState(newElementsState);
596 694
       print('kepenjet increase');
597 695
     } else {
598 696
       print('cant increment');
@@ -612,15 +710,15 @@ class Editor extends ChangeNotifier {
612 710
     }
613 711
     // check if value is allowed for resize
614 712
     if (CanvasStyle.fontSizeMap.containsKey(decrementTo)) {
615
-      // ? Save History
616
-      addUndoEntry(CanvasHistoryModifyType.resize, elementProperties);
713
+      List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
714
+      var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
617 715
 
618
-      selectedElm!.fontScale = decrementTo;
716
+      newElement.fontScale = decrementTo;
717
+
718
+      setNewElementsState(newElementsState);
619 719
     } else {
620 720
       print('cant decrement');
621 721
     }
622
-
623
-    notifyListeners();
624 722
   }
625 723
 
626 724
   // Qr Size Handler
@@ -634,8 +732,13 @@ class Editor extends ChangeNotifier {
634 732
       return;
635 733
     }
636 734
 
637
-    selectedElm!.qrScale = fontSize;
638
-    notifyListeners();
735
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
736
+    var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
737
+
738
+    // selectedElm!.qrScale = fontSize;
739
+    newElement.qrScale = fontSize;
740
+
741
+    setNewElementsState(newElementsState);
639 742
   }
640 743
   
641 744
   void incrementQrSize() {
@@ -651,12 +754,17 @@ class Editor extends ChangeNotifier {
651 754
     }
652 755
     // check if value is allowed for resize
653 756
     if (CanvasStyle.qrSizeMap.containsKey(incrementTo)) {
654
-      selectedElm!.qrScale = incrementTo;
757
+      List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
758
+      var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
759
+
760
+      // selectedElm!.qrScale = incrementTo;
761
+      newElement.qrScale = incrementTo;
762
+
763
+      setNewElementsState(newElementsState);
655 764
     } else {
656 765
       print('cant increment');
657 766
     }
658 767
 
659
-    notifyListeners();
660 768
   }
661 769
 
662 770
   void decrementQrSize() {
@@ -674,7 +782,13 @@ class Editor extends ChangeNotifier {
674 782
 
675 783
     // check if value is allowed for resize
676 784
     if (CanvasStyle.qrSizeMap.containsKey(decrementTo)) {
677
-      selectedElm!.qrScale = decrementTo;
785
+      List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
786
+      var newElement = newElementsState.firstWhere((e) => e.id == selectedElmId);
787
+
788
+      // selectedElm!.qrScale = decrementTo;
789
+      newElement.qrScale = decrementTo;
790
+
791
+      setNewElementsState(newElementsState);
678 792
     } else {
679 793
       print('cant decrement');
680 794
     }
@@ -713,12 +827,22 @@ class Editor extends ChangeNotifier {
713 827
 
714 828
     if (!shouldDelete) return;
715 829
 
716
-    // ? Save History
717
-    addUndoEntry(CanvasHistoryModifyType.remove, elementProperties);
830
+    // // ? Save History
831
+    // setNewElementsState(CanvasHistoryModifyType.remove, elementProperties);
718 832
 
719
-    elementProperties.removeWhere((e) => e.id == selectedElm!.id);
720
-    unSelectElm();
833
+    // elementProperties.removeWhere((e) => e.id == selectedElm!.id);
834
+
835
+    List<ElementState> newElementsState = _cloneElementsState(currentElementsState);
836
+    log('selectedElmId: ${selectedElm!.id}');
837
+
838
+    for (var i = 0; i < newElementsState.length; i++) {
839
+      log('element[$i]: ${newElementsState[i].id}');
840
+    }
841
+    newElementsState.removeWhere((e) => e.id == selectedElm!.id);
721 842
 
843
+    setNewElementsState(newElementsState);
844
+    
845
+    unSelectElm();
722 846
     notifyListeners();
723 847
   }
724 848
 
@@ -730,22 +854,6 @@ class Editor extends ChangeNotifier {
730 854
   //   }
731 855
   // }
732 856
 
733
-  // ? helper method
734
-  void _showLockedToast(String titleText) {
735
-    toastification.show(
736
-      title: Text(titleText),
737
-      description: Text('unlock to change element property'),
738
-      closeButtonShowType: CloseButtonShowType.none,
739
-      style: ToastificationStyle.minimal,
740
-      type: ToastificationType.warning,
741
-      autoCloseDuration: const Duration(seconds: 3),
742
-      alignment: Alignment.bottomCenter,
743
-      dragToClose: true
744
-    );
745
-  }
746
-
747
-
748
-
749 857
 
750 858
   // ? Template to JSON
751 859
   String buildJSON() {
@@ -762,9 +870,9 @@ class Editor extends ChangeNotifier {
762 870
       }
763 871
     };
764 872
 
765
-    for (var element in elementProperties) {
873
+    for (var element in currentElementsState) {
766 874
       var elementMap = {
767
-        'id': uuid.v4(),
875
+        'id': element.id,
768 876
         'content': element.valueController.text,
769 877
         'height': element.elementKey.currentContext?.size?.height.round() ?? 0,
770 878
         'width': element.elementKey.currentContext?.size?.width.round() ?? 0,
@@ -793,7 +901,7 @@ class Editor extends ChangeNotifier {
793 901
   }
794 902
 
795 903
 
796
-  String _getElementTypeResult(ElementProperty element) {
904
+  String _getElementTypeResult(ElementState element) {
797 905
     switch (element.type) {
798 906
       case ElementType.text:
799 907
         return 'text';
@@ -819,11 +927,68 @@ class Editor extends ChangeNotifier {
819 927
     }
820 928
   }
821 929
 
822
-  int _getElementSizeResult(ElementProperty element) {
930
+  int _getElementSizeResult(ElementState element) {
823 931
     if (element.type == ElementType.qr) {
824 932
       return element.qrScale ?? 1;
825 933
     }
826 934
 
827 935
     return element.fontScale;
828 936
   }
829
-}
937
+
938
+
939
+
940
+
941
+
942
+
943
+  // ? Helper Method
944
+
945
+  void _setAddNewElementState(ElementState newElement) {
946
+    List<ElementState> newElementsState = [..._cloneElementsState(currentElementsState), newElement];
947
+    setNewElementsState(newElementsState);
948
+  }
949
+
950
+  void _rotateText(ElementState element) {
951
+    if (element.quarterTurns < 3) {
952
+      element.quarterTurns += 1;
953
+    } else {
954
+      element.quarterTurns = 0;
955
+    }
956
+  }
957
+
958
+  void _rotateTextBox(ElementState element) {
959
+    if (element.quarterTurns == 0) {
960
+      element.quarterTurns = 3;
961
+    } else {
962
+      element.quarterTurns = 0;
963
+    }
964
+  }
965
+
966
+
967
+  void _showLockedToast(String titleText) {
968
+    toastification.show(
969
+      title: Text(titleText),
970
+      description: Text('unlock to change element property'),
971
+      closeButtonShowType: CloseButtonShowType.none,
972
+      style: ToastificationStyle.minimal,
973
+      type: ToastificationType.warning,
974
+      autoCloseDuration: const Duration(seconds: 3),
975
+      alignment: Alignment.bottomCenter,
976
+      dragToClose: true
977
+    );
978
+  }
979
+
980
+  void _setNewStateTextEdit(List<ElementState> newElementsState) {
981
+    log(selectedElm?.id ?? 'null');
982
+    if (selectedElm == null) return;
983
+    
984
+    
985
+    var newElement = newElementsState.firstWhereOrNull((e) => e.id == selectedElmId);
986
+
987
+    if (newElement != null && [ElementType.text, ElementType.textbox].contains(newElement.type)) {
988
+      newElement.valueController.selection = TextSelection.collapsed(offset: selectedElm!.valueController.selection.base.offset);
989
+    }
990
+  }
991
+}
992
+
993
+
994
+enum SetActionType { add, remove, move, resize, lock, rotate, textEdit, redo }

+ 32 - 0
lib/utils/debouncer.dart

@@ -0,0 +1,32 @@
1
+import 'dart:async';
2
+
3
+class Debouncer {
4
+  static Timer? _timer;
5
+
6
+  static void run(void Function() callback, [Duration duration = const Duration(milliseconds: 500)]) {
7
+    if (_timer?.isActive ?? false) {
8
+      _timer!.cancel();
9
+    
10
+    }
11
+
12
+    _timer = Timer(duration, callback);
13
+  }
14
+}
15
+
16
+
17
+void main() {
18
+  // Debouncer debouncer = Debouncer(Duration(seconds: 5));
19
+  print('program running');
20
+
21
+
22
+  // debouncer.run(() {
23
+  //   print('Terdebounce');
24
+  // });
25
+
26
+  
27
+
28
+  Debouncer.run(
29
+    () => print('terdebounce'),
30
+    Duration(seconds: 5)
31
+  );
32
+}

+ 338 - 303
lib/widgets/elements.dart

@@ -7,6 +7,7 @@ import 'package:flutter/gestures.dart';
7 7
 import 'package:flutter/material.dart';
8 8
 import 'package:flutter_canvas_editor/history.dart';
9 9
 import 'package:flutter_canvas_editor/providers/editor.dart';
10
+import 'package:flutter_canvas_editor/utils/debouncer.dart';
10 11
 import 'package:intl/intl.dart';
11 12
 import 'package:provider/provider.dart';
12 13
 import 'dart:math' as math;
@@ -67,7 +68,7 @@ class ElementSize {
67 68
   ElementSize({required this.width, required this.height});
68 69
 }
69 70
 
70
-class ElementProperty {
71
+class ElementState {
71 72
   String id;
72 73
   TextEditingController valueController;
73 74
   ElementType type;
@@ -83,7 +84,7 @@ class ElementProperty {
83 84
   bool isLocked;
84 85
   String? lastSavedText;
85 86
 
86
-  ElementProperty({
87
+  ElementState({
87 88
     required this.id,
88 89
     required this.valueController,
89 90
     required this.type,
@@ -105,7 +106,7 @@ class ElementProperty {
105 106
 
106 107
 
107 108
 class ElementWidget extends StatefulWidget {
108
-  final ElementProperty elementProperty;
109
+  final ElementState elementProperty;
109 110
   final CanvasProperty canvasProperty;
110 111
   final GlobalKey globalKey;
111 112
   
@@ -121,7 +122,11 @@ class ElementWidget extends StatefulWidget {
121 122
 }
122 123
 
123 124
 class _ElementWidgetState extends State<ElementWidget> {
124
-  Offset? _dragStartPosition;
125
+  Offset? _textboxDragStartPosition;
126
+  double? _textboxDragStartWidth;
127
+
128
+  ElementPosition? _elementDragStartPosition;
129
+  // String? _valueOnStartEditing;
125 130
 
126 131
   // dragable container 
127 132
   final double _resizerHeight = 36;
@@ -132,7 +137,7 @@ class _ElementWidgetState extends State<ElementWidget> {
132 137
   double _currentScale = 1.0;
133 138
 
134 139
   late TransformationController _transformController;
135
-
140
+  
136 141
   // // TODO: pindhkan ke state element property agar bisa dipake di widget Toolbar
137 142
   // String? _lastSavedText;
138 143
 
@@ -142,6 +147,8 @@ class _ElementWidgetState extends State<ElementWidget> {
142 147
   void initState() {
143 148
     super.initState();
144 149
     
150
+    // _valueOnStartEditing = widget.elementProperty.valueController.text;
151
+
145 152
     WidgetsBinding.instance.addPostFrameCallback((_) => _updateScale);
146 153
 
147 154
     _transformController = Provider.of<Editor>(context, listen: false).canvasTransformationController;
@@ -149,6 +156,8 @@ class _ElementWidgetState extends State<ElementWidget> {
149 156
     _setInitialScale();
150 157
     _listenTransformationChanges();
151 158
     _addFocusNodeListener();
159
+
160
+    widget.elementProperty.valueController.addListener(() {});
152 161
   }
153 162
 
154 163
 
@@ -180,7 +189,9 @@ class _ElementWidgetState extends State<ElementWidget> {
180 189
   void _onTextFieldFocusChange() {
181 190
     if (widget.elementProperty.lastSavedText != widget.elementProperty.valueController.text) {
182 191
       print('executed');
183
-      Provider.of<Editor>(context, listen: false).addUndoEntry(CanvasHistoryModifyType.textEdit, Provider.of<Editor>(context, listen:  false).elementProperties);
192
+      // Provider.of<Editor>(context, listen: false).setNewElementsState(CanvasHistoryModifyType.textEdit, Provider.of<Editor>(context, listen:  false).elementProperties);
193
+
194
+
184 195
       widget.elementProperty.lastSavedText = widget.elementProperty.valueController.text;
185 196
     }
186 197
   }
@@ -206,324 +217,352 @@ class _ElementWidgetState extends State<ElementWidget> {
206 217
   Widget build(BuildContext context) {
207 218
     final editorProvider = Provider.of<Editor>(context);
208 219
 
209
-    ElementProperty element = widget.elementProperty;
220
+    ElementState element = widget.elementProperty;
210 221
     final CanvasProperty canvas = widget.canvasProperty;
211 222
 
212 223
     WidgetsBinding.instance.addPostFrameCallback((_) {
213 224
       _height = element.elementKey.currentContext!.size!.height;
214 225
     });
215 226
 
216
-    List<ElementProperty> currentCanvasState = editorProvider.elementProperties;
227
+    // List<ElementState> currentCanvasState = editorProvider.elementProperties;
217 228
 
218 229
     return Positioned(
219 230
       top: element.position.top,
220 231
       left: element.position.left,
221
-      child: GestureDetector(
222
-        onDoubleTap: () {
223
-          print('double tap detected');
224
-          Provider.of<Editor>(context, listen: false).selectElmById(element.id);
225
-          Provider.of<Editor>(context, listen: false).enableEdit();
226
-        },
227
-        onTap: () {
228
-          print('Element Gesture Detector Tapped!');
229
-          Provider.of<Editor>(context, listen: false).selectElmById(element.id);
230
-        },
231
-        onPanStart: (details) {
232
-          log('Pan Start');
233
-          inspect(editorProvider.elementProperties);
234
-          editorProvider.addUndoEntry(CanvasHistoryModifyType.move, editorProvider.elementProperties);
235
-        },
236
-        onPanUpdate: (details) {
237
-          if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
238
-            return;
239
-          }
240
-
241
-          if (element.isLocked) return;
242
-    
243
-          final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
244
-    
245
-          if (elmKeyContext == null) {
246
-            debugPrint('WARNING, elmKeyContext not found');
247
-          }
248
-    
249
-          bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
250
-    
251
-          double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
252
-          double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
253
-    
254
-          double right = element.position.left + width;
255
-          double bottom = element.position.top + height;
256
-          
257
-    
258
-          bool isElmWidthExceedCanvas = width > canvas.width; 
259
-          bool isElmHeightExceedCanvas = height > canvas.height;
260
-      
261
-          // Check if the object is out of the canvas
262
-          if (element.position.top < 0) {
263
-            setState(() {
264
-              element.position.top = 0;
265
-            });
266
-      
267
-            print('object is out of canvas');
268
-            return;
269
-          }
270
-      
271
-          if (element.position.left < 0) {
272
-            setState(() {
273
-              element.position.left = 0;
274
-            });
275
-      
276
-            print('object is out of canvas');
277
-            return;
278
-          }
279
-    
280
-          if (!isElmHeightExceedCanvas && bottom > canvas.height) {
281
-            setState(() {
282
-              element.position.top = (canvas.height - height).roundToDouble() - 1;
283
-            });
284
-      
285
-            print('object is out of canvas');
286
-            return;
287
-          }
288
-      
289
-      
290
-          if (!isElmWidthExceedCanvas && right > canvas.width) {
291
-            setState(() {
292
-              element.position.left = (canvas.width - width).roundToDouble() - 1;
293
-            });
294
-      
295
-            print('object is out of canvas');
296
-            return;
297
-          }
298
-      
299
-          Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
300
-        },
301
-        onPanEnd: (details) {
302
-          // ? Adjust overflow position
303
-          ElementPosition adjustedElementPosition = element.position;
304
-          final elmKeyContext = element.elementKey.currentContext;
305
-
306
-          bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
307
-    
308
-          double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
309
-          double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
310
-    
311
-          double right = element.position.left + width;
312
-          double bottom = element.position.top + height;
313
-
314
-
315
-          if (element.position.top < 0) {
316
-              adjustedElementPosition.top = 0;
317
-          }
318
-
319
-          if (element.position.left < 0) {
320
-              adjustedElementPosition.left = 0;
321
-          }
322
-
323
-          if ((element.position.top + height) > canvas.height) {
324
-              adjustedElementPosition.top = (canvas.height - height).roundToDouble() - 1;
325
-          }
326
-
327
-          if ((element.position.left + width) > canvas.width) {
328
-              adjustedElementPosition.left = (canvas.width - width).roundToDouble() - 1;
232
+      child: IgnorePointer(
233
+        ignoring: editorProvider.shouldIgnoreTouch(element.id),
234
+        child: GestureDetector(
235
+          onDoubleTap: () {
236
+            print('double tap detected');
237
+            Provider.of<Editor>(context, listen: false).selectElmById(element.id);
238
+            Provider.of<Editor>(context, listen: false).enableEdit();
239
+          },
240
+          onTap: () {
241
+            print('Element Gesture Detector Tapped!');
242
+            Provider.of<Editor>(context, listen: false).selectElmById(element.id);
243
+          },
244
+          onPanStart: (details) {
245
+            if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
246
+              return;
329 247
             }
330
-
331
-
332
-          Provider.of<Editor>(context, listen: false).updateElmPosition(
333
-            Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top),
334
-            // Provider.of<Editor>(context, listen: false).elementProperties
335
-          );
336
-        },
337
-        child: Stack(
338
-          clipBehavior: Clip.none,
339
-          children: [
340
-            RotatedBox(
341
-              // angle: element.quarterTurns * (math.pi / 2),
342
-              quarterTurns: element.quarterTurns,
343
-              child: Stack(
344
-                clipBehavior: Clip.none,
345
-                children: [
346
-                  Container(
347
-                    width: element.type == ElementType.text 
348
-                      ? null 
248
+        
249
+            if (element.isLocked) return;
250
+        
251
+            log('Pan Start');
252
+            // inspect(editorProvider.elementProperties);
253
+            // editorProvider.setNewElementsState(CanvasHistoryModifyType.move, editorProvider.elementProperties);
254
+        
255
+            _elementDragStartPosition = Provider.of<Editor>(context, listen: false).getClonedElementPosition(element);
256
+          },
257
+          onPanUpdate: (details) {
258
+            if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
259
+              return;
260
+            }
261
+        
262
+            if (element.isLocked) return;
349 263
             
350
-                      : element.type == ElementType.qr 
351
-                      ? CanvasStyle.getQrSize(element.qrScale!)
264
+            final elmKeyContext = Provider.of<Editor>(context, listen: false).selectedElmKey?.currentContext;
352 265
             
353
-                      : element.width ,
354
-                    height: element.type == ElementType.qr 
355
-                      ? CanvasStyle.getQrSize(element.qrScale!)
356
-                      : null,
357
-                    // child: Text('Top: ${element.position.top}, Left: ${element.position.left}, isSelected: ${Provider.of<Editor>(context, listen: false).isSelected(element.id)}'),
358
-                    key: widget.globalKey,
359
-                    decoration: BoxDecoration(
360
-                      // color: element.color ?? Colors.blue,
361
-                      border: Provider.of<Editor>(context, listen: true).isSelected(element.id) ? Border.all(
362
-                        color:Colors.red,
363
-                        width: 2,
364
-                        strokeAlign: BorderSide.strokeAlignOutside,
365
-                      ) : null,
366
-                    ),
367
-                    // ? child element
368
-                    child: _buildChildElement(element),
369
-                  ),
266
+            if (elmKeyContext == null) {
267
+              debugPrint('WARNING, elmKeyContext not found');
268
+            }
370 269
             
371
-                  // ? Textbox Resizer
372
-                  if (editorProvider.shouldShowTextboxResizer(element.id)) Positioned(
373
-                    right: _resizerWidth / -2,
374
-                    top:_height / 2 - (_resizerHeight / 2),
375
-                    child: DeferPointer(
376
-                      link: editorProvider.textboxResizerDeferredPointerHandlerLink,
377
-                      // paintOnTop: true,
378
-                      child: Transform.scale(
379
-                        scale: 1 / _currentScale,
380
-                        child: Listener(
381
-                          onPointerDown: (details) {
382
-                            _dragStartPosition = details.position;
383
-                          },
384
-                          onPointerMove: (details) {
385
-                            if (element.isLocked) return;
386
-                        
387
-                            setState(() {
388
-                              final selectedElm = Provider.of<Editor>(context, listen: false).selectedElm;
389
-                        
390
-                              if (selectedElm == null) return;
391
-                        
392
-                        
393
-                              final elmKeyContext = selectedElm.elementKey.currentContext;
394
-                        
395
-                              double width = elmKeyContext!.size!.width;
396
-                        
397
-                              print(MediaQuery.of(context).devicePixelRatio);
398
-                                    
399
-                                    
400
-                              var delta;
401
-                              double canvasWidth = 1.0;
402
-                                    
403
-                              print (_dragStartPosition!.dx);
404
-                              print (_dragStartPosition!.dy);
405
-                                    
406
-                              // adjust width based on rotation
407
-                              print('quarter turn: ${element.quarterTurns}');
408
-                              switch(element.quarterTurns) {
409
-                                case 0:
410
-                                  delta = details.position.dx - _dragStartPosition!.dx;
411
-                                  canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.width;
412
-                                case 3:
413
-                                  delta = _dragStartPosition!.dy - details.position.dy;
414
-                                  element.position.top -= delta / _currentScale;
415
-                                  canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.height;
416
-                              }
417
-                              
418
-                              element.width += delta / _currentScale; // Adjust width
419
-                              print('current scale $_currentScale');
420
-                        
421
-                              // Enforce minimum size
422
-                              element.width = element.width.clamp(75.0, canvasWidth);
423
-                               
424
-                              _dragStartPosition = details.position;
425
-                              if (width <= 0) return;
426
-                        
427
-                              Provider.of<Editor>(context, listen: false).updateElmWitdh(element.width);
428
-                            });
429
-                          },
430
-                          onPointerUp: (details) {
431
-                            _dragStartPosition = null;
432
-                          },
433
-                          child: Container(
434
-                            decoration: BoxDecoration(
435
-                              shape: BoxShape.circle,
436
-                              color: Colors.blue.withOpacity(0.7),
437
-                            ),
438
-                            width: _resizerWidth,
439
-                            height: _resizerHeight,
440
-                            child: const RotatedBox(
441
-                              quarterTurns: 1,
442
-                              child: Icon(
443
-                                Icons.height,
444
-                                size: 24,
445
-                                color: Colors.white,
270
+            bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
271
+            
272
+            double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
273
+            double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
274
+            
275
+            double right = element.position.left + width;
276
+            double bottom = element.position.top + height;
277
+            
278
+            
279
+            bool isElmWidthExceedCanvas = width > canvas.width; 
280
+            bool isElmHeightExceedCanvas = height > canvas.height;
281
+        
282
+            // Check if the object is out of the canvas
283
+            if (element.position.top < 0) {
284
+              setState(() {
285
+                element.position.top = 0;
286
+              });
287
+        
288
+              print('object is out of canvas');
289
+              return;
290
+            }
291
+        
292
+            if (element.position.left < 0) {
293
+              setState(() {
294
+                element.position.left = 0;
295
+              });
296
+        
297
+              print('object is out of canvas');
298
+              return;
299
+            }
300
+            
301
+            if (!isElmHeightExceedCanvas && bottom > canvas.height) {
302
+              setState(() {
303
+                element.position.top = (canvas.height - height).roundToDouble() - 1;
304
+              });
305
+        
306
+              print('object is out of canvas');
307
+              return;
308
+            }
309
+        
310
+        
311
+            if (!isElmWidthExceedCanvas && right > canvas.width) {
312
+              setState(() {
313
+                element.position.left = (canvas.width - width).roundToDouble() - 1;
314
+              });
315
+        
316
+              print('object is out of canvas');
317
+              return;
318
+            }
319
+        
320
+            Provider.of<Editor>(context, listen: false).updateElmPosition(details.delta);
321
+          },
322
+          onPanEnd: (details) {
323
+            if (!Provider.of<Editor>(context, listen: false).isSelected(element.id)) {
324
+              return;
325
+            }
326
+        
327
+            if (element.isLocked) return;
328
+            
329
+        
330
+            // ? Adjust overflow position
331
+            ElementPosition adjustedElementPosition = element.position;
332
+            final elmKeyContext = element.elementKey.currentContext;
333
+        
334
+            bool isElmRotatedVertically = [1,3].contains(element.quarterTurns);
335
+            
336
+            double width = isElmRotatedVertically ? elmKeyContext!.size!.height : elmKeyContext!.size!.width;
337
+            double height = isElmRotatedVertically ? elmKeyContext.size!.width : elmKeyContext.size!.height;
338
+            
339
+            double right = element.position.left + width;
340
+            double bottom = element.position.top + height;
341
+        
342
+        
343
+            if (element.position.top < 0) {
344
+                adjustedElementPosition.top = 0;
345
+            }
346
+        
347
+            if (element.position.left < 0) {
348
+                adjustedElementPosition.left = 0;
349
+            }
350
+        
351
+            if ((element.position.top + height) > canvas.height) {
352
+                adjustedElementPosition.top = (canvas.height - height).roundToDouble() - 1;
353
+            }
354
+        
355
+            if ((element.position.left + width) > canvas.width) {
356
+                adjustedElementPosition.left = (canvas.width - width).roundToDouble() - 1;
357
+              }
358
+        
359
+        
360
+            // Provider.of<Editor>(context, listen: false).updateElmPosition(
361
+            //   Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top),
362
+            //   // Provider.of<Editor>(context, listen: false).elementProperties
363
+            // );
364
+        
365
+            Provider.of<Editor>(context, listen: false).resetElmPosition(_elementDragStartPosition);
366
+        
367
+            Provider.of<Editor>(context, listen: false).moveElement(
368
+              Offset(adjustedElementPosition.left - element.position.left, adjustedElementPosition.top - element.position.top)
369
+            );
370
+          },
371
+          child: Stack(
372
+            clipBehavior: Clip.none,
373
+            children: [
374
+              RotatedBox(
375
+                // angle: element.quarterTurns * (math.pi / 2),
376
+                quarterTurns: element.quarterTurns,
377
+                child: Stack(
378
+                  clipBehavior: Clip.none,
379
+                  children: [
380
+                    Container(
381
+                      width: element.type == ElementType.text 
382
+                        ? null 
383
+              
384
+                        : element.type == ElementType.qr 
385
+                        ? CanvasStyle.getQrSize(element.qrScale!)
386
+              
387
+                        : element.width ,
388
+                      height: element.type == ElementType.qr 
389
+                        ? CanvasStyle.getQrSize(element.qrScale!)
390
+                        : null,
391
+                      // child: Text('Top: ${element.position.top}, Left: ${element.position.left}, isSelected: ${Provider.of<Editor>(context, listen: false).isSelected(element.id)}'),
392
+                      key: widget.globalKey,
393
+                      decoration: BoxDecoration(
394
+                        // color: element.color ?? Colors.blue,
395
+                        border: Provider.of<Editor>(context, listen: true).isSelected(element.id) ? Border.all(
396
+                          color:Colors.red,
397
+                          width: 2,
398
+                          strokeAlign: BorderSide.strokeAlignOutside,
399
+                        ) : null,
400
+                      ),
401
+                      // ? child element
402
+                      child: _buildChildElement(element),
403
+                    ),
404
+              
405
+                    // ? Textbox Resizer
406
+                    if (editorProvider.shouldShowTextboxResizer(element.id)) Positioned(
407
+                      right: _resizerWidth / -2,
408
+                      top:_height / 2 - (_resizerHeight / 2),
409
+                      child: DeferPointer(
410
+                        link: editorProvider.textboxResizerDeferredPointerHandlerLink,
411
+                        // paintOnTop: true,
412
+                        child: Transform.scale(
413
+                          scale: 1 / _currentScale,
414
+                          child: Listener(
415
+                            onPointerDown: (details) {
416
+                              _textboxDragStartPosition = details.position;
417
+                              _textboxDragStartWidth = element.width;
418
+                            },
419
+                            onPointerMove: (details) {
420
+                              if (element.isLocked) return;
421
+                          
422
+                              setState(() {
423
+                                final selectedElm = Provider.of<Editor>(context, listen: false).selectedElm;
424
+                          
425
+                                if (selectedElm == null) return;
426
+                          
427
+                          
428
+                                final elmKeyContext = selectedElm.elementKey.currentContext;
429
+                          
430
+                                double width = elmKeyContext!.size!.width;
431
+                          
432
+                                print(MediaQuery.of(context).devicePixelRatio);
433
+                                      
434
+                                      
435
+                                var delta;
436
+                                double canvasWidth = 1.0;
437
+                                      
438
+                                print (_textboxDragStartPosition!.dx);
439
+                                print (_textboxDragStartPosition!.dy);
440
+                                      
441
+                                // adjust width based on rotation
442
+                                print('quarter turn: ${element.quarterTurns}');
443
+                                switch(element.quarterTurns) {
444
+                                  case 0:
445
+                                    delta = details.position.dx - _textboxDragStartPosition!.dx;
446
+                                    canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.width;
447
+                                  case 3:
448
+                                    delta = _textboxDragStartPosition!.dy - details.position.dy;
449
+                                    element.position.top -= delta / _currentScale;
450
+                                    canvasWidth = Provider.of<Editor>(context, listen: false).canvasProperty.height;
451
+                                }
452
+                                
453
+                                element.width += delta / _currentScale; // Adjust width
454
+                                print('current scale $_currentScale');
455
+                          
456
+                                // Enforce minimum size
457
+                                element.width = element.width.clamp(75.0, canvasWidth);
458
+                                 
459
+                                _textboxDragStartPosition = details.position;
460
+                                if (width <= 0) return;
461
+                          
462
+                                Provider.of<Editor>(context, listen: false).updateElmWitdh(element.width);
463
+                              });
464
+                            },
465
+                            onPointerUp: (details) {
466
+                              editorProvider.commitTextboxResize(element.width, _textboxDragStartWidth ?? 70);
467
+        
468
+                              _textboxDragStartPosition = null;
469
+                              _textboxDragStartWidth = null;
470
+                            },
471
+                            child: Container(
472
+                              decoration: BoxDecoration(
473
+                                shape: BoxShape.circle,
474
+                                color: Colors.blue.withOpacity(0.7),
475
+                              ),
476
+                              width: _resizerWidth,
477
+                              height: _resizerHeight,
478
+                              child: const RotatedBox(
479
+                                quarterTurns: 1,
480
+                                child: Icon(
481
+                                  Icons.height,
482
+                                  size: 24,
483
+                                  color: Colors.white,
484
+                                ),
446 485
                               ),
447 486
                             ),
448 487
                           ),
449 488
                         ),
450 489
                       ),
451 490
                     ),
452
-                  ),
453
-            
454
-            
455
-                  
456
-            
457
-            
458
-            
459
-                  // if (Provider.of<Editor>(context).selectedElmId == element.id && element.type != ElementType.qr) ... [
460
-                    
491
+              
492
+              
461 493
                     
462
-                  // ]
463
-                ],
494
+              
495
+              
496
+              
497
+                    // if (Provider.of<Editor>(context).selectedElmId == element.id && element.type != ElementType.qr) ... [
498
+                      
499
+                      
500
+                    // ]
501
+                  ],
502
+                ),
464 503
               ),
465
-            ),
466
-
467
-            //? Overlay Button
468
-            if (editorProvider.shouldShowOverlay(element.id)) Positioned(
469
-              top: -60 / _currentScale,
470
-              left: 0,
471
-              child: DeferPointer(
472
-                paintOnTop: true,
473
-                child: Transform.scale(
474
-                  scale: 1 / _currentScale,
475
-                  alignment: Alignment.topLeft,
476
-                  child: Row(
477
-                    children: [
478
-                      IconButton.filled(
479
-                        onPressed: () {
480
-                          print('delete overlay tapped');
481
-                          editorProvider.deleteElement(context);
482
-                        }, 
483
-                        icon: const Icon(Icons.delete),
484
-                        color: Theme.of(context).colorScheme.error,
485
-                        style: ButtonStyle(
486
-                          backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
504
+        
505
+              //? Overlay Button
506
+              if (editorProvider.shouldShowOverlay(element.id)) Positioned(
507
+                top: -60 / _currentScale,
508
+                left: 0,
509
+                child: DeferPointer(
510
+                  paintOnTop: true,
511
+                  child: Transform.scale(
512
+                    scale: 1 / _currentScale,
513
+                    alignment: Alignment.topLeft,
514
+                    child: Row(
515
+                      children: [
516
+                        IconButton.filled(
517
+                          onPressed: () {
518
+                            print('delete overlay tapped');
519
+                            editorProvider.deleteElement(context);
520
+                          }, 
521
+                          icon: const Icon(Icons.delete),
522
+                          color: Theme.of(context).colorScheme.error,
523
+                          style: ButtonStyle(
524
+                            backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
525
+                          ),
487 526
                         ),
488
-                      ),
489
-                      IconButton.filled(
490
-                        onPressed: () {
491
-                          print('rotate overlay tapped');
492
-                          editorProvider.rotate();
493
-                  
494
-                          // test
495
-                          var getBox = element.elementKey.currentContext!.findRenderObject();
496
-                        }, 
497
-                        icon: const Icon(Icons.rotate_90_degrees_cw),
498
-                        // color: Theme.of(context).colorScheme.error,
499
-                        style: const ButtonStyle(
500
-                          // backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
527
+                        IconButton.filled(
528
+                          onPressed: () {
529
+                            print('rotate overlay tapped');
530
+                            editorProvider.rotate();
531
+                    
532
+                            // test
533
+                            var getBox = element.elementKey.currentContext!.findRenderObject();
534
+                          }, 
535
+                          icon: const Icon(Icons.rotate_90_degrees_cw),
536
+                          // color: Theme.of(context).colorScheme.error,
537
+                          style: const ButtonStyle(
538
+                            // backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer)
539
+                          ),
501 540
                         ),
502
-                      ),
503
-                      IconButton.filled(
504
-                        onPressed: () {
505
-                          print('lock overlay tapped');
506
-                          editorProvider.toggleLockElement();
507
-                        }, 
508
-                        icon: Icon(element.isLocked ? Icons.lock_outline : Icons.lock_open),
509
-                        // color: element.isLocked ? Theme.of(context).colorScheme.error,
510
-                        style: ButtonStyle(
511
-                          backgroundColor: element.isLocked ? WidgetStatePropertyAll(Theme.of(context).colorScheme.error) : null
541
+                        IconButton.filled(
542
+                          onPressed: () {
543
+                            print('lock overlay tapped');
544
+                            editorProvider.toggleLockElement();
545
+                          }, 
546
+                          icon: Icon(element.isLocked ? Icons.lock_outline : Icons.lock_open),
547
+                          // color: element.isLocked ? Theme.of(context).colorScheme.error,
548
+                          style: ButtonStyle(
549
+                            backgroundColor: element.isLocked ? WidgetStatePropertyAll(Theme.of(context).colorScheme.error) : null
550
+                          ),
512 551
                         ),
513
-                      ),
514
-                    ],
552
+                      ],
553
+                    ),
515 554
                   ),
516
-                ),
555
+                )
517 556
               )
518
-            )
519
-          ],
557
+            ],
558
+          ),
520 559
         ),
521 560
       ),
522 561
     );
523 562
   }
524 563
 
525 564
 
526
-  Widget _buildChildElement(ElementProperty element) {
565
+  Widget _buildChildElement(ElementState element) {
527 566
     // ? build QR element
528 567
     if (element.type == ElementType.qr) {
529 568
       return const Image(image: AssetImage('asset/images/qr_template.png'));
@@ -533,8 +572,7 @@ class _ElementWidgetState extends State<ElementWidget> {
533 572
     if (Provider.of<Editor>(context, listen: true).isEditing && (Provider.of<Editor>(context, listen: true).selectedElmId == element.id)) {
534 573
       return IntrinsicWidth(
535 574
         child: TextField(
536
-          focusNode: _focus,
537
-          controller: Provider.of<Editor>(context).selectedElm!.valueController,
575
+          controller: element.valueController,
538 576
           autofocus: true,
539 577
           keyboardType: TextInputType.multiline,
540 578
           enableSuggestions: false,
@@ -548,17 +586,14 @@ class _ElementWidgetState extends State<ElementWidget> {
548 586
           ),
549 587
           onChanged: (newText) {
550 588
 
551
-            if (widget.elementProperty.lastSavedText != null && newText != widget.elementProperty.lastSavedText) {
552
-              EasyDebounce.debounce(
553
-                'text-debounce${element.id}', 
554
-                const Duration(milliseconds: 500), 
555
-                () {
556
-                  Provider.of<Editor>(context, listen: false).addUndoEntry(CanvasHistoryModifyType.textEdit, Provider.of<Editor>(context, listen: false).elementProperties);
557
-                  widget.elementProperty.lastSavedText = newText;
558
-                }
559
-              );
589
+            Debouncer.run(() {
590
+              log('[SAVING TO HISTORY]');
591
+              Provider.of<Editor>(context, listen: false).editText(element.valueController);
592
+              element.valueController.text = Provider.of<Editor>(context, listen: false).valueOnStartEditing ?? '';
593
+
594
+              Provider.of<Editor>(context, listen: false).setValueOnStartEditing(newText);
595
+            });
560 596
 
561
-            }
562 597
 
563 598
             setState(() {});
564 599
           },

+ 141 - 111
lib/widgets/toolbar.dart

@@ -1,8 +1,11 @@
1
+import 'dart:developer';
2
+
1 3
 import 'package:easy_debounce/easy_debounce.dart';
2 4
 import 'package:flutter/material.dart';
3 5
 import 'package:flutter_canvas_editor/history.dart';
4 6
 import 'package:flutter_canvas_editor/providers/editor.dart';
5 7
 import 'package:flutter_canvas_editor/style/canvas_style.dart';
8
+import 'package:flutter_canvas_editor/utils/debouncer.dart';
6 9
 import 'package:flutter_canvas_editor/widgets/elements.dart';
7 10
 import 'package:provider/provider.dart';
8 11
 
@@ -16,26 +19,139 @@ class ToolbarWidget extends StatefulWidget {
16 19
 }
17 20
 
18 21
 class _ToolbarWidgetState extends State<ToolbarWidget> {
19
-  List<DropdownMenuItem<int>> fontSizeDropdownItems= [];
20
-
21
-  List<DropdownMenuItem<int>> qrSizeDropdownItems= [];
22
+  
22 23
 
23 24
   FocusNode? _focus = FocusNode();
24 25
 
25 26
   // String? _lastSavedText;
26 27
 
27 28
 
29
+  
30
+
31
+  // void _addFocusNodeListener() {
32
+  //   if (Provider.of<Editor>(context, listen: false).selectedElmKey != null && [ElementType.text, ElementType.textbox].contains(Provider.of<Editor>(context, listen: false).selectedElm!.type)) {
33
+  //     _focus!.addListener(_onTextFieldFocusChange);
34
+  //   }
35
+  // }
36
+
37
+  // void _onTextFieldFocusChange() {
38
+  //   print('toolbar onfocus change');
39
+
40
+  //   if (Provider.of<Editor>(context, listen: false).selectedElm?.lastSavedText != Provider.of<Editor>(context, listen: false).selectedElm?.valueController.text) {
41
+  //     // Provider.of<Editor>(context, listen: false).setNewElementsState(CanvasHistoryModifyType.textEdit, Provider.of<Editor>(context, listen: false).elementProperties);
42
+  //     Provider.of<Editor>(context, listen: false).selectedElm?.lastSavedText = Provider.of<Editor>(context, listen: false).selectedElm?.valueController.text;
43
+  //   }
44
+  // }
45
+
46
+  // void _removeFocusNodeListener() {
47
+  //   if ([ElementType.text, ElementType.textbox].contains(Provider.of<Editor>(context).selectedElm!.type)) {
48
+  //     _focus!.removeListener(_onTextFieldFocusChange);
49
+  //   }
50
+  // }
51
+
52
+
53
+  @override
54
+  void dispose() {
55
+    // TODO: implement dispose
56
+
57
+    // _removeFocusNodeListener();
58
+    super.dispose();
59
+  }
60
+
61
+
62
+  @override
63
+  Widget build(BuildContext context) {
64
+    final editorProvider = Provider.of<Editor>(context);
65
+
66
+    return Container(
67
+      padding: EdgeInsets.symmetric(horizontal: 16),
68
+      color: Colors.white,
69
+      width: MediaQuery.of(context).size.width,
70
+      height: 150,
71
+      child: Provider.of<Editor>(context).insertElementMode ? insertElementSection() : ToolbarPropertiesWidget(editorProvider: editorProvider),
72
+      // child: ListView(
73
+      //   children: Provider.of<Editor>(context).insertElementMode ? insertElementSection() : elementPropertiesSection(editorProvider),
74
+      // ),
75
+    );
76
+  }
77
+
78
+  Widget insertElementSection() {
79
+    return ListView(
80
+      children: [
81
+        Text('Insert Element'),
82
+        SizedBox(height: 20),
83
+        ElevatedButton(
84
+          onPressed: Provider.of<Editor>(context, listen: false).addTextElement, 
85
+          child: Text('Add Text Element')
86
+        ),
87
+        ElevatedButton(
88
+          onPressed: Provider.of<Editor>(context, listen: false).addTextboxElement, 
89
+          child: Text('Add Textbox Element')
90
+        ),
91
+        SizedBox(height: 24),
92
+
93
+
94
+        // ? Variable Element Section
95
+        Text('Insert Variable Element'),
96
+        SizedBox(height: 24),
97
+
98
+
99
+        ElevatedButton(
100
+          onPressed: Provider.of<Editor>(context, listen: false).addProductNameElement, 
101
+          child: Text('Add Product Name Element')
102
+        ),
103
+        ElevatedButton(
104
+          onPressed: Provider.of<Editor>(context, listen: false).addVariantNameElement, 
105
+          child: Text('Add Variant Name Element')
106
+        ),
107
+        ElevatedButton(
108
+          onPressed: Provider.of<Editor>(context, listen: false).addProductionCodeElement, 
109
+          child: Text('Add Production Code Element')
110
+        ),
111
+        ElevatedButton(
112
+          onPressed: Provider.of<Editor>(context, listen: false).addProductionDateElement, 
113
+          child: Text('Add Production Date Element')
114
+        ),
115
+        ElevatedButton(
116
+          onPressed: Provider.of<Editor>(context, listen: false).addSerialNumberElement, 
117
+          child: Text('Add Serial Number Element')
118
+        ),
119
+      ]
120
+    );
121
+  }
122
+
123
+  
124
+}
125
+
126
+
127
+
128
+class ToolbarPropertiesWidget extends StatefulWidget {
129
+  final Editor editorProvider;
130
+
131
+  const ToolbarPropertiesWidget({super.key, required this.editorProvider});
132
+
133
+  @override
134
+  State<ToolbarPropertiesWidget> createState() => _ToolbarPropertiesWidgetState();
135
+}
136
+
137
+class _ToolbarPropertiesWidgetState extends State<ToolbarPropertiesWidget> {
138
+  List<DropdownMenuItem<int>> fontSizeDropdownItems= [];
139
+
140
+  List<DropdownMenuItem<int>> qrSizeDropdownItems= [];
141
+
142
+  // String? _valueOnStartEditing;
143
+
28 144
   @override
29 145
   void initState() {
30
-    // TODO: implement initState
31 146
     super.initState();
32 147
 
148
+    // _valueOnStartEditing = widget.editorProvider.selectedElm?.valueController.text; 
149
+
33 150
     populateFontSizeDropdownItems();
34 151
     populateQrSizeDropdownItems();
35 152
 
36
-    WidgetsBinding.instance.addPostFrameCallback((_) {
37
-      _addFocusNodeListener();
38
-    });
153
+    log('[TOOLBAR PROPERTIES EXECUTED]');
154
+
39 155
   }
40 156
 
41 157
   //functions
@@ -63,95 +179,14 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
63 179
     });
64 180
   }
65 181
 
66
-  void _addFocusNodeListener() {
67
-    if (Provider.of<Editor>(context, listen: false).selectedElmKey != null && [ElementType.text, ElementType.textbox].contains(Provider.of<Editor>(context, listen: false).selectedElm!.type)) {
68
-      _focus!.addListener(_onTextFieldFocusChange);
69
-    }
70
-  }
71
-
72
-  void _onTextFieldFocusChange() {
73
-    print('toolbar onfocus change');
74
-
75
-    if (Provider.of<Editor>(context, listen: false).selectedElm?.lastSavedText != Provider.of<Editor>(context, listen: false).selectedElm?.valueController.text) {
76
-      Provider.of<Editor>(context, listen: false).addUndoEntry(CanvasHistoryModifyType.textEdit, Provider.of<Editor>(context, listen: false).elementProperties);
77
-      Provider.of<Editor>(context, listen: false).selectedElm?.lastSavedText = Provider.of<Editor>(context, listen: false).selectedElm?.valueController.text;
78
-    }
79
-  }
80
-
81
-  void _removeFocusNodeListener() {
82
-    if ([ElementType.text, ElementType.textbox].contains(Provider.of<Editor>(context).selectedElm!.type)) {
83
-      _focus!.removeListener(_onTextFieldFocusChange);
84
-    }
85
-  }
86
-
87
-
88
-  @override
89
-  void dispose() {
90
-    // TODO: implement dispose
91
-
92
-    _removeFocusNodeListener();
93
-    super.dispose();
94
-  }
95
-
96 182
 
97 183
   @override
98 184
   Widget build(BuildContext context) {
99
-    final editorProvider = Provider.of<Editor>(context);
100
-
101
-    return Container(
102
-      padding: EdgeInsets.symmetric(horizontal: 16),
103
-      color: Colors.white,
104
-      width: MediaQuery.of(context).size.width,
105
-      height: 150,
106
-      child: ListView(
107
-        children: Provider.of<Editor>(context).insertElementMode ? insertElementSection() : elementPropertiesSection(editorProvider),
108
-      ),
185
+    return ListView(
186
+      children: elementPropertiesSection(widget.editorProvider),
109 187
     );
110 188
   }
111 189
 
112
-  List<Widget> insertElementSection() {
113
-    return [
114
-      Text('Insert Element'),
115
-      SizedBox(height: 20),
116
-      ElevatedButton(
117
-        onPressed: Provider.of<Editor>(context, listen: false).addTextElement, 
118
-        child: Text('Add Text Element')
119
-      ),
120
-      ElevatedButton(
121
-        onPressed: Provider.of<Editor>(context, listen: false).addTextboxElement, 
122
-        child: Text('Add Textbox Element')
123
-      ),
124
-      SizedBox(height: 24),
125
-
126
-
127
-      // ? Variable Element Section
128
-      Text('Insert Variable Element'),
129
-      SizedBox(height: 24),
130
-
131
-
132
-      ElevatedButton(
133
-        onPressed: Provider.of<Editor>(context, listen: false).addProductNameElement, 
134
-        child: Text('Add Product Name Element')
135
-      ),
136
-      ElevatedButton(
137
-        onPressed: Provider.of<Editor>(context, listen: false).addVariantNameElement, 
138
-        child: Text('Add Variant Name Element')
139
-      ),
140
-      ElevatedButton(
141
-        onPressed: Provider.of<Editor>(context, listen: false).addProductionCodeElement, 
142
-        child: Text('Add Production Code Element')
143
-      ),
144
-      ElevatedButton(
145
-        onPressed: Provider.of<Editor>(context, listen: false).addProductionDateElement, 
146
-        child: Text('Add Production Date Element')
147
-      ),
148
-      ElevatedButton(
149
-        onPressed: Provider.of<Editor>(context, listen: false).addSerialNumberElement, 
150
-        child: Text('Add Serial Number Element')
151
-      ),
152
-    ];
153
-  }
154
-
155 190
   List<Widget> elementPropertiesSection(Editor editorProvider) {
156 191
     final element = Provider.of<Editor>(context).selectedElm;
157 192
 
@@ -171,7 +206,7 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
171 206
   }
172 207
 
173 208
 
174
-  List<Widget> _variablePropertiesSection(Editor editorProvider, ElementProperty? element) {
209
+  List<Widget> _variablePropertiesSection(Editor editorProvider, ElementState? element) {
175 210
     return [
176 211
       Container(
177 212
         margin: EdgeInsets.only(bottom: 16),
@@ -192,34 +227,29 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
192 227
     ];
193 228
   }
194 229
 
195
-  List<Widget> _commonPropertiesSection(Editor editorProvider, ElementProperty? element) {
230
+  List<Widget> _commonPropertiesSection(Editor editorProvider, ElementState? element) {
231
+
196 232
     return [
197 233
       // ? Value Editor
198 234
       if([ElementType.text, ElementType.textbox].contains(editorProvider.selectedElm!.type)) TextField(
199
-        focusNode: _focus,
200 235
         readOnly: element!.isLocked, 
201 236
         controller: element!.valueController,
202 237
         onTap: editorProvider.enableEdit,
203
-        onEditingComplete: () {
204
-          FocusManager.instance.primaryFocus?.unfocus();
238
+        // onEditingComplete: () {
239
+        //   FocusManager.instance.primaryFocus?.unfocus();
205 240
 
206
-          editorProvider.disableEdit();
241
+        //   editorProvider.disableEdit();
207 242
 
208
-          print('kepenjet');
209
-        },
243
+        //   print('kepenjet');
244
+        // },
210 245
         onChanged: (newText) {
211
-          if (element.lastSavedText != null && newText != element.lastSavedText) {
212
-            EasyDebounce.debounce(
213
-              'text-debounce-toolbar${element.id}',
214
-              const Duration(milliseconds: 500),
215
-              () {
216
-                editorProvider.addUndoEntry(CanvasHistoryModifyType.textEdit, editorProvider.elementProperties);
217
-                element.lastSavedText = newText;
218
-              },
219
-              
220
-            );
246
+          Debouncer.run(() {
247
+            log('[SAVING TO HISTORY]');
248
+            Provider.of<Editor>(context, listen: false).editText(element.valueController);
249
+            element.valueController.text = widget.editorProvider.valueOnStartEditing ?? '';
221 250
 
222
-          }
251
+            widget.editorProvider.setValueOnStartEditing(newText);
252
+          });
223 253
 
224 254
 
225 255
           setState(() {});
@@ -243,7 +273,7 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
243 273
   }
244 274
 
245 275
 
246
-  Widget _buildFontResizerWidget(ElementProperty? element) {
276
+  Widget _buildFontResizerWidget(ElementState? element) {
247 277
     return Row(
248 278
       children: [
249 279
         DropdownButton<int>(
@@ -266,7 +296,7 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
266 296
     );
267 297
   }
268 298
 
269
-  Widget _buildQrResizerWidget(ElementProperty? element) {
299
+  Widget _buildQrResizerWidget(ElementState? element) {
270 300
     return  Row(
271 301
       children: [
272 302
         DropdownButton<int>(