add new dependency

This commit is contained in:
otsmr 2025-12-08 00:06:11 +01:00
parent 3a25778782
commit fb66274bf7
25 changed files with 1987 additions and 2 deletions

View file

@ -11,4 +11,5 @@ optional: 71c638891ce4f2aff35c7387727989f31f9d877d
photo_view: a13ca2fc387a3fb1276126959e092c44d0029987
pointycastle: bbd8569f68a7fccbdf0b92d0b44a9219c126c8dd
qr: ff808bb3f354e6a7029ec953cbe0144a42021db6
qr_flutter: d5e7206396105d643113618290bbcc755d05f492
x25519: ecb1d357714537bba6e276ef45f093846d4beaee

View file

@ -1,5 +1,8 @@
qr:
git: https://github.com/kevmoo/qr.dart.git
qr_flutter:
git: https://github.com/theyakka/qr.flutter.git
dependencies:
qr:
git: https://github.com/kevmoo/qr.dart.git
mutex:
git: https://github.com/hoylen/dart-mutex.git

View file

@ -25,5 +25,7 @@ dependency_overrides:
path: ./dependencies/pointycastle
qr:
path: ./dependencies/qr
qr_flutter:
path: ./dependencies/qr_flutter
x25519:
path: ./dependencies/x25519

29
qr_flutter/LICENSE Normal file
View file

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, Luke Freeman.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,14 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
export 'package:qr/qr.dart';
export 'src/errors.dart';
export 'src/qr_image_view.dart';
export 'src/qr_painter.dart';
export 'src/qr_versions.dart';
export 'src/types.dart';
export 'src/validator.dart';

View file

@ -0,0 +1,48 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'package:flutter/widgets.dart';
import 'qr_versions.dart';
/// An exception that is thrown when an invalid QR code version / type is
/// requested.
class QrUnsupportedVersionException implements Exception {
/// Create a new QrUnsupportedVersionException.
factory QrUnsupportedVersionException(int providedVersion) {
final message =
'Invalid version. $providedVersion is not >= ${QrVersions.min} '
'and <= ${QrVersions.max}';
return QrUnsupportedVersionException._internal(providedVersion, message);
}
QrUnsupportedVersionException._internal(this.providedVersion, this.message);
/// The version you passed to the QR code operation.
final int providedVersion;
/// A message describing the exception state.
final String message;
@override
String toString() => 'QrUnsupportedVersionException: $message';
}
/// An exception that is thrown when something goes wrong with the
/// [ImageProvider] for the embedded image of a QrImageView or QrPainter.
class QrEmbeddedImageException implements Exception {
/// Create a new QrEmbeddedImageException.
factory QrEmbeddedImageException(String message) {
return QrEmbeddedImageException._internal(message);
}
QrEmbeddedImageException._internal(this.message);
/// A message describing the exception state.
final String message;
@override
String toString() => 'QrEmbeddedImageException: $message';
}

View file

@ -0,0 +1,55 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'package:flutter/widgets.dart';
import 'types.dart';
/// Caches painter objects so we do have to recreate them and waste expensive
/// cycles.
class PaintCache {
final List<Paint> _pixelPaints = <Paint>[];
final Map<String, Paint> _keyedPaints = <String, Paint>{};
String _cacheKey(QrCodeElement element, {FinderPatternPosition? position}) {
final posKey = position != null ? position.toString() : 'any';
return '$element:$posKey';
}
/// Save a [Paint] for the provided element and position into the cache.
void cache(
Paint paint,
QrCodeElement element, {
FinderPatternPosition? position,
}) {
if (element == QrCodeElement.codePixel) {
_pixelPaints.add(paint);
} else {
_keyedPaints[_cacheKey(element, position: position)] = paint;
}
}
/// Retrieve the first [Paint] object from the paint cache for the provided
/// element and position.
Paint? firstPaint(QrCodeElement element, {FinderPatternPosition? position}) {
return element == QrCodeElement.codePixel
? _pixelPaints.first
: _keyedPaints[_cacheKey(element, position: position)];
}
/// Retrieve all [Paint] objects from the paint cache for the provided
/// element and position. Note: Finder pattern elements can only have a max
/// one [Paint] object per position. As such they will always return a [List]
/// with a fixed size of `1`.
List<Paint?> paints(
QrCodeElement element, {
FinderPatternPosition? position,
}) {
return element == QrCodeElement.codePixel
? _pixelPaints
: <Paint?>[_keyedPaints[_cacheKey(element, position: position)]];
}
}

View file

