Skip to content

Commit

Permalink
Add eye and data module styling. Absorb foreground color into styling
Browse files Browse the repository at this point in the history
  • Loading branch information
ankur2136 committed Jul 8, 2020
1 parent bea204f commit cb6a312
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 29 deletions.
10 changes: 8 additions & 2 deletions example/lib/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ class _MainScreenState extends State<MainScreen> {
painter: QrPainter(
data: message,
version: QrVersions.auto,
color: Color(0xff1a5441),
emptyColor: Color(0xffeafcf6),
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Colors.red,
),
dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Colors.red,
),
// size: 320.0,
embeddedImage: snapshot.data,
embeddedImageStyle: QrEmbeddedImageStyle(
Expand Down
36 changes: 28 additions & 8 deletions lib/src/qr_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import 'dart:async';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:qr/qr.dart';
import 'package:qr_flutter/qr_flutter.dart';

import 'qr_painter.dart';
import 'qr_versions.dart';
Expand All @@ -25,8 +27,7 @@ class QrImage extends StatefulWidget {
Key key,
this.size,
this.padding = const EdgeInsets.all(10.0),
this.backgroundColor = const Color(0x00FFFFFF),
this.foregroundColor = const Color(0xFF000000),
this.backgroundColor = Colors.transparent,
this.version = QrVersions.auto,
this.errorCorrectionLevel = QrErrorCorrectLevel.L,
this.errorStateBuilder,
Expand All @@ -35,6 +36,14 @@ class QrImage extends StatefulWidget {
this.embeddedImage,
this.embeddedImageStyle,
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,
}) : assert(QrVersions.isSupportedVersion(version)),
_data = data,
Expand All @@ -48,8 +57,7 @@ class QrImage extends StatefulWidget {
Key key,
this.size,
this.padding = const EdgeInsets.all(10.0),
this.backgroundColor = const Color(0x00FFFFFF),
this.foregroundColor = const Color(0xFF000000),
this.backgroundColor = Colors.transparent,
this.version = QrVersions.auto,
this.errorCorrectionLevel = QrErrorCorrectLevel.L,
this.errorStateBuilder,
Expand All @@ -58,6 +66,14 @@ class QrImage extends StatefulWidget {
this.embeddedImage,
this.embeddedImageStyle,
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,
}) : assert(QrVersions.isSupportedVersion(version)),
_data = null,
Expand All @@ -72,9 +88,6 @@ class QrImage extends StatefulWidget {
/// The background color of the final QR code widget.
final Color backgroundColor;

/// The foreground color of the final QR code widget.
final Color foregroundColor;

/// The QR code version to use.
final int version;

Expand Down Expand Up @@ -123,6 +136,12 @@ class QrImage extends StatefulWidget {
/// 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
_QrImageState createState() => _QrImageState();
}
Expand Down Expand Up @@ -191,10 +210,11 @@ class _QrImageState extends State<QrImage> {
Widget _qrWidget(BuildContext context, ui.Image image, double edgeLength) {
final painter = QrPainter.withQr(
qr: _qr,
color: widget.foregroundColor,
gapless: widget.gapless,
embeddedImageStyle: widget.embeddedImageStyle,
embeddedImage: image,
eyeStyle: widget.eyeStyle,
dataModuleStyle: widget.dataModuleStyle,
);
return _QrContentView(
edgeLength: edgeLength,
Expand Down
72 changes: 54 additions & 18 deletions lib/src/qr_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,25 @@ import 'validator.dart';

const _finderPatternLimit = 7;

// default color for the qr code pixels
const _qrDefaultColor = Color(0xff111111);

/// 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,
this.color = _qrDefaultColor,
this.emptyColor,
this.gapless = false,
this.embeddedImage,
this.embeddedImageStyle,
this.eyeStyle = const QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Color(0xFF000000),
),
this.dataModuleStyle = const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Color(0xFF000000),
),
}) : assert(QrVersions.isSupportedVersion(version)) {
_init(data);
}
Expand All @@ -47,11 +51,18 @@ class QrPainter extends CustomPainter {
/// flow or for when you need to pre-validate the QR data.
QrPainter.withQr({
QrCode qr,
this.color = _qrDefaultColor,
this.emptyColor,
this.gapless = false,
this.embeddedImage,
this.embeddedImageStyle,
this.eyeStyle = const QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Color(0xFF000000),
),
this.dataModuleStyle = const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Color(0xFF000000),
),
}) : _qr = qr,
version = qr.typeNumber,
errorCorrectionLevel = qr.errorCorrectLevel {
Expand All @@ -65,9 +76,6 @@ class QrPainter extends CustomPainter {
/// The error correction level of the QR code.
final int errorCorrectionLevel; // the qr code error correction level

/// The color of the squares.
final Color color; // the color of the dark squares

/// The color of the non-squares (background).
@Deprecated(
'You should us the background color value of your container widget')
Expand All @@ -83,6 +91,12 @@ class QrPainter extends CustomPainter {
/// 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;

Expand Down Expand Up @@ -174,7 +188,7 @@ class QrPainter extends CustomPainter {
final gap = !gapless ? _gapSize : 0;
// get the painters for the pixel information
final pixelPaint = _paintCache.firstPaint(QrCodeElement.codePixel);
pixelPaint.color = color;
pixelPaint.color = dataModuleStyle.color;
Paint emptyPixelPaint;
if (emptyColor != null) {
emptyPixelPaint = _paintCache.firstPaint(QrCodeElement.codePixelEmpty);
Expand Down Expand Up @@ -203,7 +217,13 @@ class QrPainter extends CustomPainter {
paintMetrics.pixelSize + pixelHTweak,
paintMetrics.pixelSize + pixelVTweak,
);
canvas.drawRect(squareRect, paint);
if (dataModuleStyle.dataModuleShape == QrDataModuleShape.square) {
canvas.drawRect(squareRect, paint);
} else {
final roundedRect = RRect.fromRectAndRadius(squareRect,
Radius.circular(paintMetrics.pixelSize + pixelHTweak));
canvas.drawRRect(roundedRect, paint);
}
}
}

Expand Down Expand Up @@ -269,7 +289,7 @@ class QrPainter extends CustomPainter {
final outerPaint = _paintCache.firstPaint(QrCodeElement.finderPatternOuter,
position: position);
outerPaint.strokeWidth = metrics.pixelSize;
outerPaint.color = color;
outerPaint.color = eyeStyle.color;

final innerPaint = _paintCache.firstPaint(QrCodeElement.finderPatternInner,
position: position);
Expand All @@ -278,21 +298,36 @@ class QrPainter extends CustomPainter {

final dotPaint = _paintCache.firstPaint(QrCodeElement.finderPatternDot,
position: position);
dotPaint.color = color;
dotPaint.color = eyeStyle.color;

final outerRect = Rect.fromLTWH(offset.dx, offset.dy, radius, radius);
canvas.drawRect(outerRect, outerPaint);

final innerRadius = radius - (2 * metrics.pixelSize);
final innerRect = Rect.fromLTWH(offset.dx + metrics.pixelSize,
offset.dy + metrics.pixelSize, innerRadius, innerRadius);
canvas.drawRect(innerRect, innerPaint);

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);
canvas.drawRect(dotRect, dotPaint);

if (eyeStyle.eyeShape == QrEyeShape.square) {
canvas.drawRect(outerRect, outerPaint);
canvas.drawRect(innerRect, innerPaint);
canvas.drawRect(dotRect, dotPaint);
} else {
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);
}
}

bool _hasOneNonZeroSide(Size size) => size.longestSide > 0;
Expand Down Expand Up @@ -332,13 +367,14 @@ class QrPainter extends CustomPainter {
@override
bool shouldRepaint(CustomPainter oldPainter) {
if (oldPainter is QrPainter) {
return color != oldPainter.color ||
errorCorrectionLevel != oldPainter.errorCorrectionLevel ||
return errorCorrectionLevel != oldPainter.errorCorrectionLevel ||
_calcVersion != oldPainter._calcVersion ||
_qr != oldPainter._qr ||
gapless != oldPainter.gapless ||
embeddedImage != oldPainter.embeddedImage ||
embeddedImageStyle != oldPainter.embeddedImageStyle;
embeddedImageStyle != oldPainter.embeddedImageStyle ||
eyeStyle != oldPainter.eyeStyle ||
dataModuleStyle != oldPainter.dataModuleStyle;
}
return true;
}
Expand Down
67 changes: 67 additions & 0 deletions lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,73 @@ enum FinderPatternPosition {
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,
}

/// Styling options for finder pattern eye.
class QrEyeStyle {
/// Create a new set of styling options for QR Eye.
const QrEyeStyle({this.eyeShape, this.color});

/// Eye shape.
final QrEyeShape eyeShape;

/// Color to tint the eye.
final Color color;

@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.
class QrDataModuleStyle {
/// Create a new set of styling options for data modules.
const QrDataModuleStyle({
this.dataModuleShape,
this.color,
});

/// Eye shape.
final QrDataModuleShape dataModuleShape;

/// Color to tint the data modules.
final Color color;

@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
class QrEmbeddedImageStyle {
/// Create a new set of styling options.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/.golden/qr_image_eye_styled_golden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit cb6a312

Please sign in to comment.