957 lines
28 KiB
Dart
957 lines
28 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
|
|
enum BarcodeType {
|
|
code128,
|
|
ean13,
|
|
code39,
|
|
upca,
|
|
qrCode,
|
|
}
|
|
|
|
enum LabelOrientation {
|
|
portrait,
|
|
landscape,
|
|
}
|
|
|
|
enum ElementType {
|
|
text,
|
|
dynamicText,
|
|
barcode,
|
|
qrCode,
|
|
rectangle,
|
|
line,
|
|
}
|
|
|
|
class LabelConfiguration {
|
|
final double widthMm;
|
|
final double heightMm;
|
|
final double marginTopMm;
|
|
final double marginBottomMm;
|
|
final double marginLeftMm;
|
|
final double marginRightMm;
|
|
final int dpi; // 203, 300, 600
|
|
final LabelOrientation orientation;
|
|
final String printerLanguage; // ZPL, TSPL, ESCPOS, PCL
|
|
final int columnsAcross; // 1-Up (single), 2-Up (twin track), 3-Up, 4-Up
|
|
final double horizontalGapMm; // Gap between side-by-side labels in mm
|
|
final double verticalGapMm; // Gap between consecutive labels along roll feed in mm
|
|
|
|
const LabelConfiguration({
|
|
this.widthMm = 50.0,
|
|
this.heightMm = 25.0,
|
|
this.marginTopMm = 1.0,
|
|
this.marginBottomMm = 1.0,
|
|
this.marginLeftMm = 1.0,
|
|
this.marginRightMm = 1.0,
|
|
this.dpi = 203,
|
|
this.orientation = LabelOrientation.portrait,
|
|
this.printerLanguage = 'ZPL',
|
|
this.columnsAcross = 1,
|
|
this.horizontalGapMm = 2.0,
|
|
this.verticalGapMm = 2.0,
|
|
});
|
|
|
|
/// Total roll web width including all columns and gaps between them
|
|
double get totalWebWidthMm {
|
|
final cols = columnsAcross.clamp(1, 4);
|
|
if (cols <= 1) return widthMm;
|
|
return (widthMm * cols) + (horizontalGapMm * (cols - 1));
|
|
}
|
|
|
|
int get totalWebWidthDots => mmToDots(totalWebWidthMm);
|
|
|
|
LabelConfiguration copyWith({
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? marginTopMm,
|
|
double? marginBottomMm,
|
|
double? marginLeftMm,
|
|
double? marginRightMm,
|
|
int? dpi,
|
|
LabelOrientation? orientation,
|
|
String? printerLanguage,
|
|
int? columnsAcross,
|
|
double? horizontalGapMm,
|
|
double? verticalGapMm,
|
|
}) {
|
|
return LabelConfiguration(
|
|
widthMm: widthMm ?? this.widthMm,
|
|
heightMm: heightMm ?? this.heightMm,
|
|
marginTopMm: marginTopMm ?? this.marginTopMm,
|
|
marginBottomMm: marginBottomMm ?? this.marginBottomMm,
|
|
marginLeftMm: marginLeftMm ?? this.marginLeftMm,
|
|
marginRightMm: marginRightMm ?? this.marginRightMm,
|
|
dpi: dpi ?? this.dpi,
|
|
orientation: orientation ?? this.orientation,
|
|
printerLanguage: printerLanguage ?? this.printerLanguage,
|
|
columnsAcross: columnsAcross ?? this.columnsAcross,
|
|
horizontalGapMm: horizontalGapMm ?? this.horizontalGapMm,
|
|
verticalGapMm: verticalGapMm ?? this.verticalGapMm,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'widthMm': widthMm,
|
|
'heightMm': heightMm,
|
|
'marginTopMm': marginTopMm,
|
|
'marginBottomMm': marginBottomMm,
|
|
'marginLeftMm': marginLeftMm,
|
|
'marginRightMm': marginRightMm,
|
|
'dpi': dpi,
|
|
'orientation': orientation.name,
|
|
'printerLanguage': printerLanguage,
|
|
'columnsAcross': columnsAcross,
|
|
'horizontalGapMm': horizontalGapMm,
|
|
'verticalGapMm': verticalGapMm,
|
|
};
|
|
|
|
factory LabelConfiguration.fromJson(Map<String, dynamic> json) {
|
|
return LabelConfiguration(
|
|
widthMm: (json['widthMm'] as num?)?.toDouble() ?? 50.0,
|
|
heightMm: (json['heightMm'] as num?)?.toDouble() ?? 25.0,
|
|
marginTopMm: (json['marginTopMm'] as num?)?.toDouble() ?? 1.0,
|
|
marginBottomMm: (json['marginBottomMm'] as num?)?.toDouble() ?? 1.0,
|
|
marginLeftMm: (json['marginLeftMm'] as num?)?.toDouble() ?? 1.0,
|
|
marginRightMm: (json['marginRightMm'] as num?)?.toDouble() ?? 1.0,
|
|
dpi: json['dpi'] as int? ?? 203,
|
|
orientation: json['orientation'] == 'landscape'
|
|
? LabelOrientation.landscape
|
|
: LabelOrientation.portrait,
|
|
printerLanguage: json['printerLanguage'] as String? ?? 'ZPL',
|
|
columnsAcross: (json['columnsAcross'] as num?)?.toInt() ?? 1,
|
|
horizontalGapMm: (json['horizontalGapMm'] as num?)?.toDouble() ?? 2.0,
|
|
verticalGapMm: (json['verticalGapMm'] as num?)?.toDouble() ?? 2.0,
|
|
);
|
|
}
|
|
|
|
int mmToDots(double mm) => ((mm / 25.4) * dpi).round();
|
|
int get widthDots => mmToDots(widthMm);
|
|
int get heightDots => mmToDots(heightMm);
|
|
}
|
|
|
|
abstract class LabelElement {
|
|
final String id;
|
|
final ElementType type;
|
|
final double xMm;
|
|
final double yMm;
|
|
final double widthMm;
|
|
final double heightMm;
|
|
final double rotation; // 0, 90, 180, 270
|
|
final bool isLocked;
|
|
final int zIndex;
|
|
|
|
const LabelElement({
|
|
required this.id,
|
|
required this.type,
|
|
required this.xMm,
|
|
required this.yMm,
|
|
required this.widthMm,
|
|
required this.heightMm,
|
|
this.rotation = 0.0,
|
|
this.isLocked = false,
|
|
this.zIndex = 0,
|
|
});
|
|
|
|
LabelElement copyWithPosition({
|
|
double? xMm,
|
|
double? yMm,
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? rotation,
|
|
bool? isLocked,
|
|
int? zIndex,
|
|
});
|
|
|
|
double get borderWidthMm => 0.0;
|
|
|
|
Map<String, dynamic> toJson();
|
|
|
|
static LabelElement fromJson(Map<String, dynamic> json) {
|
|
final typeStr = json['type'] as String? ?? 'text';
|
|
switch (typeStr) {
|
|
case 'dynamicText':
|
|
return DynamicTextElement.fromJson(json);
|
|
case 'barcode':
|
|
return BarcodeElement.fromJson(json);
|
|
case 'qrCode':
|
|
return QrCodeElement.fromJson(json);
|
|
case 'rectangle':
|
|
return RectangleElement.fromJson(json);
|
|
case 'line':
|
|
return LineElement.fromJson(json);
|
|
case 'text':
|
|
default:
|
|
return TextElement.fromJson(json);
|
|
}
|
|
}
|
|
}
|
|
|
|
class TextElement extends LabelElement {
|
|
final String text;
|
|
final String fontFamily;
|
|
final double fontSizePt;
|
|
final bool isBold;
|
|
final bool isItalic;
|
|
final TextAlign alignment;
|
|
@override
|
|
final double borderWidthMm;
|
|
final double paddingMm;
|
|
|
|
const TextElement({
|
|
required super.id,
|
|
required super.xMm,
|
|
required super.yMm,
|
|
required super.widthMm,
|
|
required super.heightMm,
|
|
super.rotation = 0.0,
|
|
super.isLocked = false,
|
|
super.zIndex = 0,
|
|
this.text = 'kifi',
|
|
this.fontFamily = 'Roboto',
|
|
this.fontSizePt = 9.0,
|
|
this.isBold = false,
|
|
this.isItalic = false,
|
|
this.alignment = TextAlign.left,
|
|
this.borderWidthMm = 0.0,
|
|
this.paddingMm = 0.0,
|
|
}) : super(type: ElementType.text);
|
|
|
|
@override
|
|
TextElement copyWithPosition({
|
|
double? xMm,
|
|
double? yMm,
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? rotation,
|
|
bool? isLocked,
|
|
int? zIndex,
|
|
String? text,
|
|
String? fontFamily,
|
|
double? fontSizePt,
|
|
bool? isBold,
|
|
bool? isItalic,
|
|
TextAlign? alignment,
|
|
double? borderWidthMm,
|
|
double? paddingMm,
|
|
}) {
|
|
return TextElement(
|
|
id: id,
|
|
xMm: xMm ?? this.xMm,
|
|
yMm: yMm ?? this.yMm,
|
|
widthMm: widthMm ?? this.widthMm,
|
|
heightMm: heightMm ?? this.heightMm,
|
|
rotation: rotation ?? this.rotation,
|
|
isLocked: isLocked ?? this.isLocked,
|
|
zIndex: zIndex ?? this.zIndex,
|
|
text: text ?? this.text,
|
|
fontFamily: fontFamily ?? this.fontFamily,
|
|
fontSizePt: fontSizePt ?? this.fontSizePt,
|
|
isBold: isBold ?? this.isBold,
|
|
isItalic: isItalic ?? this.isItalic,
|
|
alignment: alignment ?? this.alignment,
|
|
borderWidthMm: borderWidthMm ?? this.borderWidthMm,
|
|
paddingMm: paddingMm ?? this.paddingMm,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'type': 'text',
|
|
'xMm': xMm,
|
|
'yMm': yMm,
|
|
'widthMm': widthMm,
|
|
'heightMm': heightMm,
|
|
'rotation': rotation,
|
|
'isLocked': isLocked,
|
|
'zIndex': zIndex,
|
|
'text': text,
|
|
'fontFamily': fontFamily,
|
|
'fontSizePt': fontSizePt,
|
|
'isBold': isBold,
|
|
'isItalic': isItalic,
|
|
'alignment': alignment.name,
|
|
'borderWidthMm': borderWidthMm,
|
|
'paddingMm': paddingMm,
|
|
};
|
|
|
|
factory TextElement.fromJson(Map<String, dynamic> json) {
|
|
return TextElement(
|
|
id: json['id'] as String,
|
|
xMm: (json['xMm'] as num?)?.toDouble() ?? 0.0,
|
|
yMm: (json['yMm'] as num?)?.toDouble() ?? 0.0,
|
|
widthMm: (json['widthMm'] as num?)?.toDouble() ?? 20.0,
|
|
heightMm: (json['heightMm'] as num?)?.toDouble() ?? 5.0,
|
|
rotation: (json['rotation'] as num?)?.toDouble() ?? 0.0,
|
|
isLocked: json['isLocked'] as bool? ?? false,
|
|
zIndex: json['zIndex'] as int? ?? 0,
|
|
text: json['text'] as String? ?? json['value'] as String? ?? 'kifi',
|
|
fontFamily: json['fontFamily'] as String? ?? 'Roboto',
|
|
fontSizePt: (json['fontSizePt'] as num?)?.toDouble() ?? 9.0,
|
|
isBold: json['isBold'] as bool? ?? false,
|
|
isItalic: json['isItalic'] as bool? ?? false,
|
|
alignment: TextAlign.values.where((a) => a.name == json['alignment']).firstOrNull ?? TextAlign.left,
|
|
borderWidthMm: (json['borderWidthMm'] as num?)?.toDouble() ?? 0.0,
|
|
paddingMm: (json['paddingMm'] as num?)?.toDouble() ?? 0.0,
|
|
);
|
|
}
|
|
}
|
|
|
|
class DynamicTextElement extends LabelElement {
|
|
final String source; // "product"
|
|
final String fieldKey; // "name", "sellingPrice", etc.
|
|
final String? prefix;
|
|
final String? suffix;
|
|
final String fallbackValue;
|
|
final String fontFamily;
|
|
final double fontSizePt;
|
|
final bool isBold;
|
|
final bool isItalic;
|
|
final TextAlign alignment;
|
|
@override
|
|
final double borderWidthMm;
|
|
final double paddingMm;
|
|
final bool autoFit;
|
|
final int maxLines;
|
|
|
|
const DynamicTextElement({
|
|
required super.id,
|
|
required super.xMm,
|
|
required super.yMm,
|
|
required super.widthMm,
|
|
required super.heightMm,
|
|
super.rotation = 0.0,
|
|
super.isLocked = false,
|
|
super.zIndex = 0,
|
|
this.source = 'product',
|
|
this.fieldKey = '',
|
|
this.prefix,
|
|
this.suffix,
|
|
this.fallbackValue = 'N/A',
|
|
this.fontFamily = 'Roboto',
|
|
this.fontSizePt = 9.0,
|
|
this.isBold = false,
|
|
this.isItalic = false,
|
|
this.alignment = TextAlign.left,
|
|
this.borderWidthMm = 0.0,
|
|
this.paddingMm = 0.0,
|
|
this.autoFit = true,
|
|
this.maxLines = 2,
|
|
}) : super(type: ElementType.dynamicText);
|
|
|
|
String get placeholder => fieldKey.isNotEmpty ? '{{$source.$fieldKey}}' : '{{select_field}}';
|
|
|
|
String formatValue(String? resolvedRaw) {
|
|
if (resolvedRaw == null || resolvedRaw.trim().isEmpty) {
|
|
return fallbackValue;
|
|
}
|
|
final p = prefix ?? '';
|
|
final s = suffix ?? '';
|
|
return '$p$resolvedRaw$s';
|
|
}
|
|
|
|
@override
|
|
DynamicTextElement copyWithPosition({
|
|
double? xMm,
|
|
double? yMm,
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? rotation,
|
|
bool? isLocked,
|
|
int? zIndex,
|
|
String? source,
|
|
String? fieldKey,
|
|
String? prefix,
|
|
String? suffix,
|
|
String? fallbackValue,
|
|
String? fontFamily,
|
|
double? fontSizePt,
|
|
bool? isBold,
|
|
bool? isItalic,
|
|
TextAlign? alignment,
|
|
double? borderWidthMm,
|
|
double? paddingMm,
|
|
bool? autoFit,
|
|
int? maxLines,
|
|
}) {
|
|
return DynamicTextElement(
|
|
id: id,
|
|
xMm: xMm ?? this.xMm,
|
|
yMm: yMm ?? this.yMm,
|
|
widthMm: widthMm ?? this.widthMm,
|
|
heightMm: heightMm ?? this.heightMm,
|
|
rotation: rotation ?? this.rotation,
|
|
isLocked: isLocked ?? this.isLocked,
|
|
zIndex: zIndex ?? this.zIndex,
|
|
source: source ?? this.source,
|
|
fieldKey: fieldKey ?? this.fieldKey,
|
|
prefix: prefix ?? this.prefix,
|
|
suffix: suffix ?? this.suffix,
|
|
fallbackValue: fallbackValue ?? this.fallbackValue,
|
|
fontFamily: fontFamily ?? this.fontFamily,
|
|
fontSizePt: fontSizePt ?? this.fontSizePt,
|
|
isBold: isBold ?? this.isBold,
|
|
isItalic: isItalic ?? this.isItalic,
|
|
alignment: alignment ?? this.alignment,
|
|
borderWidthMm: borderWidthMm ?? this.borderWidthMm,
|
|
paddingMm: paddingMm ?? this.paddingMm,
|
|
autoFit: autoFit ?? this.autoFit,
|
|
maxLines: maxLines ?? this.maxLines,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'type': 'dynamicText',
|
|
'xMm': xMm,
|
|
'yMm': yMm,
|
|
'widthMm': widthMm,
|
|
'heightMm': heightMm,
|
|
'rotation': rotation,
|
|
'isLocked': isLocked,
|
|
'zIndex': zIndex,
|
|
'source': source,
|
|
'fieldKey': fieldKey,
|
|
'placeholder': placeholder,
|
|
'prefix': prefix,
|
|
'suffix': suffix,
|
|
'fallbackValue': fallbackValue,
|
|
'fontFamily': fontFamily,
|
|
'fontSizePt': fontSizePt,
|
|
'isBold': isBold,
|
|
'isItalic': isItalic,
|
|
'alignment': alignment.name,
|
|
'borderWidthMm': borderWidthMm,
|
|
'paddingMm': paddingMm,
|
|
'autoFit': autoFit,
|
|
'maxLines': maxLines,
|
|
};
|
|
|
|
factory DynamicTextElement.fromJson(Map<String, dynamic> json) {
|
|
return DynamicTextElement(
|
|
id: json['id'] as String,
|
|
xMm: (json['xMm'] as num?)?.toDouble() ?? 0.0,
|
|
yMm: (json['yMm'] as num?)?.toDouble() ?? 0.0,
|
|
widthMm: (json['widthMm'] as num?)?.toDouble() ?? 30.0,
|
|
heightMm: (json['heightMm'] as num?)?.toDouble() ?? 5.0,
|
|
rotation: (json['rotation'] as num?)?.toDouble() ?? 0.0,
|
|
isLocked: json['isLocked'] as bool? ?? false,
|
|
zIndex: json['zIndex'] as int? ?? 0,
|
|
source: json['source'] as String? ?? 'product',
|
|
fieldKey: json['fieldKey'] as String? ?? json['field'] as String? ?? '',
|
|
prefix: json['prefix'] as String?,
|
|
suffix: json['suffix'] as String?,
|
|
fallbackValue: json['fallbackValue'] as String? ?? json['fallback'] as String? ?? 'N/A',
|
|
fontFamily: json['fontFamily'] as String? ?? 'Roboto',
|
|
fontSizePt: (json['fontSizePt'] as num?)?.toDouble() ?? 9.0,
|
|
isBold: json['isBold'] as bool? ?? false,
|
|
isItalic: json['isItalic'] as bool? ?? false,
|
|
alignment: TextAlign.values.where((a) => a.name == json['alignment']).firstOrNull ?? TextAlign.left,
|
|
borderWidthMm: (json['borderWidthMm'] as num?)?.toDouble() ?? 0.0,
|
|
paddingMm: (json['paddingMm'] as num?)?.toDouble() ?? 0.0,
|
|
autoFit: json['autoFit'] as bool? ?? true,
|
|
maxLines: json['maxLines'] as int? ?? 2,
|
|
);
|
|
}
|
|
}
|
|
|
|
class BarcodeElement extends LabelElement {
|
|
final String valueType; // "static" or "dynamic"
|
|
final String staticValue;
|
|
final String source; // "product"
|
|
final String fieldKey; // "barcode" or "sku"
|
|
final BarcodeType barcodeType;
|
|
final bool showText;
|
|
@override
|
|
final double borderWidthMm;
|
|
final double paddingMm;
|
|
|
|
const BarcodeElement({
|
|
required super.id,
|
|
required super.xMm,
|
|
required super.yMm,
|
|
required super.widthMm,
|
|
required super.heightMm,
|
|
super.rotation = 0.0,
|
|
super.isLocked = false,
|
|
super.zIndex = 0,
|
|
this.valueType = 'dynamic',
|
|
this.staticValue = '123456789012',
|
|
this.source = 'product',
|
|
this.fieldKey = 'barcode',
|
|
this.barcodeType = BarcodeType.code128,
|
|
this.showText = true,
|
|
this.borderWidthMm = 0.0,
|
|
this.paddingMm = 0.5,
|
|
}) : super(type: ElementType.barcode);
|
|
|
|
String get placeholder => valueType == 'dynamic' ? '{{$source.$fieldKey}}' : staticValue;
|
|
|
|
@override
|
|
BarcodeElement copyWithPosition({
|
|
double? xMm,
|
|
double? yMm,
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? rotation,
|
|
bool? isLocked,
|
|
int? zIndex,
|
|
String? valueType,
|
|
String? staticValue,
|
|
String? source,
|
|
String? fieldKey,
|
|
BarcodeType? barcodeType,
|
|
bool? showText,
|
|
double? borderWidthMm,
|
|
double? paddingMm,
|
|
}) {
|
|
return BarcodeElement(
|
|
id: id,
|
|
xMm: xMm ?? this.xMm,
|
|
yMm: yMm ?? this.yMm,
|
|
widthMm: widthMm ?? this.widthMm,
|
|
heightMm: heightMm ?? this.heightMm,
|
|
rotation: rotation ?? this.rotation,
|
|
isLocked: isLocked ?? this.isLocked,
|
|
zIndex: zIndex ?? this.zIndex,
|
|
valueType: valueType ?? this.valueType,
|
|
staticValue: staticValue ?? this.staticValue,
|
|
source: source ?? this.source,
|
|
fieldKey: fieldKey ?? this.fieldKey,
|
|
barcodeType: barcodeType ?? this.barcodeType,
|
|
showText: showText ?? this.showText,
|
|
borderWidthMm: borderWidthMm ?? this.borderWidthMm,
|
|
paddingMm: paddingMm ?? this.paddingMm,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'type': 'barcode',
|
|
'xMm': xMm,
|
|
'yMm': yMm,
|
|
'widthMm': widthMm,
|
|
'heightMm': heightMm,
|
|
'rotation': rotation,
|
|
'isLocked': isLocked,
|
|
'zIndex': zIndex,
|
|
'valueType': valueType,
|
|
'staticValue': staticValue,
|
|
'source': source,
|
|
'fieldKey': fieldKey,
|
|
'placeholder': placeholder,
|
|
'barcodeType': barcodeType.name,
|
|
'showText': showText,
|
|
'borderWidthMm': borderWidthMm,
|
|
'paddingMm': paddingMm,
|
|
};
|
|
|
|
factory BarcodeElement.fromJson(Map<String, dynamic> json) {
|
|
return BarcodeElement(
|
|
id: json['id'] as String,
|
|
xMm: (json['xMm'] as num?)?.toDouble() ?? 0.0,
|
|
yMm: (json['yMm'] as num?)?.toDouble() ?? 0.0,
|
|
widthMm: (json['widthMm'] as num?)?.toDouble() ?? 40.0,
|
|
heightMm: (json['heightMm'] as num?)?.toDouble() ?? 10.0,
|
|
rotation: (json['rotation'] as num?)?.toDouble() ?? 0.0,
|
|
isLocked: json['isLocked'] as bool? ?? false,
|
|
zIndex: json['zIndex'] as int? ?? 0,
|
|
valueType: json['valueType'] as String? ?? 'dynamic',
|
|
staticValue: json['staticValue'] as String? ?? '123456789012',
|
|
source: json['source'] as String? ?? 'product',
|
|
fieldKey: json['fieldKey'] as String? ?? json['field'] as String? ?? 'barcode',
|
|
barcodeType: BarcodeType.values.where((b) => b.name.toLowerCase() == (json['barcodeType'] as String? ?? '').toLowerCase()).firstOrNull ?? BarcodeType.code128,
|
|
showText: json['showText'] as bool? ?? true,
|
|
borderWidthMm: (json['borderWidthMm'] as num?)?.toDouble() ?? 0.0,
|
|
paddingMm: (json['paddingMm'] as num?)?.toDouble() ?? 0.5,
|
|
);
|
|
}
|
|
}
|
|
|
|
class QrCodeElement extends LabelElement {
|
|
final String valueType; // "static" or "dynamic"
|
|
final String staticValue;
|
|
final String source; // "product"
|
|
final String fieldKey;
|
|
|
|
const QrCodeElement({
|
|
required super.id,
|
|
required super.xMm,
|
|
required super.yMm,
|
|
required super.widthMm,
|
|
required super.heightMm,
|
|
super.rotation = 0.0,
|
|
super.isLocked = false,
|
|
super.zIndex = 0,
|
|
this.valueType = 'dynamic',
|
|
this.staticValue = 'https://kifi.app',
|
|
this.source = 'product',
|
|
this.fieldKey = 'barcode',
|
|
}) : super(type: ElementType.qrCode);
|
|
|
|
String get placeholder => valueType == 'dynamic' ? '{{$source.$fieldKey}}' : staticValue;
|
|
|
|
@override
|
|
QrCodeElement copyWithPosition({
|
|
double? xMm,
|
|
double? yMm,
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? rotation,
|
|
bool? isLocked,
|
|
int? zIndex,
|
|
String? valueType,
|
|
String? staticValue,
|
|
String? source,
|
|
String? fieldKey,
|
|
}) {
|
|
return QrCodeElement(
|
|
id: id,
|
|
xMm: xMm ?? this.xMm,
|
|
yMm: yMm ?? this.yMm,
|
|
widthMm: widthMm ?? this.widthMm,
|
|
heightMm: heightMm ?? this.heightMm,
|
|
rotation: rotation ?? this.rotation,
|
|
isLocked: isLocked ?? this.isLocked,
|
|
zIndex: zIndex ?? this.zIndex,
|
|
valueType: valueType ?? this.valueType,
|
|
staticValue: staticValue ?? this.staticValue,
|
|
source: source ?? this.source,
|
|
fieldKey: fieldKey ?? this.fieldKey,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'type': 'qrCode',
|
|
'xMm': xMm,
|
|
'yMm': yMm,
|
|
'widthMm': widthMm,
|
|
'heightMm': heightMm,
|
|
'rotation': rotation,
|
|
'isLocked': isLocked,
|
|
'zIndex': zIndex,
|
|
'valueType': valueType,
|
|
'staticValue': staticValue,
|
|
'source': source,
|
|
'fieldKey': fieldKey,
|
|
'placeholder': placeholder,
|
|
};
|
|
|
|
factory QrCodeElement.fromJson(Map<String, dynamic> json) {
|
|
return QrCodeElement(
|
|
id: json['id'] as String,
|
|
xMm: (json['xMm'] as num?)?.toDouble() ?? 0.0,
|
|
yMm: (json['yMm'] as num?)?.toDouble() ?? 0.0,
|
|
widthMm: (json['widthMm'] as num?)?.toDouble() ?? 12.0,
|
|
heightMm: (json['heightMm'] as num?)?.toDouble() ?? 12.0,
|
|
rotation: (json['rotation'] as num?)?.toDouble() ?? 0.0,
|
|
isLocked: json['isLocked'] as bool? ?? false,
|
|
zIndex: json['zIndex'] as int? ?? 0,
|
|
valueType: json['valueType'] as String? ?? 'dynamic',
|
|
staticValue: json['staticValue'] as String? ?? 'https://kifi.app',
|
|
source: json['source'] as String? ?? 'product',
|
|
fieldKey: json['fieldKey'] as String? ?? json['field'] as String? ?? 'barcode',
|
|
);
|
|
}
|
|
}
|
|
|
|
class RectangleElement extends LabelElement {
|
|
@override
|
|
final double borderWidthMm;
|
|
final bool isFilled;
|
|
final double cornerRadiusMm;
|
|
|
|
const RectangleElement({
|
|
required super.id,
|
|
required super.xMm,
|
|
required super.yMm,
|
|
required super.widthMm,
|
|
required super.heightMm,
|
|
super.rotation = 0.0,
|
|
super.isLocked = false,
|
|
super.zIndex = 0,
|
|
this.borderWidthMm = 0.3,
|
|
this.isFilled = false,
|
|
this.cornerRadiusMm = 0.0,
|
|
}) : super(type: ElementType.rectangle);
|
|
|
|
@override
|
|
RectangleElement copyWithPosition({
|
|
double? xMm,
|
|
double? yMm,
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? rotation,
|
|
bool? isLocked,
|
|
int? zIndex,
|
|
double? borderWidthMm,
|
|
bool? isFilled,
|
|
double? cornerRadiusMm,
|
|
}) {
|
|
return RectangleElement(
|
|
id: id,
|
|
xMm: xMm ?? this.xMm,
|
|
yMm: yMm ?? this.yMm,
|
|
widthMm: widthMm ?? this.widthMm,
|
|
heightMm: heightMm ?? this.heightMm,
|
|
rotation: rotation ?? this.rotation,
|
|
isLocked: isLocked ?? this.isLocked,
|
|
zIndex: zIndex ?? this.zIndex,
|
|
borderWidthMm: borderWidthMm ?? this.borderWidthMm,
|
|
isFilled: isFilled ?? this.isFilled,
|
|
cornerRadiusMm: cornerRadiusMm ?? this.cornerRadiusMm,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'type': 'rectangle',
|
|
'xMm': xMm,
|
|
'yMm': yMm,
|
|
'widthMm': widthMm,
|
|
'heightMm': heightMm,
|
|
'rotation': rotation,
|
|
'isLocked': isLocked,
|
|
'zIndex': zIndex,
|
|
'borderWidthMm': borderWidthMm,
|
|
'isFilled': isFilled,
|
|
'cornerRadiusMm': cornerRadiusMm,
|
|
};
|
|
|
|
factory RectangleElement.fromJson(Map<String, dynamic> json) {
|
|
return RectangleElement(
|
|
id: json['id'] as String,
|
|
xMm: (json['xMm'] as num?)?.toDouble() ?? 0.0,
|
|
yMm: (json['yMm'] as num?)?.toDouble() ?? 0.0,
|
|
widthMm: (json['widthMm'] as num?)?.toDouble() ?? 20.0,
|
|
heightMm: (json['heightMm'] as num?)?.toDouble() ?? 10.0,
|
|
rotation: (json['rotation'] as num?)?.toDouble() ?? 0.0,
|
|
isLocked: json['isLocked'] as bool? ?? false,
|
|
zIndex: json['zIndex'] as int? ?? 0,
|
|
borderWidthMm: (json['borderWidthMm'] as num?)?.toDouble() ?? 0.3,
|
|
isFilled: json['isFilled'] as bool? ?? false,
|
|
cornerRadiusMm: (json['cornerRadiusMm'] as num?)?.toDouble() ?? 0.0,
|
|
);
|
|
}
|
|
}
|
|
|
|
class LineElement extends LabelElement {
|
|
final double thicknessMm;
|
|
final bool isVertical;
|
|
|
|
const LineElement({
|
|
required super.id,
|
|
required super.xMm,
|
|
required super.yMm,
|
|
required super.widthMm,
|
|
required super.heightMm,
|
|
super.rotation = 0.0,
|
|
super.isLocked = false,
|
|
super.zIndex = 0,
|
|
this.thicknessMm = 0.3,
|
|
this.isVertical = false,
|
|
}) : super(type: ElementType.line);
|
|
|
|
@override
|
|
LineElement copyWithPosition({
|
|
double? xMm,
|
|
double? yMm,
|
|
double? widthMm,
|
|
double? heightMm,
|
|
double? rotation,
|
|
bool? isLocked,
|
|
int? zIndex,
|
|
double? thicknessMm,
|
|
bool? isVertical,
|
|
}) {
|
|
return LineElement(
|
|
id: id,
|
|
xMm: xMm ?? this.xMm,
|
|
yMm: yMm ?? this.yMm,
|
|
widthMm: widthMm ?? this.widthMm,
|
|
heightMm: heightMm ?? this.heightMm,
|
|
rotation: rotation ?? this.rotation,
|
|
isLocked: isLocked ?? this.isLocked,
|
|
zIndex: zIndex ?? this.zIndex,
|
|
thicknessMm: thicknessMm ?? this.thicknessMm,
|
|
isVertical: isVertical ?? this.isVertical,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'type': 'line',
|
|
'xMm': xMm,
|
|
'yMm': yMm,
|
|
'widthMm': widthMm,
|
|
'heightMm': heightMm,
|
|
'rotation': rotation,
|
|
'isLocked': isLocked,
|
|
'zIndex': zIndex,
|
|
'thicknessMm': thicknessMm,
|
|
'isVertical': isVertical,
|
|
};
|
|
|
|
factory LineElement.fromJson(Map<String, dynamic> json) {
|
|
return LineElement(
|
|
id: json['id'] as String,
|
|
xMm: (json['xMm'] as num?)?.toDouble() ?? 0.0,
|
|
yMm: (json['yMm'] as num?)?.toDouble() ?? 0.0,
|
|
widthMm: (json['widthMm'] as num?)?.toDouble() ?? 20.0,
|
|
heightMm: (json['heightMm'] as num?)?.toDouble() ?? 1.0,
|
|
rotation: (json['rotation'] as num?)?.toDouble() ?? 0.0,
|
|
isLocked: json['isLocked'] as bool? ?? false,
|
|
zIndex: json['zIndex'] as int? ?? 0,
|
|
thicknessMm: (json['thicknessMm'] as num?)?.toDouble() ?? 0.3,
|
|
isVertical: json['isVertical'] as bool? ?? false,
|
|
);
|
|
}
|
|
}
|
|
|
|
class LabelDocument {
|
|
final String id;
|
|
final String name;
|
|
final String? description;
|
|
final int version;
|
|
final LabelConfiguration config;
|
|
final List<LabelElement> elements;
|
|
|
|
const LabelDocument({
|
|
required this.id,
|
|
required this.name,
|
|
this.description,
|
|
this.version = 1,
|
|
this.config = const LabelConfiguration(),
|
|
this.elements = const [],
|
|
});
|
|
|
|
LabelDocument copyWith({
|
|
String? id,
|
|
String? name,
|
|
String? description,
|
|
int? version,
|
|
LabelConfiguration? config,
|
|
List<LabelElement>? elements,
|
|
}) {
|
|
return LabelDocument(
|
|
id: id ?? this.id,
|
|
name: name ?? this.name,
|
|
description: description ?? this.description,
|
|
version: version ?? this.version,
|
|
config: config ?? this.config,
|
|
elements: elements ?? this.elements,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'version': version,
|
|
'name': name,
|
|
'description': description,
|
|
'label': config.toJson(),
|
|
'elements': elements.map((e) => e.toJson()).toList(),
|
|
};
|
|
|
|
String toJsonString() => jsonEncode(toJson());
|
|
|
|
factory LabelDocument.fromJson(Map<String, dynamic> json, {String? id, String? name, String? description}) {
|
|
final labelConfig = json['label'] != null
|
|
? LabelConfiguration.fromJson(json['label'] as Map<String, dynamic>)
|
|
: const LabelConfiguration();
|
|
|
|
final rawElements = json['elements'] as List<dynamic>? ?? [];
|
|
final elementsList = rawElements
|
|
.map((e) => LabelElement.fromJson(e as Map<String, dynamic>))
|
|
.toList();
|
|
|
|
return LabelDocument(
|
|
id: id ?? (json['id'] as String? ?? 'template_1'),
|
|
name: name ?? (json['name'] as String? ?? 'Untitled Template'),
|
|
description: description ?? (json['description'] as String?),
|
|
version: json['version'] as int? ?? 1,
|
|
config: labelConfig,
|
|
elements: elementsList,
|
|
);
|
|
}
|
|
|
|
static LabelDocument createDefault() {
|
|
return const LabelDocument(
|
|
id: 'default_template',
|
|
name: '50x25 Product Barcode',
|
|
description: 'Standard jewellery and product barcode label (50x25mm)',
|
|
config: LabelConfiguration(
|
|
widthMm: 50.0,
|
|
heightMm: 25.0,
|
|
dpi: 203,
|
|
),
|
|
elements: [
|
|
TextElement(
|
|
id: 'elem_brand',
|
|
xMm: 2.0,
|
|
yMm: 1.8,
|
|
widthMm: 46.0,
|
|
heightMm: 3.5,
|
|
text: 'kifi',
|
|
fontSizePt: 8.5,
|
|
isBold: true,
|
|
alignment: TextAlign.center,
|
|
),
|
|
DynamicTextElement(
|
|
id: 'elem_product_name',
|
|
xMm: 2.0,
|
|
yMm: 5.5,
|
|
widthMm: 46.0,
|
|
heightMm: 4.0,
|
|
source: 'product',
|
|
fieldKey: 'name',
|
|
fontSizePt: 8.0,
|
|
alignment: TextAlign.center,
|
|
),
|
|
BarcodeElement(
|
|
id: 'elem_barcode',
|
|
xMm: 5.0,
|
|
yMm: 10.0,
|
|
widthMm: 40.0,
|
|
heightMm: 8.0,
|
|
valueType: 'dynamic',
|
|
source: 'product',
|
|
fieldKey: 'barcode',
|
|
showText: true,
|
|
),
|
|
DynamicTextElement(
|
|
id: 'elem_price',
|
|
xMm: 2.0,
|
|
yMm: 19.5,
|
|
widthMm: 23.0,
|
|
heightMm: 3.8,
|
|
source: 'product',
|
|
fieldKey: 'sellingPrice',
|
|
prefix: '₹ ',
|
|
fontSizePt: 8.0,
|
|
isBold: true,
|
|
alignment: TextAlign.left,
|
|
),
|
|
DynamicTextElement(
|
|
id: 'elem_weight',
|
|
xMm: 26.0,
|
|
yMm: 19.5,
|
|
widthMm: 22.0,
|
|
heightMm: 3.8,
|
|
source: 'product',
|
|
fieldKey: 'weightInG',
|
|
prefix: 'Wt: ',
|
|
suffix: ' g',
|
|
fontSizePt: 7.5,
|
|
alignment: TextAlign.right,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|