@ -0,0 +1,336 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:qr/qr.dart';
import 'qr_painter.dart';
import 'qr_versions.dart';
import 'types.dart';
import 'validator.dart';
/// A widget that shows a QR code.
class QrImageView extends StatefulWidget {
/// Create a new QR code using the [String] data and the passed options (or
/// using the default options).
QrImageView({
required String data,
super.key,
this.size,
this.padding = const EdgeInsets.all(10.0),
this.backgroundColor = Colors.transparent,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.foregroundColor = Colors.black,
this.version = QrVersions.auto,
this.errorCorrectionLevel = QrErrorCorrectLevel.L,
this.errorStateBuilder,
this.constrainErrorBounds = true,
this.gapless = true,
this.embeddedImage,
this.embeddedImageStyle = const QrEmbeddedImageStyle(),
this.semanticsLabel = 'qr code',
this.eyeStyle = const QrEyeStyle(
eyeShape: QrEyeShape.square,
),
this.dataModuleStyle = const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
),
this.embeddedImageEmitsError = false,
this.gradient,
}) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
),
_data = data,
_qrCode = null;
/// Create a new QR code using the [QrCode] data and the passed options (or
/// using the default options).
QrImageView.withQr({
required QrCode qr,
super.key,
this.size,
this.padding = const EdgeInsets.all(10.0),
this.backgroundColor = Colors.transparent,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.foregroundColor = Colors.black,
this.version = QrVersions.auto,
this.errorCorrectionLevel = QrErrorCorrectLevel.L,
this.errorStateBuilder,
this.constrainErrorBounds = true,
this.gapless = true,
this.embeddedImage,
this.embeddedImageStyle = const QrEmbeddedImageStyle(),
this.semanticsLabel = 'qr code',
this.eyeStyle = const QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Colors.black,
),
this.dataModuleStyle = const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Colors.black,
),
this.embeddedImageEmitsError = false,
this.gradient,
}) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
),
_data = null,
_qrCode = qr;
// The data passed to the widget
final String? _data;
// The QR code data passed to the widget
final QrCode? _qrCode;
/// The background color of the final QR code widget.
final Color backgroundColor;
/// The foreground color of the final QR code widget.
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
final Color foregroundColor;
/// The gradient for all (dataModule and eye)
final Gradient? gradient;
/// The QR code version to use.
final int version;
/// The QR code error correction level to use.
final int errorCorrectionLevel;
/// The external padding between the edge of the widget and the content.
final EdgeInsets padding;
/// The intended size of the widget.
final double? size;
/// The callback that is executed in the event of an error so that you can
/// interrogate the exception and construct an alternative view to present
/// to your user.
final QrErrorBuilder? errorStateBuilder;
/// If `true` then the error widget will be constrained to the boundary of the
/// QR widget if it had been valid. If `false` the error widget will grow to
/// the size it needs. If the error widget is allowed to grow, your layout may
/// jump around (depending on specifics).
///
/// NOTE: Setting a [size] value will override this setting and both the
/// content widget and error widget will adhere to the size value.
final bool constrainErrorBounds;
/// If set to false, each of the squares in the QR code will have a small
/// gap. Default is true.
final bool gapless;
/// The image data to embed (as an overlay) in the QR code. The image will
/// be added to the center of the QR code.
final ImageProvider? embeddedImage;
/// Styling options for the image overlay.
final QrEmbeddedImageStyle embeddedImageStyle;
/// If set to true and there is an error loading the embedded image, the
/// [errorStateBuilder] callback will be called (if it is defined). If false,
/// the widget will ignore the embedded image and just display the QR code.
/// The default is false.
final bool embeddedImageEmitsError;
/// [semanticsLabel] will be used by screen readers to describe the content of
/// the qr code.
/// Default is 'qr code'.
final String semanticsLabel;
/// Styling option for QR Eye ball and frame.
final QrEyeStyle eyeStyle;
/// Styling option for QR data module.
final QrDataModuleStyle dataModuleStyle;
@override
State<QrImageView> createState() => _QrImageViewState();
}
class _QrImageViewState extends State<QrImageView> {
/// The QR code string data.
QrCode? _qr;
/// The current validation status.
late QrValidationResult _validationResult;
@override
Widget build(BuildContext context) {
if (widget._data != null) {
_validationResult = QrValidator.validate(
data: widget._data!,
version: widget.version,
errorCorrectionLevel: widget.errorCorrectionLevel,
);
_qr = _validationResult.isValid ? _validationResult.qrCode : null;
} else if (widget._qrCode != null) {
_qr = widget._qrCode;
_validationResult =
QrValidationResult(status: QrValidationStatus.valid, qrCode: _qr);
}
return LayoutBuilder(
builder: (context, constraints) {
// validation failed, show an error state widget if builder is present.
if (!_validationResult.isValid) {
return _errorWidget(context, constraints, _validationResult.error);
}
// no error, build the regular widget
final widgetSize =
widget.size ?? constraints.biggest.shortestSide;
if (widget.embeddedImage != null) {
// if requesting to embed an image then we need to load via a
// FutureBuilder because the image provider will be async.
return FutureBuilder<ui.Image>(
future: _loadQrImage(context, widget.embeddedImageStyle),
builder: (ctx, snapshot) {
if (snapshot.error != null) {
debugPrint('snapshot error: ${snapshot.error}');
return widget.embeddedImageEmitsError
? _errorWidget(context, constraints, snapshot.error)
: _qrWidget(null, widgetSize);
}
if (snapshot.hasData) {
debugPrint('loaded image');
final loadedImage = snapshot.data;
return _qrWidget(loadedImage, widgetSize);
} else {
return Container();
}
},
);
} else {
return _qrWidget(null, widgetSize);
}
},
);
}
Widget _qrWidget(ui.Image? image, double edgeLength) {
final painter = QrPainter.withQr(
qr: _qr!,
// ignore: deprecated_member_use_from_same_package
color: widget.foregroundColor,
gapless: widget.gapless,
embeddedImageStyle: widget.embeddedImageStyle,
embeddedImage: image,
eyeStyle: widget.eyeStyle,
dataModuleStyle: widget.dataModuleStyle,
gradient: widget.gradient
);
return _QrContentView(
edgeLength: edgeLength,
backgroundColor: widget.backgroundColor,
padding: widget.padding,
semanticsLabel: widget.semanticsLabel,
child: CustomPaint(painter: painter),
);
}
Widget _errorWidget(
BuildContext context,
BoxConstraints constraints,
Object? error,
) {
final errorWidget = widget.errorStateBuilder == null
? Container()
: widget.errorStateBuilder!(context, error);
final errorSideLength = widget.constrainErrorBounds
? widget.size ?? constraints.biggest.shortestSide
: constraints.biggest.longestSide;
return _QrContentView(
edgeLength: errorSideLength,
backgroundColor: widget.backgroundColor,
padding: widget.padding,
semanticsLabel: widget.semanticsLabel,
child: errorWidget,
);
}
late ImageStreamListener streamListener;
Future<ui.Image> _loadQrImage(
BuildContext buildContext,
QrEmbeddedImageStyle? style,
) {
if (style != null) {}
final mq = MediaQuery.of(buildContext);
final completer = Completer<ui.Image>();
final stream = widget.embeddedImage!.resolve(
ImageConfiguration(
devicePixelRatio: mq.devicePixelRatio,
),
);
streamListener = ImageStreamListener(
(info, err) {
stream.removeListener(streamListener);
completer.complete(info.image);
},
onError: (err, _) {
stream.removeListener(streamListener);
completer.completeError(err);
},
);
stream.addListener(streamListener);
return completer.future;
}
}
/// A function type to be called when any form of error occurs while
/// painting a [QrImageView].
typedef QrErrorBuilder = Widget Function(BuildContext context, Object? error);
class _QrContentView extends StatelessWidget {
const _QrContentView({
required this.edgeLength,
required this.child,
this.backgroundColor,
this.padding,
this.semanticsLabel,
});
/// The length of both edges (because it has to be a square).
final double edgeLength;
/// The background color of the containing widget.
final Color? backgroundColor;
/// The padding that surrounds the child widget.
final EdgeInsets? padding;
/// The child widget.
final Widget child;
/// [semanticsLabel] will be used by screen readers to describe the content of
/// the qr code.
final String? semanticsLabel;
@override
Widget build(BuildContext context) {
return Semantics(
label: semanticsLabel,
child: Container(
width: edgeLength,
height: edgeLength,
color: backgroundColor,
child: Padding(
padding: padding!,
child: child,
),
),
);
}
}

