import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:image/image.dart' as imglib; import '../../flutter_zxing.dart'; /// Widget to create a code from a text and barcode format class WriterWidget extends StatefulWidget { const WriterWidget({ super.key, this.text, this.format = Format.qrCode, this.height = 120, // Width is calculated from height and format ratio this.margin = 0, this.eccLevel = EccLevel.low, this.messages = const Messages(), this.onSuccess, this.onError, }); final String? text; final int format; final int height; final int margin; final EccLevel eccLevel; final Messages messages; final Function(Encode result, Uint8List? bytes)? onSuccess; final Function(String error)? onError; @override State createState() => _WriterWidgetState(); } class _WriterWidgetState extends State with TickerProviderStateMixin { final GlobalKey _formKey = GlobalKey(); final TextEditingController _textController = TextEditingController(); final TextEditingController _widthController = TextEditingController(); final TextEditingController _heightController = TextEditingController(); final TextEditingController _marginController = TextEditingController(); bool isAndroid() => Theme.of(context).platform == TargetPlatform.android; final List _supportedFormats = CodeFormat.supportedEncodeFormats; int _codeFormat = Format.qrCode; EccLevel _eccLevel = EccLevel.low; Messages get messages => widget.messages; Map get _eccTitlesMap => { EccLevel.low: messages.lowEccLevel, EccLevel.medium: messages.mediumEccLevel, EccLevel.quartile: messages.quartileEccLevel, EccLevel.high: messages.highEccLevel, }; @override void initState() { _codeFormat = widget.format; _eccLevel = widget.eccLevel; _textController.text = widget.text ?? _codeFormat.demoText; _widthController.text = (widget.height * _codeFormat.ratio).round().toString(); _heightController.text = widget.height.toString(); _marginController.text = widget.margin.toString(); super.initState(); } @override void dispose() { _textController.dispose(); _widthController.dispose(); _heightController.dispose(); _marginController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Form( key: _formKey, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ const SizedBox(height: 20), // Input multiline text TextFormField( controller: _textController, keyboardType: TextInputType.multiline, maxLines: null, maxLength: _codeFormat.maxTextLength, onChanged: (String value) { setState(() {}); }, decoration: InputDecoration( border: const OutlineInputBorder(), filled: true, labelText: messages.textLabel, counterText: '${_textController.value.text.length} / ${_codeFormat.maxTextLength}', ), validator: (String? value) { if (value?.isEmpty ?? false) { return messages.invalidText; } return null; }, ), const SizedBox(height: 20), // Format DropDown button Row( children: [ Flexible( child: DropdownButtonFormField( value: _codeFormat, decoration: InputDecoration( border: const OutlineInputBorder(), filled: true, labelText: messages.formatLabel, ), items: _supportedFormats .map((int format) => DropdownMenuItem( value: format, child: Text(zx.barcodeFormatName(format)), )) .toList(), onChanged: (int? format) { setState(() { _codeFormat = format ?? Format.qrCode; _textController.text = _codeFormat.demoText; _heightController.text = widget.height.toString(); _widthController.text = (widget.height * _codeFormat.ratio) .round() .toString(); }); }, ), ), if (_codeFormat.isSupportedEccLevel) ...[ const SizedBox(width: 10), Flexible( child: DropdownButtonFormField( value: _eccLevel, decoration: InputDecoration( border: const OutlineInputBorder(), filled: true, labelText: messages.eccLevelLabel, ), items: _eccTitlesMap.entries .map((MapEntry entry) => DropdownMenuItem( value: entry.key, child: Text(entry.value), )) .toList(), onChanged: (EccLevel? ecc) { setState(() { _eccLevel = ecc ?? EccLevel.low; }); }, ), ), ] ], ), const SizedBox(height: 20), Row( children: [ Flexible( child: TextFormField( controller: _widthController, keyboardType: TextInputType.number, decoration: InputDecoration( labelText: messages.widthLabel, ), validator: (String? value) { final int? width = int.tryParse(value ?? ''); if (width == null) { return messages.invalidWidth; } return null; }, onChanged: (String value) { // use format ratio to calculate height final int? width = int.tryParse(value); if (width != null) { final int height = (width / _codeFormat.ratio).round(); _heightController.text = height.toString(); } }, ), ), // const SizedBox(width: 8), Flexible( child: TextFormField( controller: _heightController, keyboardType: TextInputType.number, decoration: InputDecoration( labelText: messages.heightLabel, ), validator: (String? value) { final int? width = int.tryParse(value ?? ''); if (width == null) { return messages.invalidHeight; } return null; }, onChanged: (String value) { // use format ratio to calculate width final int? height = int.tryParse(value); if (height != null) { final int width = (height * _codeFormat.ratio).round(); _widthController.text = width.toString(); } }, ), ), Flexible( child: TextFormField( controller: _marginController, keyboardType: TextInputType.number, decoration: InputDecoration( labelText: messages.marginLabel, ), validator: (String? value) { final int? width = int.tryParse(value ?? ''); if (width == null) { return messages.invalidMargin; } return null; }, ), ), ], ), const SizedBox(height: 20), // Write button ElevatedButton( onPressed: createBarcode, child: Text(messages.createButton), ), const SizedBox(height: 10), ], ), ), ), ); } void createBarcode() { if (_formKey.currentState?.validate() ?? false) { _formKey.currentState?.save(); FocusScope.of(context).unfocus(); final String text = _textController.value.text; final int width = int.parse(_widthController.value.text); final int height = int.parse(_heightController.value.text); final int margin = int.parse(_marginController.value.text); final EccLevel ecc = _eccLevel; final Encode result = zx.encodeBarcode( contents: text, params: EncodeParams( format: _codeFormat, width: width, height: height, margin: margin, eccLevel: ecc, ), ); String? error; if (result.isValid && result.data != null) { try { final imglib.Image img = imglib.Image.fromBytes( width, height, result.data!, ); final Uint8List encodedBytes = Uint8List.fromList( imglib.encodeJpg(img), ); widget.onSuccess?.call(result, encodedBytes); } catch (e) { error = e.toString(); } } else { error = result.error; } if (error != null) { widget.onError?.call(error); } } } }