浏览代码

feat: variable elements

Raihan Rizal 4 月之前
父节点
当前提交
8855d17bfe
共有 7 个文件被更改,包括 320 次插入113 次删除
  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
   Widget build(BuildContext context) {
112
   Widget build(BuildContext context) {
113
     return DeferredPointerHandler(
113
     return DeferredPointerHandler(
114
       link: Provider.of<Editor>(context).textboxResizerDeferredPointerHandlerLink,
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
                 child: Stack(
141
                 child: Stack(
165
                   children: [
142
                   children: [
166
                     InteractiveViewer(
143
                     InteractiveViewer(
191
                         ),
168
                         ),
192
                       ),
169
                       ),
193
                     ),
170
                     ),
194
-      
171
+                    
195
                     if (_showResetPosition) Positioned(
172
                     if (_showResetPosition) Positioned(
196
                       bottom: 16,
173
                       bottom: 16,
197
                       right: 16,
174
                       right: 16,
200
                         onPressed: () {
177
                         onPressed: () {
201
                           Provider.of<Editor>(context, listen: false).resetCanvasTransformationScale();
178
                           Provider.of<Editor>(context, listen: false).resetCanvasTransformationScale();
202
                           _showResetPosition = false;
179
                           _showResetPosition = false;
203
-      
180
+                    
204
                           setState(() {}); 
181
                           setState(() {}); 
205
                         }, 
182
                         }, 
206
                         icon: Icon(Icons.center_focus_strong),
183
                         icon: Icon(Icons.center_focus_strong),
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
     selectElmById(id);
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
   void updateElmPosition(Offset offset) {
254
   void updateElmPosition(Offset offset) {
149
     ElementProperty? element = selectedElm;
255
     ElementProperty? element = selectedElm;
150
 
256
 
181
   }
287
   }
182
 
288
 
183
   void enableEdit() {
289
   void enableEdit() {
290
+    if (isVariableElement) return;
291
+
184
     if (selectedElm!.isLocked) {
292
     if (selectedElm!.isLocked) {
185
       _showLockedToast('Cant modify locked element');
293
       _showLockedToast('Cant modify locked element');
186
       return;
294
       return;
255
     return true;
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
   // ? Element overlay
376
   // ? Element overlay
260
   bool shouldShowTextboxResizer(String elmId) {
377
   bool shouldShowTextboxResizer(String elmId) {

+ 3 - 2
lib/style/canvas_style.dart

34
     return fontSizeMap[elmFontSize] ?? fontSizeFallback;
34
     return fontSizeMap[elmFontSize] ?? fontSizeFallback;
35
   }
35
   }
36
 
36
 
37
-  static TextStyle getTextStyle(int elmFontSize) {
37
+  static TextStyle getTextStyle(int elmFontSize, [bool? isVariable]) {
38
     return TextStyle(
38
     return TextStyle(
39
       fontFamily: 'RobotoCondensed', 
39
       fontFamily: 'RobotoCondensed', 
40
       fontSize: CanvasStyle.getFontSize(elmFontSize),
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
 import 'package:flutter/gestures.dart';
2
 import 'package:flutter/gestures.dart';
3
 import 'package:flutter/material.dart';
3
 import 'package:flutter/material.dart';
4
 import 'package:flutter_canvas_editor/providers/editor.dart';
4
 import 'package:flutter_canvas_editor/providers/editor.dart';
5
+import 'package:intl/intl.dart';
5
 import 'package:provider/provider.dart';
6
 import 'package:provider/provider.dart';
6
 import 'dart:math' as math;
7
 import 'dart:math' as math;
7
 
8
 
10
 
11
 
11
 enum ElementType {text, textbox, qr, image}
12
 enum ElementType {text, textbox, qr, image}
12
 
13
 
14
+enum ElementVariableType {productName, variantName, productionCode, productionDate, serialNumber}
15
+
13
 String elementGetter(ElementType type) {
16
 String elementGetter(ElementType type) {
14
   switch (type) {
17
   switch (type) {
15
     case ElementType.text:
18
     case ElementType.text:
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
 class ElementPosition {
51
 class ElementPosition {
29
   double top;
52
   double top;
30
   double left;
53
   double left;
44
   TextEditingController valueController;
67
   TextEditingController valueController;
45
   ElementType type;
68
   ElementType type;
46
   ElementPosition position;
69
   ElementPosition position;
70
+  ElementVariableType? variableType;
47
   // ElementSize size;
71
   // ElementSize size;
48
   double width;
72
   double width;
49
   int quarterTurns;
73
   int quarterTurns;
65
     this.color,
89
     this.color,
66
     this.fontScale = 3,
90
     this.fontScale = 3,
67
     this.qrScale,
91
     this.qrScale,
68
-    this.isLocked = false
92
+    this.isLocked = false,
93
+    this.variableType
69
   });
94
   });
70
 
95
 
71
 }
96
 }
262
                         strokeAlign: BorderSide.strokeAlignOutside,
287
                         strokeAlign: BorderSide.strokeAlignOutside,
263
                       ) : null,
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
                   // ? Textbox Resizer
294
                   // ? Textbox Resizer
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
         onPressed: Provider.of<Editor>(context, listen: false).addTextboxElement, 
80
         onPressed: Provider.of<Editor>(context, listen: false).addTextboxElement, 
81
         child: Text('Add Textbox Element')
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
     return [
117
     return [
94
       Text('Properties'),
118
       Text('Properties'),
95
       SizedBox(height: 20),
119
       SizedBox(height: 20),
120
+
96
       Text('Selected elements: ${Provider.of<Editor>(context).selectedElmType}'),
121
       Text('Selected elements: ${Provider.of<Editor>(context).selectedElmType}'),
97
 
122
 
98
       Text('Top: ${Provider.of<Editor>(context, listen: true).selectedElm!.position.top}, Left: ${Provider.of<Editor>(context, listen: true).selectedElm!.position.left}}'),
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
       // ? Value Editor
151
       // ? Value Editor
101
       if([ElementType.text, ElementType.textbox].contains(editorProvider.selectedElm!.type)) TextField(
152
       if([ElementType.text, ElementType.textbox].contains(editorProvider.selectedElm!.type)) TextField(
102
         readOnly: element!.isLocked, 
153
         readOnly: element!.isLocked, 
122
       ),
173
       ),
123
 
174
 
124
       // ? Font Resizer (Only show when selected element is [ElementType.text, ElementType.textbox])
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
       // ? Qr Resizer (Only show when selected element is ElementType.qr)
178
       // ? Qr Resizer (Only show when selected element is ElementType.qr)
151
       if (Provider.of<Editor>(context).shouldShowQrResizer) Row(
179
       if (Provider.of<Editor>(context).shouldShowQrResizer) Row(
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
       url: "https://pub.dev"
115
       url: "https://pub.dev"
116
     source: hosted
116
     source: hosted
117
     version: "1.0.0"
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
   leak_tracker:
126
   leak_tracker:
119
     dependency: transitive
127
     dependency: transitive
120
     description:
128
     description:

+ 1 - 0
pubspec.yaml

36
   resizable_widget: ^1.0.5
36
   resizable_widget: ^1.0.5
37
   uuid: ^4.5.1
37
   uuid: ^4.5.1
38
   toastification: ^2.3.0
38
   toastification: ^2.3.0
39
+  intl: ^0.20.2
39
 
40
 
40
 dev_dependencies:
41
 dev_dependencies:
41
   flutter_lints: ^3.0.0
42
   flutter_lints: ^3.0.0