Files
Kifi/kifi-app/lib/features/barcode_designer/domain/label_document.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,
),
],
);
}
}