View file

@ -0,0 +1,694 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qr/qr.dart';
import 'errors.dart';
import 'paint_cache.dart';
import 'qr_versions.dart';
import 'types.dart';
import 'validator.dart';
// ignore_for_file: deprecated_member_use_from_same_package
const int _finderPatternLimit = 7;
// default colors for the qr code pixels
const Color _qrDefaultColor = Color(0xff000000);
const Color _qrDefaultEmptyColor = Color(0x00ffffff);
/// A [CustomPainter] object that you can use to paint a QR code.
class QrPainter extends CustomPainter {
/// Create a new QRPainter with passed options (or defaults).
QrPainter({
required String data,
required this.version,
this.errorCorrectionLevel = QrErrorCorrectLevel.L,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.color = _qrDefaultColor,
@Deprecated(
'You should use the background color value of your container widget',
)
this.emptyColor = _qrDefaultEmptyColor,
this.gapless = false,
this.embeddedImage,
this.embeddedImageStyle = const QrEmbeddedImageStyle(),
this.eyeStyle = const QrEyeStyle(),
this.dataModuleStyle = const QrDataModuleStyle(),
this.gradient,
}) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
) {
_init(data);
}
/// Create a new QrPainter with a pre-validated/created [QrCode] object. This
/// constructor is useful when you have a custom validation / error handling
/// flow or for when you need to pre-validate the QR data.
QrPainter.withQr({
required QrCode qr,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.color = _qrDefaultColor,
@Deprecated(
'You should use the background color value of your container widget',
)
this.emptyColor = _qrDefaultEmptyColor,
this.gapless = false,
this.embeddedImage,
this.embeddedImageStyle = const QrEmbeddedImageStyle(),
this.eyeStyle = const QrEyeStyle(),
this.dataModuleStyle = const QrDataModuleStyle(),
this.gradient,
}) : _qr = qr,
version = qr.typeNumber,
errorCorrectionLevel = qr.errorCorrectLevel {
_calcVersion = version;
_initPaints();
}
/// The QR code version.
final int version; // the qr code version
/// The error correction level of the QR code.
final int errorCorrectionLevel; // the qr code error correction level
/// The color of the squares.
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
final Color color;
/// The gradient for all (dataModuleShape, eyeShape, embeddedImageShape)
final Gradient? gradient;
/// The color of the non-squares (background).
@Deprecated(
'You should use the background color value of your container widget')
final Color emptyColor; // the other color
/// If set to false, the painter will leave a 1px gap between each of the
/// squares.
final bool gapless;
/// The image data to embed (as an overlay) in the QR code. The image will
/// be added to the center of the QR code.
final ui.Image? embeddedImage;
/// Styling options for the image overlay.
final QrEmbeddedImageStyle embeddedImageStyle;
/// Styling option for QR Eye ball and frame.
final QrEyeStyle eyeStyle;
/// Styling option for QR data module.
final QrDataModuleStyle dataModuleStyle;
/// The base QR code data
QrCode? _qr;
/// QR Image renderer
late QrImage _qrImage;
/// This is the version (after calculating) that we will use if the user has
/// requested the 'auto' version.
late final int _calcVersion;
/// The size of the 'gap' between the pixels
final double _gapSize = 0.25;
/// Cache for all of the [Paint] objects.
final PaintCache _paintCache = PaintCache();
void _init(String data) {
if (!QrVersions.isSupportedVersion(version)) {
throw QrUnsupportedVersionException(version);
}
// configure and make the QR code data
final validationResult = QrValidator.validate(
data: data,
version: version,
errorCorrectionLevel: errorCorrectionLevel,
);
if (!validationResult.isValid) {
throw validationResult.error!;
}
_qr = validationResult.qrCode;
_calcVersion = _qr!.typeNumber;
_initPaints();
}
void _initPaints() {
// Initialize `QrImage` for rendering
_qrImage = QrImage(_qr!);
// Cache the pixel paint object. For now there is only one but we might
// expand it to multiple later (e.g.: different colours).
_paintCache.cache(
Paint()..style = PaintingStyle.fill,
QrCodeElement.codePixel,
);
// Cache the empty pixel paint object. Empty color is deprecated and will go
// away.
_paintCache.cache(
Paint()..style = PaintingStyle.fill,
QrCodeElement.codePixelEmpty,
);
// Cache the finder pattern painters. We'll keep one for each one in case
// we want to provide customization options later.
for (final position in FinderPatternPosition.values) {
_paintCache.cache(
Paint()..style = PaintingStyle.stroke,
QrCodeElement.finderPatternOuter,
position: position,
);
_paintCache.cache(
Paint()..style = PaintingStyle.stroke,
QrCodeElement.finderPatternInner,
position: position,
);
_paintCache.cache(
Paint()..style = PaintingStyle.fill,
QrCodeElement.finderPatternDot,
position: position,
);
}
}
@override
void paint(Canvas canvas, Size size) {
// if the widget has a zero size side then we cannot continue painting.
if (size.shortestSide == 0) {
debugPrint(
"[QR] WARN: width or height is zero. You should set a 'size' value "
'or nest this painter in a Widget that defines a non-zero size');
return;
}
final paintMetrics = _PaintMetrics(
containerSize: size.shortestSide,
moduleCount: _qr!.moduleCount,
gapSize: gapless ? 0 : _gapSize,
);
// draw the finder pattern elements
_drawFinderPatternItem(
FinderPatternPosition.topLeft,
canvas,
paintMetrics,
);
_drawFinderPatternItem(
FinderPatternPosition.bottomLeft,
canvas,
paintMetrics,
);
_drawFinderPatternItem(
FinderPatternPosition.topRight,
canvas,
paintMetrics,
);
// DEBUG: draw the inner content boundary
// final paint = Paint()..style = ui.PaintingStyle.stroke;
// paint.strokeWidth = 1;
// paint.color = const Color(0x55222222);
// canvas.drawRect(
// Rect.fromLTWH(paintMetrics.inset, paintMetrics.inset,
// paintMetrics.innerContentSize, paintMetrics.innerContentSize),
// paint);
Size? embeddedImageSize;
Offset? embeddedImagePosition;
Offset? safeAreaPosition;
Rect? safeAreaRect;
if (embeddedImage != null) {
final originalSize = Size(
embeddedImage!.width.toDouble(),
embeddedImage!.height.toDouble(),
);
final requestedSize = embeddedImageStyle.size;
embeddedImageSize = _scaledAspectSize(size, originalSize, requestedSize);
embeddedImagePosition = Offset(
(size.width - embeddedImageSize.width) / 2.0,
(size.height - embeddedImageSize.height) / 2.0,
);
if(embeddedImageStyle.safeArea) {
final safeAreaMultiplier = embeddedImageStyle.safeAreaMultiplier;
safeAreaPosition = Offset(
(size.width - embeddedImageSize.width * safeAreaMultiplier) / 2.0,
(size.height - embeddedImageSize.height * safeAreaMultiplier) / 2.0,
);
safeAreaRect = Rect.fromLTWH(
safeAreaPosition.dx,
safeAreaPosition.dy,
embeddedImageSize.width * safeAreaMultiplier,
embeddedImageSize.height * safeAreaMultiplier,
);
}
if(embeddedImageStyle.embeddedImageShape != EmbeddedImageShape.none) {
final color = _priorityColor(embeddedImageStyle.shapeColor);
final squareRect = Rect.fromLTWH(
embeddedImagePosition.dx,
embeddedImagePosition.dy,
embeddedImageSize.width,
embeddedImageSize.height,
);
final paint = Paint()..color = color;
switch(embeddedImageStyle.embeddedImageShape) {
case EmbeddedImageShape.square:
if(embeddedImageStyle.borderRadius > 0) {
final roundedRect = RRect.fromRectAndRadius(
squareRect,
Radius.circular(embeddedImageStyle.borderRadius),
);
canvas.drawRRect(roundedRect, paint);
} else {
canvas.drawRect(squareRect, paint);
}
break;
case EmbeddedImageShape.circle:
final roundedRect = RRect.fromRectAndRadius(squareRect,
Radius.circular(squareRect.width / 2));
canvas.drawRRect(roundedRect, paint);
break;
default:
break;
}
}
}
final gap = !gapless ? _gapSize : 0;
// get the painters for the pixel information
final pixelPaint = _paintCache.firstPaint(QrCodeElement.codePixel);
pixelPaint!.color = _priorityColor(dataModuleStyle.color);
final emptyPixelPaint = _paintCache
.firstPaint(QrCodeElement.codePixelEmpty);
emptyPixelPaint!.color = _qrDefaultEmptyColor;
final borderRadius = Radius
.circular(dataModuleStyle.borderRadius);
final outsideBorderRadius = Radius
.circular(dataModuleStyle.outsideBorderRadius);
final isRoundedOutsideCorners = dataModuleStyle.roundedOutsideCorners;
for (var x = 0; x < _qr!.moduleCount; x++) {
for (var y = 0; y < _qr!.moduleCount; y++) {
// draw the finder patterns independently
if (_isFinderPatternPosition(x, y)) {
continue;
}
final isDark = _qrImage.isDark(y, x);
final paint = isDark ? pixelPaint : emptyPixelPaint;
if (!isDark && !isRoundedOutsideCorners) {
continue;
}
// paint a pixel
final squareRect = _createDataModuleRect(paintMetrics, x, y, gap);
// check safeArea
if(embeddedImageStyle.safeArea
&& safeAreaRect?.overlaps(squareRect) == true) continue;
switch(dataModuleStyle.dataModuleShape) {
case QrDataModuleShape.square:
if(dataModuleStyle.borderRadius > 0) {
// If pixel isDark == true and outside safe area
// than can't be rounded
final isDarkLeft = _isDarkOnSide(x - 1, y,
safeAreaRect, paintMetrics, gap);
final isDarkTop = _isDarkOnSide(x, y - 1,
safeAreaRect, paintMetrics, gap);
final isDarkRight = _isDarkOnSide(x + 1, y,
safeAreaRect, paintMetrics, gap);
final isDarkBottom = _isDarkOnSide(x, y + 1,
safeAreaRect, paintMetrics, gap);
if(!isDark && isRoundedOutsideCorners) {
final isDarkTopLeft = _isDarkOnSide(x - 1, y - 1,
safeAreaRect, paintMetrics, gap);;
final isDarkTopRight = _isDarkOnSide(x + 1, y - 1,
safeAreaRect, paintMetrics, gap);;
final isDarkBottomLeft = _isDarkOnSide(x - 1, y + 1,
safeAreaRect, paintMetrics, gap);;
final isDarkBottomRight = _isDarkOnSide(x + 1, y + 1,
safeAreaRect, paintMetrics, gap);;
final roundedRect = RRect.fromRectAndCorners(
squareRect,
topLeft: isDarkTop && isDarkLeft && isDarkTopLeft
? outsideBorderRadius
: Radius.zero,
topRight: isDarkTop && isDarkRight && isDarkTopRight
? outsideBorderRadius
: Radius.zero,
bottomLeft: isDarkBottom && isDarkLeft && isDarkBottomLeft
? outsideBorderRadius
: Radius.zero,
bottomRight: isDarkBottom && isDarkRight && isDarkBottomRight
? outsideBorderRadius
: Radius.zero,
);
canvas.drawPath(
Path.combine(
PathOperation.difference,
Path()..addRect(squareRect),
Path()..addRRect(roundedRect)..close(),
),
pixelPaint,
);
} else {
final roundedRect = RRect.fromRectAndCorners(
squareRect,
topLeft: isDarkTop || isDarkLeft
? Radius.zero
: borderRadius,
topRight: isDarkTop || isDarkRight
? Radius.zero
: borderRadius,
bottomLeft: isDarkBottom || isDarkLeft
? Radius.zero
: borderRadius,
bottomRight: isDarkBottom || isDarkRight
? Radius.zero
: borderRadius,
);
canvas.drawRRect(roundedRect, paint);
}
} else {
canvas.drawRect(squareRect, paint);
}
break;
default:
final roundedRect = RRect.fromRectAndRadius(squareRect,
Radius.circular(squareRect.width / 2));
canvas.drawRRect(roundedRect, paint);
break;
}
}
}
// set gradient for all
if(gradient != null) {
final paintGradient = Paint();
paintGradient.shader = gradient!
.createShader(Rect.fromLTWH(0, 0, size.width, size.height));
paintGradient.blendMode = BlendMode.values[12];
canvas.drawRect(
Rect.fromLTWH(
paintMetrics.inset,
paintMetrics.inset,
paintMetrics.innerContentSize,
paintMetrics.innerContentSize,
),
paintGradient,
);
}
// draw the image overlay.
if (embeddedImage != null) {
_drawImageOverlay(
canvas,
embeddedImagePosition!,
embeddedImageSize!,
embeddedImageStyle,
);
}
}
bool _isDarkOnSide(int x, int y, Rect? safeAreaRect,
_PaintMetrics paintMetrics, num gap,) {
final maxIndexPixel = _qrImage.moduleCount - 1;
final xIsContains = x >= 0 && x <= maxIndexPixel;
final yIsContains = y >= 0 && y <= maxIndexPixel;
return xIsContains && yIsContains
? _qrImage.isDark(y, x)
&& !(safeAreaRect?.overlaps(
_createDataModuleRect(paintMetrics, x, y, gap))
?? false)
: false;
}
Rect _createDataModuleRect(_PaintMetrics paintMetrics, int x, int y, num gap) {
final left = paintMetrics.inset + (x * (paintMetrics.pixelSize + gap));
final top = paintMetrics.inset + (y * (paintMetrics.pixelSize + gap));
var pixelHTweak = 0.0;
var pixelVTweak = 0.0;
if (gapless && _hasAdjacentHorizontalPixel(x, y, _qr!.moduleCount)) {
pixelHTweak = 0.5;
}
if (gapless && _hasAdjacentVerticalPixel(x, y, _qr!.moduleCount)) {
pixelVTweak = 0.5;
}
return Rect.fromLTWH(
left,
top,
paintMetrics.pixelSize + pixelHTweak,
paintMetrics.pixelSize + pixelVTweak,
);
}
bool _hasAdjacentVerticalPixel(int x, int y, int moduleCount) {
if (y + 1 >= moduleCount) {
return false;
}
return _qrImage.isDark(y + 1, x);
}
bool _hasAdjacentHorizontalPixel(int x, int y, int moduleCount) {
if (x + 1 >= moduleCount) {
return false;
}
return _qrImage.isDark(y, x + 1);
}
bool _isFinderPatternPosition(int x, int y) {
final isTopLeft = y < _finderPatternLimit && x < _finderPatternLimit;
final isBottomLeft = y < _finderPatternLimit &&
(x >= _qr!.moduleCount - _finderPatternLimit);
final isTopRight = y >= _qr!.moduleCount - _finderPatternLimit &&
(x < _finderPatternLimit);
return isTopLeft || isBottomLeft || isTopRight;
}
void _drawFinderPatternItem(
FinderPatternPosition position,
Canvas canvas,
_PaintMetrics metrics,
) {
final totalGap = (_finderPatternLimit - 1) * metrics.gapSize;
final radius =
((_finderPatternLimit * metrics.pixelSize) + totalGap) -
metrics.pixelSize;
final strokeAdjust = metrics.pixelSize / 2.0;
final edgePos =
(metrics.inset + metrics.innerContentSize) - (radius + strokeAdjust);
Offset offset;
if (position == FinderPatternPosition.topLeft) {
offset =
Offset(metrics.inset + strokeAdjust, metrics.inset + strokeAdjust);
} else if (position == FinderPatternPosition.bottomLeft) {
offset = Offset(metrics.inset + strokeAdjust, edgePos);
} else {
offset = Offset(edgePos, metrics.inset + strokeAdjust);
}
// configure the paints
final outerPaint = _paintCache.firstPaint(
QrCodeElement.finderPatternOuter,
position: position,
)!;
final color = _priorityColor(eyeStyle.color);
outerPaint.strokeWidth = metrics.pixelSize;
outerPaint.color = color;
final innerPaint = _paintCache
.firstPaint(QrCodeElement.finderPatternInner, position: position)!;
innerPaint.strokeWidth = metrics.pixelSize;
innerPaint.color = emptyColor;
final dotPaint = _paintCache.firstPaint(
QrCodeElement.finderPatternDot,
position: position,
);
dotPaint!.color = color;
final outerRect =
Rect.fromLTWH(offset.dx, offset.dy, radius, radius);
final innerRadius = radius - (2 * metrics.pixelSize);
final innerRect = Rect.fromLTWH(
offset.dx + metrics.pixelSize,
offset.dy + metrics.pixelSize,
innerRadius,
innerRadius,
);
final gap = metrics.pixelSize * 2;
final dotSize = radius - gap - (2 * strokeAdjust);
final dotRect = Rect.fromLTWH(
offset.dx + metrics.pixelSize + strokeAdjust,
offset.dy + metrics.pixelSize + strokeAdjust,
dotSize,
dotSize,
);
switch(eyeStyle.eyeShape) {
case QrEyeShape.square:
if(eyeStyle.borderRadius > 0) {
final roundedOuterStrokeRect = RRect.fromRectAndRadius(
outerRect, Radius.circular(eyeStyle.borderRadius));
canvas.drawRRect(roundedOuterStrokeRect, outerPaint);
canvas.drawRect(innerRect, innerPaint);
final roundedDotStrokeRect = RRect.fromRectAndRadius(
dotRect, Radius.circular(eyeStyle.borderRadius / 2));
canvas.drawRRect(roundedDotStrokeRect, dotPaint);
} else {
canvas.drawRect(outerRect, outerPaint);
canvas.drawRect(innerRect, innerPaint);
canvas.drawRect(dotRect, dotPaint);
}
break;
default:
final roundedOuterStrokeRect =
RRect.fromRectAndRadius(outerRect, Radius.circular(radius));
canvas.drawRRect(roundedOuterStrokeRect, outerPaint);
final roundedInnerStrokeRect =
RRect.fromRectAndRadius(outerRect, Radius.circular(innerRadius));
canvas.drawRRect(roundedInnerStrokeRect, innerPaint);
final roundedDotStrokeRect =
RRect.fromRectAndRadius(dotRect, Radius.circular(dotSize));
canvas.drawRRect(roundedDotStrokeRect, dotPaint);
break;
}
}
bool _hasOneNonZeroSide(Size size) => size.longestSide > 0;
Size _scaledAspectSize(
Size widgetSize,
Size originalSize,
Size? requestedSize,
) {
if (requestedSize != null && !requestedSize.isEmpty) {
return requestedSize;
} else if (requestedSize != null && _hasOneNonZeroSide(requestedSize)) {
final maxSide = requestedSize.longestSide;
final ratio = maxSide / originalSize.longestSide;
return Size(ratio * originalSize.width, ratio * originalSize.height);
} else {
final maxSide = 0.25 * widgetSize.shortestSide;
final ratio = maxSide / originalSize.longestSide;
return Size(ratio * originalSize.width, ratio * originalSize.height);
}
}
void _drawImageOverlay(
Canvas canvas,
Offset position,
Size size,
QrEmbeddedImageStyle? style,
) {
final paint = Paint()
..isAntiAlias = true
..filterQuality = FilterQuality.high;
if (style != null) {
if (style.color != null) {
paint.colorFilter = ColorFilter.mode(style.color!, BlendMode.srcATop);
}
}
final srcSize = Size(
embeddedImage!.width.toDouble(),
embeddedImage!.height.toDouble(),
);
final src = Alignment.center.inscribe(srcSize, Offset.zero & srcSize);
final dst = Alignment.center.inscribe(size, position & size);
canvas.drawImageRect(embeddedImage!, src, dst, paint);
}
/// if [gradient] != null, then only black [_qrDefaultColor],
/// needed for gradient
/// else [color] or [QrPainter.color]
Color _priorityColor(Color? color) =>
gradient != null ? _qrDefaultColor : color ?? this.color;
@override
bool shouldRepaint(CustomPainter oldPainter) {
if (oldPainter is QrPainter) {
return errorCorrectionLevel != oldPainter.errorCorrectionLevel ||
_calcVersion != oldPainter._calcVersion ||
_qr != oldPainter._qr ||
gapless != oldPainter.gapless ||
embeddedImage != oldPainter.embeddedImage ||
embeddedImageStyle != oldPainter.embeddedImageStyle ||
eyeStyle != oldPainter.eyeStyle ||
dataModuleStyle != oldPainter.dataModuleStyle;
}
return true;
}
/// Returns a [ui.Picture] object containing the QR code data.
ui.Picture toPicture(double size) {
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
paint(canvas, Size(size, size));
return recorder.endRecording();
}
/// Returns the raw QR code [ui.Image] object.
Future<ui.Image> toImage(double size) {
return toPicture(size).toImage(size.toInt(), size.toInt());
}
/// Returns the raw QR code image byte data.
Future<ByteData?> toImageData(
double size, {
ui.ImageByteFormat format = ui.ImageByteFormat.png,
}) async {
final image = await toImage(size);
return image.toByteData(format: format);
}
}
class _PaintMetrics {
_PaintMetrics({
required this.containerSize,
required this.gapSize,
required this.moduleCount,
}) {
_calculateMetrics();
}
final int moduleCount;
final double containerSize;
final double gapSize;
late final double _pixelSize;
double get pixelSize => _pixelSize;
late final double _innerContentSize;
double get innerContentSize => _innerContentSize;
late final double _inset;
double get inset => _inset;
void _calculateMetrics() {
final gapTotal = (moduleCount - 1) * gapSize;
final pixelSize = (containerSize - gapTotal) / moduleCount;
_pixelSize = (pixelSize * 2).roundToDouble() / 2;
_innerContentSize = (_pixelSize * moduleCount) + gapTotal;
_inset = (containerSize - _innerContentSize) / 2;
}
}

