Browse Source

feat: variable elements

Raihan Rizal 4 months ago
parent
commit
8855d17bfe
7 changed files with 320 additions and 113 deletions
  1. 33 56
      lib/main.dart
  2. 117 0
      lib/providers/editor.dart
  3. 3 2
      lib/style/canvas_style.dart
  4. 78 27
      lib/widgets/elements.dart
  5. 80 28
      lib/widgets/toolbar.dart
  6. 8 0
      pubspec.lock
  7. 1 0
      pubspec.yaml

+ 33 - 56
lib/main.dart

@@ -112,55 +112,32 @@ class _HomePageState extends State<HomePage> {
112 112
   Widget build(BuildContext context) {
113 113
     return DeferredPointerHandler(
114 114
       link: Provider.of<Editor>(context).textboxResizerDeferredPointerHandlerLink,
115
-      child: GestureDetector(
116
-        onDoubleTap: () {
117
-          // print((_transformationController.value) == Matrix4.identity());
118
-          // print(_transformationController.value.getMaxScaleOnAxis());
119
-          
120
-
121
-        },
122
-        onTap: () {
123
-          print('Main Gesture Detector Tapped!');
124
-          setState(() {
125
-            Provider.of<Editor>(context, listen: false).unSelectElm();
126
-          });
127
-        },
128
-      
129
-        // onScaleStart: (details) {
130
-        //   print('Scale start $details');
131
-        //   _previousScale = _scale;
132
-        //   _previousPosition = details.focalPoint;
133
-        // },
134
-        // onScaleUpdate: (details) {
135
-        //   print('Scale update $details');
136
-        //   setState(() {
137
-        //     _scale = _previousScale * details.scale;
138
-        //     _position += details.focalPoint - _previousPosition;
139
-        //     _previousPosition = details.focalPoint;
140
-        //   });
141
-        // },
142
-        // onScaleEnd: (details) {
143
-        //   _previousScale = 1.0;
144
-        // },
145
-        child: Scaffold(
146
-          backgroundColor: Colors.grey,
147
-          appBar: AppBar(
148
-            title: Text('Template Editor'),
149
-            actions: [
150
-              IconButton(
151
-                onPressed: () {
152
-                  Navigator.push(
153
-                    context, 
154
-                    MaterialPageRoute(builder:  (context) => CanvasSetupPage(),)
155
-                  );
156
-                }, 
157
-                icon: Icon(Icons.link)
158
-              )
159
-            ],
160
-          ),
161
-          body: Column(
162
-            children: [
163
-              Expanded(
115
+      child: Scaffold(
116
+        backgroundColor: Colors.grey,
117
+        appBar: AppBar(
118
+          title: Text('Template Editor'),
119
+          actions: [
120
+            IconButton(
121
+              onPressed: () {
122
+                Navigator.push(
123
+                  context, 
124
+                  MaterialPageRoute(builder:  (context) => CanvasSetupPage(),)
125
+                );
126
+              }, 
127
+              icon: Icon(Icons.link)
128
+            )
129
+          ],
130
+        ),
131
+        body: Column(
132
+          children: [
133
+            Expanded(
134
+              child: GestureDetector(
135
+                onTap: () {
136
+                  print('Main Gesture Detector Tapped!');
137
+                  setState(() {
138
+                    Provider.of<Editor>(context, listen: false).unSelectElm();
139
+                  });
140
+                },
164 141
                 child: Stack(
165 142
                   children: [
166 143
                     InteractiveViewer(
@@ -191,7 +168,7 @@ class _HomePageState extends State<HomePage> {
191 168
                         ),
192 169
                       ),
193 170
                     ),
194
-      
171
+                    
195 172
                     if (_showResetPosition) Positioned(
196 173
                       bottom: 16,
197 174
                       right: 16,
@@ -200,7 +177,7 @@ class _HomePageState extends State<HomePage> {
200 177
                         onPressed: () {
201 178
                           Provider.of<Editor>(context, listen: false).resetCanvasTransformationScale();
202 179
                           _showResetPosition = false;
203
-      
180
+                    
204 181
                           setState(() {}); 
205 182
                         }, 
206 183
                         icon: Icon(Icons.center_focus_strong),
@@ -210,11 +187,11 @@ class _HomePageState extends State<HomePage> {
210 187
                   ],
211 188
                 ),
212 189
               ),
213
-      
214
-              // Toolbar
215
-              ToolbarWidget()
216
-            ],
217
-          ),
190
+            ),
191
+            
192
+            // Toolbar
193
+            ToolbarWidget()
194
+          ],
218 195
         ),
219 196
       ),
220 197
     );

+ 117 - 0
lib/providers/editor.dart

@@ -145,6 +145,112 @@ class Editor extends ChangeNotifier {
145 145
     selectElmById(id);
146 146
   }
147 147
 
148
+  // ? Variable Element
149
+
150
+  void addProductNameElement() {
151
+    String id = uuid.v4();
152
+
153
+    ElementProperty element = ElementProperty(
154
+      id: id, 
155
+      valueController: TextEditingController(text: '{{PRODUCTNAME}}'), 
156
+      type: ElementType.textbox,
157
+      variableType: ElementVariableType.productName, 
158
+      position: ElementPosition(top: 0, left: 0), 
159
+      width: 250, 
160
+      quarterTurns: 0, 
161
+      elementKey: GlobalKey()
162
+    );
163
+
164
+    elementProperties.add(element);
165
+    notifyListeners();
166
+
167
+    selectElmById(id);
168
+  }
169
+
170
+  void addVariantNameElement() {
171
+    String id = uuid.v4();
172
+
173
+    ElementProperty element = ElementProperty(
174
+      id: id, 
175
+      valueController: TextEditingController(text: '{{VARIANTNAME}}'), 
176
+      type: ElementType.textbox,
177
+      variableType: ElementVariableType.variantName, 
178
+      position: ElementPosition(top: 0, left: 0), 
179
+      width: 250, 
180
+      quarterTurns: 0, 
181
+      elementKey: GlobalKey()
182
+    );
183
+
184
+    elementProperties.add(element);
185
+    notifyListeners();
186
+
187
+    selectElmById(id);
188
+  }
189
+
190
+  void addProductionCodeElement() {
191
+    String id = uuid.v4();
192
+
193
+    ElementProperty element = ElementProperty(
194
+      id: id, 
195
+      valueController: TextEditingController(text: '{{PRODUCTIONCODE}}'), 
196
+      type: ElementType.textbox,
197
+      variableType: ElementVariableType.productionCode, 
198
+      position: ElementPosition(top: 0, left: 0), 
199
+      width: 250, 
200
+      quarterTurns: 0, 
201
+      elementKey: GlobalKey()
202
+    );
203
+
204
+    elementProperties.add(element);
205
+    notifyListeners();
206
+
207
+    selectElmById(id);
208
+  }
209
+
210
+  void addProductionDateElement() {
211
+    String id = uuid.v4();
212
+
213
+    ElementProperty element = ElementProperty(
214
+      id: id, 
215
+      valueController: TextEditingController(text: '{{PRODUCTIONDATE}}'), 
216
+      type: ElementType.text,
217
+      variableType: ElementVariableType.productionDate, 
218
+      position: ElementPosition(top: 0, left: 0), 
219
+      width: 250, 
220
+      quarterTurns: 0, 
221
+      elementKey: GlobalKey()
222
+    );
223
+
224
+    elementProperties.add(element);
225
+    notifyListeners();
226
+
227
+    selectElmById(id);
228
+  }
229
+
230
+  void addSerialNumberElement() {
231
+    String id = uuid.v4();
232
+
233
+    ElementProperty element = ElementProperty(
234
+      id: id, 
235
+      valueController: TextEditingController(text: '{{SERIALNUMBER}}'), 
236
+      type: ElementType.text,
237
+      variableType: ElementVariableType.serialNumber, 
238
+      position: ElementPosition(top: 0, left: 0), 
239
+      width: 250, 
240
+      quarterTurns: 0, 
241
+      elementKey: GlobalKey()
242
+    );
243
+
244
+    elementProperties.add(element);
245
+    notifyListeners();
246
+
247
+    selectElmById(id);
248
+  }
249
+  
250
+
251
+
252
+
253
+
148 254
   void updateElmPosition(Offset offset) {
149 255
     ElementProperty? element = selectedElm;
150 256
 
@@ -181,6 +287,8 @@ class Editor extends ChangeNotifier {
181 287
   }
182 288
 
183 289
   void enableEdit() {
290
+    if (isVariableElement) return;
291
+
184 292
     if (selectedElm!.isLocked) {
185 293
       _showLockedToast('Cant modify locked element');
186 294
       return;
@@ -255,6 +363,15 @@ class Editor extends ChangeNotifier {
255 363
     return true;
256 364
   }
257 365
 
366
+  bool get isVariableElement {
367
+    if (selectedElm == null) return false;
368
+
369
+    if (selectedElm!.variableType == null) return false;
370
+
371
+    return true;
372
+  }
373
+
374
+
258 375
 
259 376
   // ? Element overlay
260 377
   bool shouldShowTextboxResizer(String elmId) {

+ 3 - 2
lib/style/canvas_style.dart

@@ -34,11 +34,12 @@ class CanvasStyle {
34 34
     return fontSizeMap[elmFontSize] ?? fontSizeFallback;
35 35
   }
36 36
 
37
-  static TextStyle getTextStyle(int elmFontSize) {
37
+  static TextStyle getTextStyle(int elmFontSize, [bool? isVariable]) {
38 38
     return TextStyle(
39 39
       fontFamily: 'RobotoCondensed', 
40 40
       fontSize: CanvasStyle.getFontSize(elmFontSize),
41
-      letterSpacing: 0
41
+      letterSpacing: 0,
42
+      color: isVariable != null ? Color(0xFF547190) : null
42 43
     );
43 44
   }
44 45
 

+ 78 - 27
lib/widgets/elements.dart

@@ -2,6 +2,7 @@ import 'package:defer_pointer/defer_pointer.dart';
2 2
 import 'package:flutter/gestures.dart';
3 3
 import 'package:flutter/material.dart';
4 4
 import 'package:flutter_canvas_editor/providers/editor.dart';
5
+import 'package:intl/intl.dart';
5 6
 import 'package:provider/provider.dart';
6 7
 import 'dart:math' as math;
7 8
 
@@ -10,6 +11,8 @@ import '../style/canvas_style.dart';
10 11
 
11 12
 enum ElementType {text, textbox, qr, image}
12 13
 
14
+enum ElementVariableType {productName, variantName, productionCode, productionDate, serialNumber}
15
+
13 16
 String elementGetter(ElementType type) {
14 17
   switch (type) {
15 18
     case ElementType.text:
@@ -25,6 +28,26 @@ String elementGetter(ElementType type) {
25 28
   }
26 29
 }
27 30
 
31
+String getVariableElmPlaceholder(ElementVariableType type) {
32
+  switch (type) {
33
+    case ElementVariableType.productName:
34
+      return 'Product Name*';
35
+    case ElementVariableType.variantName:
36
+      return 'Variant Name*';
37
+    case ElementVariableType.productionCode:
38
+      return 'Production Code*';
39
+    case ElementVariableType.productionDate:
40
+      DateTime now = DateTime.now();
41
+      final currentDatetime = DateFormat('yyyy-MM-dd HH:mm:ss').format(now.toLocal());
42
+
43
+      return '$currentDatetime*';
44
+    case ElementVariableType.serialNumber:
45
+      return 'Serial Number*';
46
+    default:
47
+      return '';
48
+  }
49
+}
50
+
28 51
 class ElementPosition {
29 52
   double top;
30 53
   double left;
@@ -44,6 +67,7 @@ class ElementProperty {
44 67
   TextEditingController valueController;
45 68
   ElementType type;
46 69
   ElementPosition position;
70
+  ElementVariableType? variableType;
47 71
   // ElementSize size;
48 72
   double width;
49 73
   int quarterTurns;
@@ -65,7 +89,8 @@ class ElementProperty {
65 89
     this.color,
66 90
     this.fontScale = 3,
67 91
     this.qrScale,
68
-    this.isLocked = false
92
+    this.isLocked = false,
93
+    this.variableType
69 94
   });
70 95
 
71 96
 }
@@ -262,32 +287,8 @@ class _ElementWidgetState extends State<ElementWidget> {
262 287
                         strokeAlign: BorderSide.strokeAlignOutside,
263 288
                       ) : null,
264 289
                     ),
265
-                    child:element.type == ElementType.qr ? const Image(image: AssetImage('asset/images/qr_template.png'),) : Provider.of<Editor>(context, listen: true).isEditing && (Provider.of<Editor>(context, listen: true).selectedElmId == element.id) ?
266
-                      IntrinsicWidth(
267
-                        child: TextField(
268
-                          controller: Provider.of<Editor>(context).selectedElm!.valueController,
269
-                          autofocus: true,
270
-                          keyboardType: TextInputType.multiline,
271
-                          enableSuggestions: false,
272
-                          autocorrect: false,
273
-                          maxLines: null,
274
-                          style: CanvasStyle.getTextStyle(element.fontScale),
275
-                          decoration: const InputDecoration(
276
-                            isDense: true,
277
-                            contentPadding: EdgeInsets.zero,
278
-                            border: InputBorder.none,
279
-                          ),
280
-                          onChanged: (_) {
281
-                            setState(() {
282
-                              
283
-                            });
284
-                          }
285
-                        ),
286
-                      ):
287
-                    Text(
288
-                      element.valueController.text,
289
-                      style: CanvasStyle.getTextStyle(element.fontScale),
290
-                    ),
290
+                    // ? child element
291
+                    child: _buildChildElement(element),
291 292
                   ),
292 293
             
293 294
                   // ? Textbox Resizer
@@ -443,4 +444,54 @@ class _ElementWidgetState extends State<ElementWidget> {
443 444
       ),
444 445
     );
445 446
   }
447
+
448
+
449
+  Widget _buildChildElement(ElementProperty element) {
450
+    // ? build QR element
451
+    if (element.type == ElementType.qr) {
452
+      return const Image(image: AssetImage('asset/images/qr_template.png'));
453
+    }
454
+
455
+    // ? [EDITING] build text field
456
+    if (Provider.of<Editor>(context, listen: true).isEditing && (Provider.of<Editor>(context, listen: true).selectedElmId == element.id)) {
457
+      return IntrinsicWidth(
458
+        child: TextField(
459
+          controller: Provider.of<Editor>(context).selectedElm!.valueController,
460
+          autofocus: true,
461
+          keyboardType: TextInputType.multiline,
462
+          enableSuggestions: false,
463
+          autocorrect: false,
464
+          maxLines: null,
465
+          style: CanvasStyle.getTextStyle(element.fontScale),
466
+          decoration: const InputDecoration(
467
+            isDense: true,
468
+            contentPadding: EdgeInsets.zero,
469
+            border: InputBorder.none,
470
+          ),
471
+          onChanged: (_) {
472
+            setState(() {
473
+              
474
+            });
475
+          }
476
+        ),
477
+      );
478
+    }
479
+
480
+    // ? build variable element
481
+    if (element.variableType != null) {
482
+      return Text(
483
+        getVariableElmPlaceholder(element.variableType!),
484
+        style: CanvasStyle.getTextStyle(element.fontScale, true),
485
+      );
486
+    }
487
+
488
+
489
+    // ? build text element
490
+    return Text(
491
+      element.valueController.text,
492
+      style: CanvasStyle.getTextStyle(element.fontScale),
493
+    );
494
+    
495
+  }
496
+  
446 497
 }

+ 80 - 28
lib/widgets/toolbar.dart

@@ -80,10 +80,34 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
80 80
         onPressed: Provider.of<Editor>(context, listen: false).addTextboxElement, 
81 81
         child: Text('Add Textbox Element')
82 82
       ),
83
-      // ElevatedButton(
84
-      //   onPressed: Provider.of<Editor>(context, listen: false).addQrCodeElement, 
85
-      //   child: Text('Add Qr Code Element')
86
-      // ),
83
+      SizedBox(height: 24),
84
+
85
+
86
+      // ? Variable Element Section
87
+      Text('Insert Variable Element'),
88
+      SizedBox(height: 24),
89
+
90
+
91
+      ElevatedButton(
92
+        onPressed: Provider.of<Editor>(context, listen: false).addProductNameElement, 
93
+        child: Text('Add Product Name Element')
94
+      ),
95
+      ElevatedButton(
96
+        onPressed: Provider.of<Editor>(context, listen: false).addVariantNameElement, 
97
+        child: Text('Add Variant Name Element')
98
+      ),
99
+      ElevatedButton(
100
+        onPressed: Provider.of<Editor>(context, listen: false).addProductionCodeElement, 
101
+        child: Text('Add Production Code Element')
102
+      ),
103
+      ElevatedButton(
104
+        onPressed: Provider.of<Editor>(context, listen: false).addProductionDateElement, 
105
+        child: Text('Add Production Date Element')
106
+      ),
107
+      ElevatedButton(
108
+        onPressed: Provider.of<Editor>(context, listen: false).addSerialNumberElement, 
109
+        child: Text('Add Serial Number Element')
110
+      ),
87 111
     ];
88 112
   }
89 113
 
@@ -93,10 +117,37 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
93 117
     return [
94 118
       Text('Properties'),
95 119
       SizedBox(height: 20),
120
+
96 121
       Text('Selected elements: ${Provider.of<Editor>(context).selectedElmType}'),
97 122
 
98 123
       Text('Top: ${Provider.of<Editor>(context, listen: true).selectedElm!.position.top}, Left: ${Provider.of<Editor>(context, listen: true).selectedElm!.position.left}}'),
124
+      SizedBox(height: 12),
125
+      
126
+      ...Provider.of<Editor>(context).isVariableElement ? _variablePropertiesSection(editorProvider, element) : _commonPropertiesSection(editorProvider, element),
127
+
99 128
       
129
+    ];
130
+  }
131
+
132
+
133
+  List<Widget> _variablePropertiesSection(Editor editorProvider, ElementProperty? element) {
134
+    return [
135
+      Container(
136
+        margin: EdgeInsets.only(bottom: 16),
137
+        color: Colors.grey[200],
138
+        width: double.infinity,
139
+        child: Text(
140
+          'This is a variable element, the value shown in editor is a placeholder. The actual value will be generated when printing the label.',
141
+        )
142
+      ),
143
+
144
+      // ? Font Resizer
145
+      _buildFontResizer(element)
146
+    ];
147
+  }
148
+
149
+  List<Widget> _commonPropertiesSection(Editor editorProvider, ElementProperty? element) {
150
+    return [
100 151
       // ? Value Editor
101 152
       if([ElementType.text, ElementType.textbox].contains(editorProvider.selectedElm!.type)) TextField(
102 153
         readOnly: element!.isLocked, 
@@ -122,30 +173,7 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
122 173
       ),
123 174
 
124 175
       // ? Font Resizer (Only show when selected element is [ElementType.text, ElementType.textbox])
125
-      if (Provider.of<Editor>(context).shouldShowFontResizer) Row(
126
-        children: [
127
-          DropdownButton<int>(
128
-            value: element?.fontScale,
129
-            items: fontSizeDropdownItems, 
130
-            onChanged: (val) {
131
-              print('dropdown value: $val');
132
-              Provider.of<Editor>(context, listen: false).changeFontSize(val);
133
-            }
134
-          ),
135
-          IconButton.filled(
136
-            onPressed: Provider.of<Editor>(context, listen: false).incrementFontSize, 
137
-            icon: Icon(Icons.add)
138
-          ),
139
-          IconButton.filled(
140
-            onPressed:  Provider.of<Editor>(context, listen: false).decrementFontSize,
141
-            icon: Icon(Icons.remove)
142
-          ),
143
-          IconButton.filled(
144
-            onPressed:  () => print('kepenjet tombol test'),
145
-            icon: Icon(Icons.remove)
146
-          ),
147
-        ],
148
-      ),
176
+      if (Provider.of<Editor>(context).shouldShowFontResizer) _buildFontResizer(element),
149 177
 
150 178
       // ? Qr Resizer (Only show when selected element is ElementType.qr)
151 179
       if (Provider.of<Editor>(context).shouldShowQrResizer) Row(
@@ -197,4 +225,28 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
197 225
       ),
198 226
     ];
199 227
   }
228
+
229
+
230
+  Widget _buildFontResizer(ElementProperty? element) {
231
+    return Row(
232
+      children: [
233
+        DropdownButton<int>(
234
+          value: element?.fontScale,
235
+          items: fontSizeDropdownItems, 
236
+          onChanged: (val) {
237
+            print('dropdown value: $val');
238
+            Provider.of<Editor>(context, listen: false).changeFontSize(val);
239
+          }
240
+        ),
241
+        IconButton.filled(
242
+          onPressed: Provider.of<Editor>(context, listen: false).incrementFontSize, 
243
+          icon: Icon(Icons.add)
244
+        ),
245
+        IconButton.filled(
246
+          onPressed:  Provider.of<Editor>(context, listen: false).decrementFontSize,
247
+          icon: Icon(Icons.remove)
248
+        ),
249
+      ],
250
+    );
251
+  }
200 252
 }

+ 8 - 0
pubspec.lock

@@ -115,6 +115,14 @@ packages:
115 115
       url: "https://pub.dev"
116 116
     source: hosted
117 117
     version: "1.0.0"
118
+  intl:
119
+    dependency: "direct main"
120
+    description:
121
+      name: intl
122
+      sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
123
+      url: "https://pub.dev"
124
+    source: hosted
125
+    version: "0.20.2"
118 126
   leak_tracker:
119 127
     dependency: transitive
120 128
     description:

+ 1 - 0
pubspec.yaml

@@ -36,6 +36,7 @@ dependencies:
36 36
   resizable_widget: ^1.0.5
37 37
   uuid: ^4.5.1
38 38
   toastification: ^2.3.0
39
+  intl: ^0.20.2
39 40
 
40 41
 dev_dependencies:
41 42
   flutter_lints: ^3.0.0