View file

@ -0,0 +1,23 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
/// This class only contains special version codes. QR codes support version
/// numbers from 1-40 and you should just use the numeric version directly.
class QrVersions {
/// Automatically determine the QR code version based on input and an
/// error correction level.
static const int auto = -1;
/// The minimum supported version code.
static const int min = 1;
/// The maximum supported version code.
static const int max = 40;
/// Checks to see if the supplied version is a valid QR code version
static bool isSupportedVersion(int version) =>
version == auto || (version >= min && version <= max);
}

View file

@ -0,0 +1,208 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'package:flutter/widgets.dart';
import 'dart:ui';
/// Represents a specific element / part of a QR code. This is used to isolate
/// the different parts so that we can style and modify specific parts
/// independently.
enum QrCodeElement {
/// The 'stroke' / outer square of the QR code finder pattern element.
finderPatternOuter,
/// The inner/in-between square of the QR code finder pattern element.
finderPatternInner,
/// The "dot" square of the QR code finder pattern element.
finderPatternDot,
/// The individual pixels of the QR code
codePixel,
/// The "empty" pixels of the QR code
codePixelEmpty,
}
/// Enumeration representing the three finder pattern (square 'eye') locations.
enum FinderPatternPosition {
/// The top left position.
topLeft,
/// The top right position.
topRight,
/// The bottom left position.
bottomLeft,
}
/// Enumeration representing the finder pattern eye's shape.
enum QrEyeShape {
/// Use square eye frame.
square,
/// Use circular eye frame.
circle,
}
/// Enumeration representing the shape of Data modules inside QR.
enum QrDataModuleShape {
/// Use square dots.
square,
/// Use circular dots.
circle,
}
/// Enumeration representing the shape behind embedded picture
enum EmbeddedImageShape {
/// Disable
none,
/// Use square.
square,
/// Use circular.
circle,
}
/// Styling options for finder pattern eye.
@immutable
class QrEyeStyle {
/// Create a new set of styling options for QR Eye.
const QrEyeStyle({
this.eyeShape = QrEyeShape.square,
this.color,
this.borderRadius = 0,
});
/// Eye shape.
final QrEyeShape eyeShape;
/// Color to tint the eye.
final Color? color;
/// Border radius
final double borderRadius;
@override
int get hashCode => eyeShape.hashCode ^ color.hashCode;
@override
bool operator ==(Object other) {
if (other is QrEyeStyle) {
return eyeShape == other.eyeShape && color == other.color;
}
return false;
}
}
/// Styling options for data module.
@immutable
class QrDataModuleStyle {
/// Create a new set of styling options for data modules.
const QrDataModuleStyle({
this.dataModuleShape = QrDataModuleShape.square,
this.color,
this.borderRadius = 0,
this.roundedOutsideCorners = false,
double? outsideBorderRadius,
}) : _outsideBorderRadius = outsideBorderRadius;
/// Data module shape.
final QrDataModuleShape dataModuleShape;
/// Color to tint the data modules.
final Color? color;
/// Border radius
final double borderRadius;
/// Only [QrDataModuleShape.square]
/// true for rounded outside corners
final bool roundedOutsideCorners;
/// Only [QrDataModuleShape.square]
/// Border radius for outside corners
final double? _outsideBorderRadius;
/// if [roundedOutsideCorners] == true, then by default use [borderRadius]
/// [_outsideBorderRadius] <= [borderRadius]
/// Get border radius for outside corners
double get outsideBorderRadius {
if(roundedOutsideCorners) {
return _outsideBorderRadius != null
&& _outsideBorderRadius! < borderRadius
? _outsideBorderRadius! : borderRadius;
}
return 0;
}
@override
int get hashCode => dataModuleShape.hashCode ^ color.hashCode;
@override
bool operator ==(Object other) {
if (other is QrDataModuleStyle) {
return dataModuleShape == other.dataModuleShape && color == other.color;
}
return false;
}
}
/// Styling options for any embedded image overlay
@immutable
class QrEmbeddedImageStyle {
/// Create a new set of styling options.
const QrEmbeddedImageStyle({
this.size,
this.color,
this.safeArea = false,
this.safeAreaMultiplier = 1,
this.embeddedImageShape = EmbeddedImageShape.none,
this.shapeColor,
this.borderRadius = 0,
});
/// The size of the image. If one dimension is zero then the other dimension
/// will be used to scale the zero dimension based on the original image
/// size.
final Size? size;
/// Color to tint the image.
final Color? color;
/// Hide data modules behind embedded image.
/// Data modules are not displayed inside area
final bool safeArea;
/// Safe area size multiplier.
final double safeAreaMultiplier;
/// Shape background embedded image
final EmbeddedImageShape embeddedImageShape;
/// Border radius shape
final double borderRadius;
/// Color background
final Color? shapeColor;
/// Check to see if the style object has a non-null, non-zero size.
bool get hasDefinedSize => size != null && size!.longestSide > 0;
@override
int get hashCode => size.hashCode ^ color.hashCode;
@override
bool operator ==(Object other) {
if (other is QrEmbeddedImageStyle) {
return size == other.size && color == other.color;
}
return false;
}
}

View file

@ -0,0 +1,80 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'package:qr/qr.dart';
import 'qr_versions.dart';
/// A utility class for validating and pre-rendering QR code data.
class QrValidator {
/// Attempt to parse / generate the QR code data and check for any errors. The
/// resulting [QrValidationResult] object will hold the status of the QR code
/// as well as the generated QR code data.
static QrValidationResult validate({
required String data,
int version = QrVersions.auto,
int errorCorrectionLevel = QrErrorCorrectLevel.L,
}) {
late final QrCode qrCode;
try {
if (version != QrVersions.auto) {
qrCode = QrCode(version, errorCorrectionLevel);
qrCode.addData(data);
} else {
qrCode = QrCode.fromData(
data: data,
errorCorrectLevel: errorCorrectionLevel,
);
}
return QrValidationResult(
status: QrValidationStatus.valid,
qrCode: qrCode,
);
} on InputTooLongException catch (title) {
return QrValidationResult(
status: QrValidationStatus.contentTooLong,
error: title,
);
} on Exception catch (ex) {
return QrValidationResult(status: QrValidationStatus.error, error: ex);
}
}
}
/// Captures the status or a QR code validation operations, as well as the
/// rendered and validated data / object so that it can be used in any
/// secondary operations (to avoid re-rendering). It also keeps any exception
/// that was thrown.
class QrValidationResult {
/// Create a new validation result instance.
QrValidationResult({required this.status, this.qrCode, this.error});
/// The status of the validation operation.
QrValidationStatus status;
/// The rendered QR code data / object.
QrCode? qrCode;
/// The exception that was thrown in the event of a non-valid result (if any).
Exception? error;
/// The validation result returned a status of valid;
bool get isValid => status == QrValidationStatus.valid;
}
/// The status of the QR code data you requested to be validated.
enum QrValidationStatus {
/// The QR code data is valid for the provided parameters.
valid,
/// The QR code data is too long for the provided version + error check
/// configuration or too long to be contained in a QR code.
contentTooLong,
/// An unknown / unexpected error occurred when we tried to validate the QR
/// code data.
error,
}

197
qr_flutter/pubspec.lock Normal file
View file

@ -0,0 +1,197 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
qr:
dependency: "direct main"
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.7"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
sdks:
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

22
qr_flutter/pubspec.yaml Normal file
View file

@ -0,0 +1,22 @@
name: qr_flutter
description: >
QR.Flutter is a Flutter library for simple and fast QR code rendering via a
Widget or custom painter.
version: 4.1.0
homepage: https://github.com/theyakka/qr.flutter
environment:
sdk: '>=2.19.6 <4.0.0'
flutter: ">=3.7.0"
dependencies:
flutter:
sdk: flutter
qr: ^3.0.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: false

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

@ -0,0 +1,14 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'package:flutter_test/flutter_test.dart';
import 'image_test.dart' as image;
import 'painter_test.dart' as painter;
void main() {
group('image:', image.main);
group('painter:', painter.main);
}

View file

@ -0,0 +1,218 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:qr_flutter/qr_flutter.dart';
void main() {
testWidgets('QrImageView generates correct image', (
tester,
) async {
final qrImage = MaterialApp(
home: Center(
child: RepaintBoundary(
child: QrImageView(
data: 'This is a test image',
version: QrVersions.auto,
gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L,
),
),
),
);
await tester.pumpWidget(qrImage);
await expectLater(
find.byType(QrImageView),
matchesGoldenFile('./.golden/qr_image_golden.png'),
);
});
testWidgets(
'QrImageView generates correct image with eye style',
(tester) async {
final qrImage = MaterialApp(
home: Center(
child: RepaintBoundary(
child: QrImageView(
data: 'This is a test image',
version: QrVersions.auto,
gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L,
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.circle,
color: Colors.green,
),
),
),
),
);
await tester.pumpWidget(qrImage);
await expectLater(
find.byType(QrImageView),
matchesGoldenFile('./.golden/qr_image_eye_styled_golden.png'),
);
},
);
testWidgets(
'QrImageView generates correct image with data module style',
(tester) async {
final qrImage = MaterialApp(
home: Center(
child: RepaintBoundary(
child: QrImageView(
data: 'This is a test image',
version: QrVersions.auto,
gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L,
dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.circle,
color: Colors.blue,
),
),
),
),
);
await tester.pumpWidget(qrImage);
await expectLater(
find.byType(QrImageView),
matchesGoldenFile('./.golden/qr_image_data_module_styled_golden.png'),
);
},
);
testWidgets(
'QrImageView generates correct image with eye and data module sytle',
(tester) async {
final qrImage = MaterialApp(
home: Center(
child: RepaintBoundary(
child: QrImageView(
data: 'This is a test image',
version: QrVersions.auto,
gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L,
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.circle,
color: Colors.green,
),
dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.circle,
color: Colors.blue,
),
),
),
),
);
await tester.pumpWidget(qrImage);
await expectLater(
find.byType(QrImageView),
matchesGoldenFile(
'./.golden/qr_image_eye_data_module_styled_golden.png',
),
);
},
);
testWidgets(
'QrImageView does not apply eye and data module color when foreground '
'color is also specified',
(tester) async {
final qrImage = MaterialApp(
home: Center(
child: RepaintBoundary(
child: QrImageView(
data: 'This is a test image',
version: QrVersions.auto,
gapless: true,
// ignore: deprecated_member_use_from_same_package
foregroundColor: Colors.red,
errorCorrectionLevel: QrErrorCorrectLevel.L,
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.circle,
color: Colors.green,
),
dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.circle,
color: Colors.blue,
),
),
),
),
);
await tester.pumpWidget(qrImage);
await expectLater(
find.byType(QrImageView),
matchesGoldenFile('./.golden/qr_image_foreground_colored_golden.png'),
);
},
);
testWidgets(
'QrImageView generates correct image with logo',
(tester) async {
await pumpWidgetWithImages(
tester,
MaterialApp(
home: Center(
child: RepaintBoundary(
child: QrImageView(
data: 'This is a a qr code with a logo',
version: QrVersions.auto,
gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L,
embeddedImage: FileImage(File('test/.images/logo_yakka.png')),
),
),
),
),
<String>['test/.images/logo_yakka.png'],
);
await tester.pumpAndSettle();
await expectLater(
find.byType(QrImageView),
matchesGoldenFile('./.golden/qr_image_logo_golden.png'),
);
},
);
}
/// Pre-cache images to make sure they show up in golden tests.
///
/// See https://github.com/flutter/flutter/issues/36552 for more info.
Future<void> pumpWidgetWithImages(
WidgetTester tester,
Widget widget,
List<String> assetNames,
) async {
Future<void>? precacheFuture;
await tester.pumpWidget(
Builder(
builder: (buildContext) {
precacheFuture = tester.runAsync(() async {
await Future.wait(<Future<void>>[
for (final String assetName in assetNames)
precacheImage(FileImage(File(assetName)), buildContext),
]);
});
return widget;
},
),
);
await precacheFuture;
}
Widget buildTestableWidget(Widget widget) {
return MediaQuery(
data: const MediaQueryData(),
child: MaterialApp(home: widget),
);
}

View file

@ -0,0 +1,41 @@
/*
* QR.Flutter
* Copyright (c) 2019 the QR.Flutter authors.
* See LICENSE for distribution and usage details.
*/
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:qr_flutter/qr_flutter.dart';
void main() {
testWidgets('QrPainter generates correct image', (tester) async {
final painter = QrPainter(
data: 'The painter is this thing',
version: QrVersions.auto,
gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L,
);
ByteData? imageData;
await tester.runAsync(() async {
imageData = await painter.toImageData(600.0);
});
final imageBytes = imageData!.buffer.asUint8List();
final Widget widget = Center(
child: RepaintBoundary(
child: SizedBox(
width: 600,
height: 600,
child: Image.memory(imageBytes),
),
),
);
await tester.pumpWidget(widget);
await tester.pumpAndSettle();
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('./.golden/qr_painter_golden.png'),
);
});
}