diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2f606..3e55077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,8 @@ ## 0.1.0 * Initial release. + +## 0.1.1 +* Beta release + +## 0.1.2 +* Fixed QR code eyes on web \ No newline at end of file diff --git a/README.md b/README.md index 0accbdc..ccf3c67 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Custom QR generator for Flutter [![pub package](https://img.shields.io/pub/v/custom_qr_generator.svg)](https://pub.dartlang.org/packages/custom_qr_generator) + + Flutter port of [Android library](https://github.com/alexzhirkevich/custom-qr-generator) # Progress @@ -22,10 +24,10 @@ This will add a line like this to your package's `pubspec.yaml` (and run an impl ```yaml dependencies: - custom_qr_generator: ^0.1.0 + custom_qr_generator: ^0.1.2 ``` -## Import it +## Import it Now in your Dart code, you can use: @@ -73,11 +75,50 @@ class MyApp extends StatelessWidget { ) ) )), - size: const Size(300, 300), + size: const Size(350, 350), ), ), ), ); } } + +``` + +# Customization + +You can implement custom shapes for any QR code parts: QrPixelShape, QrBallShape, QrFrameShape +like this: +```dart +class QrPixelShapeCircle extends QrPixelShape { + + @override + Path createPath(double size, Neighbors neighbors) => + Path()..addOval(Rect.fromLTRB(0, 0, size, size)); +} +``` + +Also you can create custom paint for this elements: + +```dart + +class QrColorRadialGradient extends QrColor { + + final List colors; + + const QrColorRadialGradient({ + required this.colors, + }); + + @override + Paint createPaint(final double width, final double height) => + Paint() + ..shader = Gradient.radial( + Offset(width / 2, height / 2), + min(width, height), + colors + ); +} + + ``` diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f..9625e10 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c6759a6..30b6524 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -127,7 +127,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -272,7 +272,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -346,7 +346,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -395,7 +395,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..3db53b6 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + diff --git a/example/lib/main.dart b/example/lib/main.dart index fd8a8b5..47d9027 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,34 +18,29 @@ class MyApp extends StatelessWidget { ), body: Center( child: CustomPaint( + // painter: Painter(), painter: QrPainter( - data: "Welcome to Flutter", + data: 'https://youtube.com', options: const QrOptions( shapes: QrShapes( - darkPixel: QrPixelShapeRoundCorners( - cornerFraction: .5 - ), - frame: QrFrameShapeRoundCorners( - cornerFraction: .25 - ), - ball: QrBallShapeRoundCorners( - cornerFraction: .25 - ) + darkPixel: QrPixelShapeRoundCorners(cornerFraction: .5), + frame: QrFrameShapeRoundCorners(cornerFraction: .25), + ball: QrBallShapeRoundCorners(cornerFraction: .25) ), colors: QrColors( dark: QrColorLinearGradient( colors: [ Color.fromARGB(255, 255, 0, 0), - Color.fromARGB(255, 0, 0, 255), + Color.fromARGB(255, 0, 0, 255) ], orientation: GradientOrientation.leftDiagonal ) ) )), - size: const Size(300, 300), + size: const Size(350, 350), ), ), ), ); } -} \ No newline at end of file +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 8c6977d..eaf6331 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,14 +21,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" charset: dependency: transitive description: @@ -42,28 +35,28 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" custom_qr_generator: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.1.0" + version: "0.1.2" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -101,21 +94,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -127,7 +127,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -148,35 +148,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.4.12" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" zxing_lib: dependency: transitive description: @@ -185,5 +178,5 @@ packages: source: hosted version: "0.6.0" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=1.17.0" diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index a19dabb..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:example/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/lib/colors/color.dart b/lib/colors/color.dart index 17cb0d8..67a9491 100644 --- a/lib/colors/color.dart +++ b/lib/colors/color.dart @@ -5,9 +5,27 @@ import 'package:custom_qr_generator/util.dart'; abstract class QrColor { + const QrColor(); + Paint createPaint(final double width, final double height); - const QrColor(); + static const unspecified = QrColorUnspecified(); + + static QrColor solid(Color color) => QrColorSolid(color); + + static QrColorLinearGradient linearGradient({ + required List colors, + required GradientOrientation orientation + }) => QrColorLinearGradient(colors: colors, orientation: orientation); + + static QrColorRadialGradient radialGradient({ + required List colors, + double radiusFraction = 1 + }) => QrColorRadialGradient(colors: colors, radiusFraction: radiusFraction); + + static QrColorSweepGradient sweepGradient({ + required List colors + }) => QrColorSweepGradient(colors: colors); } class QrColorUnspecified extends QrColor { @@ -16,7 +34,8 @@ class QrColorUnspecified extends QrColor { @override Paint createPaint(double width, double height) => - const QrColorSolid(Color.fromARGB(0,0,0,0)).createPaint(width, height); + const QrColorSolid(Color.fromARGB(0,0,0,0)) + .createPaint(width, height); @override bool operator ==(Object other) => diff --git a/lib/neighbors.dart b/lib/neighbors.dart index 454787f..083ca6a 100644 --- a/lib/neighbors.dart +++ b/lib/neighbors.dart @@ -9,6 +9,8 @@ class Neighbors { final bool bottom; final bool bottomRight; + static const empty = Neighbors(); + const Neighbors({ this.topLeft = false, this.topRight = false, diff --git a/lib/qr_painter.dart b/lib/qr_painter.dart index 10c13fa..a781560 100644 --- a/lib/qr_painter.dart +++ b/lib/qr_painter.dart @@ -1,11 +1,14 @@ +import 'dart:convert'; import 'dart:math'; +import 'package:custom_qr_generator/custom_qr_generator.dart'; import 'package:custom_qr_generator/util.dart'; import 'package:custom_qr_generator/colors/color.dart'; import 'package:custom_qr_generator/options/options.dart'; import 'package:flutter/widgets.dart'; import 'package:zxing_lib/qrcode.dart'; +import 'package:zxing_lib/zxing.dart'; class QrPainter extends CustomPainter { @@ -15,13 +18,21 @@ class QrPainter extends CustomPainter { QrPainter({ required this.data, - required this.options + required this.options, }) { + matrix = Encoder - .encode( - data, options.ecl, Map.fromEntries([ - ])) + .encode(data, options.ecl) .matrix!; + + var width = matrix.width~/4; + if (width % 2 != matrix.width%2) { + width++; + } + var height = matrix.height~/4; + if (height % 2 != matrix.height%2) { + height++; + } } @override @@ -39,19 +50,18 @@ class QrPainter extends CustomPainter { Path fullDarkPath = Path(); Path fullLightPath = Path(); - final framePath = options.shapes.frame.createPath(pixelSize * 7, null); - final framePaint = options.colors.frame.createPaint( - pixelSize * 3, pixelSize * 3); - final ballPath = options.shapes.ball.createPath(pixelSize * 3, null); - final ballPaint = options.colors.ball.createPaint( - pixelSize * 3, pixelSize * 3); + var frameSize= pixelSize * 7; + var ballSize = pixelSize * 3; - if (!options.shapes.darkPixel.dependOnNeighbors) { - darkPath = options.shapes.darkPixel.createPath(pixelSize, null); - } - if (!options.shapes.lightPixel.dependOnNeighbors) { - lightPath = options.shapes.lightPixel.createPath(pixelSize, null); - } + final framePathZeroOffset = options.shapes.frame + .createPath(Offset.zero, frameSize, Neighbors.empty); + final framePaint = options.colors.frame + .createPaint(pixelSize * 3, pixelSize * 3); + + final ballPathZeroOffset = options.shapes.ball + .createPath(Offset.zero, pixelSize * 3, Neighbors.empty); + final ballPaint = options.colors.ball + .createPaint(pixelSize * 3, pixelSize * 3); canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), @@ -61,57 +71,40 @@ class QrPainter extends CustomPainter { canvas.save(); canvas.translate(padding, padding); - // todo: workaround for Dart bug with adding combined path. - fullDarkPath = Path.combine(PathOperation.union, fullDarkPath, Path()); - - if (options.colors.frame is! QrColorUnspecified) { - canvas.drawPath(framePath, framePaint); - } else { - fullDarkPath.addPath(framePath, Offset.zero); + void drawFrame(double dx, double dy) { + if (options.colors.frame is! QrColorUnspecified) { + canvas.save(); + canvas.translate(dx, dy); + canvas.drawPath(framePathZeroOffset, framePaint); + canvas.restore(); + } else { + var path = options.shapes.frame + .createPath(Offset(dx, dy), frameSize, Neighbors.empty); + canvas.drawPath(path, darkPaint); + } } - if (options.colors.ball is! QrColorUnspecified) { - canvas.save(); - canvas.translate(pixelSize * 2, pixelSize * 2); - canvas.drawPath(ballPath, ballPaint); - canvas.restore(); - } else { - fullDarkPath.addPath(ballPath, Offset(pixelSize * 2, pixelSize * 2)); + void drawBall(double dx, double dy) { + if (options.colors.ball is! QrColorUnspecified) { + canvas.save(); + canvas.translate(dx, dy); + canvas.drawPath(ballPathZeroOffset, ballPaint); + canvas.restore(); + } else { + var path = options.shapes.ball + .createPath(Offset(dx, dy), ballSize, Neighbors.empty); + canvas.drawPath(path, darkPaint); + } } - canvas.save(); - canvas.translate(pixelSize * (matrix.width - 7), 0); - if (options.colors.frame is! QrColorUnspecified) { - canvas.drawPath(framePath, framePaint); - } else { - fullDarkPath.addPath( - framePath, Offset(pixelSize * (matrix.width - 7), 0)); - } - if (options.colors.ball is! QrColorUnspecified) { - canvas.translate(pixelSize * 2, pixelSize * 2); - canvas.drawPath(ballPath, ballPaint); - } else { - fullDarkPath.addPath( - ballPath, Offset(pixelSize * (matrix.width - 5), pixelSize * 2)); - } - canvas.restore(); + drawFrame(0,0); + drawBall(pixelSize * 2, pixelSize * 2); - canvas.save(); - canvas.translate(0, pixelSize * (matrix.height - 7)); - if (options.colors.frame is! QrColorUnspecified) { - canvas.drawPath(framePath, framePaint); - } else { - fullDarkPath.addPath( - framePath, Offset(0, pixelSize * (matrix.width - 7))); - } - if (options.colors.ball is! QrColorUnspecified) { - canvas.translate(pixelSize * 2, pixelSize * 2); - canvas.drawPath(ballPath, ballPaint); - } else { - fullDarkPath.addPath( - ballPath, Offset(pixelSize * 2, pixelSize * (matrix.height - 5))); - } - canvas.restore(); + drawFrame(pixelSize * (matrix.width - 7), 0); + drawBall(pixelSize * (matrix.width - 5), pixelSize * 2); + + drawFrame(0,pixelSize * (matrix.width - 7)); + drawBall(pixelSize * 2, pixelSize * (matrix.height - 5)); for (int i = 0; i < matrix.width; i++) { @@ -123,28 +116,34 @@ class QrPainter extends CustomPainter { continue; } - if (options.shapes.darkPixel.dependOnNeighbors) { + if (options.colors.dark is! QrColorUnspecified) { darkPath = options.shapes.darkPixel.createPath( - pixelSize, matrix.neighbors(i, j) + Offset.zero, pixelSize, matrix.neighbors(i, j) ); } - if (options.shapes.lightPixel.dependOnNeighbors) { + if (options.colors.light is! QrColorUnspecified) { lightPath = options.shapes.lightPixel.createPath( - pixelSize, matrix.neighbors(i, j) + Offset.zero, pixelSize, matrix.neighbors(i, j) ); } - if (matrix.get(i, j) == 1) { + if (matrix.get(i, j) == 1 && options.colors.dark is! QrColorUnspecified) { fullDarkPath.addPath(darkPath!, Offset(i * pixelSize, j * pixelSize)); } - if (matrix.get(i, j) != 0) { - fullLightPath.addPath( - lightPath!, Offset(i * pixelSize, j * pixelSize)); + if (matrix.get(i, j) != 0 && options.colors.light is! QrColorUnspecified) { + fullLightPath.addPath(lightPath!, Offset(i * pixelSize, j * pixelSize)); } } } - canvas.drawPath(fullDarkPath, darkPaint); - canvas.drawPath(fullLightPath, lightPaint); + + + if (options.colors.dark is! QrColorUnspecified) { + canvas.drawPath(fullDarkPath, darkPaint); + } + if (options.colors.light is! QrColorUnspecified) { + canvas.drawPath(fullLightPath, lightPaint); + } canvas.restore(); + } @override diff --git a/lib/shapes/ball_shape.dart b/lib/shapes/ball_shape.dart index 07be4a2..1ad7fcd 100644 --- a/lib/shapes/ball_shape.dart +++ b/lib/shapes/ball_shape.dart @@ -1,12 +1,18 @@ import 'package:custom_qr_generator/shapes/shape.dart'; -abstract class QrBallShape extends Shape { - - @override - bool get dependOnNeighbors => false; +abstract class QrBallShape extends QrElementShape { const QrBallShape(); + + static const basic = QrBallShapeDefault(); + + static QrBallShapeCircle circle({double radiusFraction = 1}) => + QrBallShapeCircle(radiusFraction: radiusFraction); + + static QrBallShapeRoundCorners roundCorners({ + required double cornerFraction + }) => QrBallShapeRoundCorners(cornerFraction: cornerFraction); } class QrBallShapeDefault extends QrBallShape with ShapeRect { diff --git a/lib/shapes/frame_shape.dart b/lib/shapes/frame_shape.dart index 97ad8ff..97b8101 100644 --- a/lib/shapes/frame_shape.dart +++ b/lib/shapes/frame_shape.dart @@ -1,15 +1,36 @@ import 'dart:ui'; import 'package:custom_qr_generator/custom_qr_generator.dart'; -import 'package:custom_qr_generator/neighbors.dart'; -import 'package:custom_qr_generator/util.dart'; -import 'package:custom_qr_generator/shapes/shape.dart'; -abstract class QrFrameShape extends Shape{ - @override - bool get dependOnNeighbors => false; +abstract class QrFrameShape extends QrElementShape { const QrFrameShape(); + + static const basic = QrFrameShapeDefault(); + + static QrFrameShapeCircle circle({ + double widthFraction = 1, + double radiusFraction = 1 + }) => QrFrameShapeCircle( + widthFraction: widthFraction, + radiusFraction: radiusFraction + ); + + static QrFrameShapeRoundCorners roundCorners({ + required double cornerFraction, + double widthFraction = 1, + bool topLeft = true, + bool topRight = true, + bool bottomLeft = true, + bool bottomRight = true + }) => QrFrameShapeRoundCorners( + widthFraction: widthFraction, + cornerFraction: cornerFraction, + topLeft: topLeft, + topRight: topRight, + bottomLeft: bottomLeft, + bottomRight: bottomRight + ); } class QrFrameShapeDefault extends QrFrameShape { @@ -17,14 +38,12 @@ class QrFrameShapeDefault extends QrFrameShape { const QrFrameShapeDefault(); @override - Path createPath(double size, Neighbors? neighbors) { - final big = Path() - ..addRect(Rect.fromLTWH(0, 0, size, size)); - final small = Path() - ..addRect(Rect.fromLTWH(size / 7, size / 7, size * 5 / 7, size * 5 / 7)); + Path createPath(Offset offset, double size, Neighbors? neighbors) => + Path() + ..fillType = PathFillType.evenOdd + ..addRect(Rect.fromLTWH(offset.dx, offset.dy, size, size)) + ..addRect(Rect.fromLTWH(offset.dx +size / 7,offset.dy+ size / 7, size * 5 / 7, size * 5 / 7)); - return Path.combine(PathOperation.xor, big, small); - } @override bool operator ==(Object other) => other is QrFrameShapeDefault; @@ -44,20 +63,23 @@ class QrFrameShapeCircle extends QrFrameShape { }); @override - Path createPath(double size, Neighbors? neighbors) { + Path createPath(Offset offset, double size, Neighbors? neighbors) { + final realSize = size * radiusFraction; - final offset = (size - realSize) / 2; + final offset2 = (size - realSize) / 2; final width = size / 7 * widthFraction.coerceAtLeast(0); - final big = Path() - ..addOval(Rect.fromLTWH(offset, offset, realSize, realSize)); - final small = Path() - ..addOval(Rect.fromLTWH( - offset + width, offset + width, - size - offset * 2 - width * 2, size - offset * 2 - width * 2 - )); - return Path.combine(PathOperation.xor, small, big); + return Path() + ..fillType = PathFillType.evenOdd + ..addOval(Rect.fromLTWH(offset.dx + offset2, offset.dy + offset2, realSize, realSize)) + ..addOval(Rect.fromLTWH( + offset.dx + offset2 + width, + offset.dy + offset2 + width, + size - offset2 * 2 - width * 2, + size - offset2 * 2 - width * 2 + )) + ; } @override @@ -89,7 +111,7 @@ class QrFrameShapeRoundCorners extends QrFrameShape { }); @override - Path createPath(double size, Neighbors? neighbors) { + Path createPath(Offset offset,double size, Neighbors? neighbors) { final cf = cornerFraction.coerceIn(0, .5); final cTopLeft = topLeft ? cf : 0; final cTopRight = topRight ? cf : 0; @@ -97,23 +119,24 @@ class QrFrameShapeRoundCorners extends QrFrameShape { final cBottomRight = bottomRight ? cf : 0; final w = size / 7 * widthFraction.coerceIn(0, 2); - final big = Path() - ..addRRect(RRect.fromLTRBAndCorners(0, 0, size, size, + return Path() + ..fillType=PathFillType.evenOdd + ..addRRect(RRect.fromLTRBAndCorners( + offset.dx, offset.dy, + offset.dx + size, offset.dy + size, topLeft: Radius.circular(size * cTopLeft), topRight: Radius.circular(size * cTopRight), bottomLeft: Radius.circular(size * cBottomLeft), bottomRight: Radius.circular(size * cBottomRight), - )); - - final small = Path() - ..addRRect(RRect.fromLTRBAndCorners(w, w, size - w, size - w, + )) + ..addRRect(RRect.fromLTRBAndCorners( + offset.dx + w, offset.dy + w, + offset.dx + size - w, offset.dy + size - w, topLeft: Radius.circular((size - 2 * w) * cTopLeft), topRight: Radius.circular((size - 2 * w) * cTopRight), bottomLeft: Radius.circular((size - 2 * w) * cBottomLeft), bottomRight: Radius.circular((size - 2 * w) * cBottomRight), )); - - return Path.combine(PathOperation.xor, big, small); } @override diff --git a/lib/shapes/pixel_shape.dart b/lib/shapes/pixel_shape.dart index 4cc0e92..85f8c3d 100644 --- a/lib/shapes/pixel_shape.dart +++ b/lib/shapes/pixel_shape.dart @@ -1,17 +1,25 @@ import 'package:custom_qr_generator/shapes/shape.dart'; -abstract class QrPixelShape extends Shape { - const QrPixelShape(); +abstract class QrPixelShape extends QrElementShape { + + const QrPixelShape(); + + static const basic = QrPixelShapeDefault(); + + static QrPixelShapeCircle circle({ + double radiusFraction = 1 + }) => QrPixelShapeCircle(radiusFraction: radiusFraction); + + static QrPixelShapeRoundCorners roundCorners({ + double cornerFraction = 1 + }) => QrPixelShapeRoundCorners(cornerFraction: cornerFraction); } class QrPixelShapeDefault extends QrPixelShape with ShapeRect { const QrPixelShapeDefault(); - @override - bool get dependOnNeighbors => false; - @override bool operator ==(Object other) => other is QrPixelShapeDefault; @@ -24,9 +32,6 @@ class QrPixelShapeCircle extends QrPixelShape with ShapeCircle { @override final double radiusFraction; - @override - bool get dependOnNeighbors => false; - const QrPixelShapeCircle({ this.radiusFraction = 1.0 }); @@ -39,22 +44,19 @@ class QrPixelShapeCircle extends QrPixelShape with ShapeCircle { int get hashCode => radiusFraction.hashCode; } - class QrPixelShapeRoundCorners extends QrPixelShape with ShapeRoundCorners { - @override - final double cornerFraction; - - @override - bool get dependOnNeighbors => true; + @override + final double cornerFraction; - const QrPixelShapeRoundCorners({ - this.cornerFraction = .5 - }); + const QrPixelShapeRoundCorners({ + this.cornerFraction = .5 + }); - @override - bool operator ==(Object other) => other is QrPixelShapeRoundCorners && - other.cornerFraction == cornerFraction; + @override + bool operator ==(Object other) => + other is QrPixelShapeRoundCorners && + other.cornerFraction == cornerFraction; @override int get hashCode => cornerFraction.hashCode; diff --git a/lib/shapes/shape.dart b/lib/shapes/shape.dart index 771e9b5..62e8980 100644 --- a/lib/shapes/shape.dart +++ b/lib/shapes/shape.dart @@ -4,55 +4,50 @@ import 'package:custom_qr_generator/util.dart'; import '../../neighbors.dart'; -abstract class Shape { +abstract class QrElementShape { - abstract final bool dependOnNeighbors; + Path createPath(Offset offset, double size, Neighbors neighbors); - Path createPath(double size, Neighbors? neighbors); - - const Shape(); + const QrElementShape(); } -mixin ShapeRect implements Shape { - - @override - bool get dependOnNeighbors => false; +mixin ShapeRect implements QrElementShape { @override - Path createPath(double size, Neighbors? neighbors) => Path() - ..addRect(Rect.fromLTRB(0, 0, size, size)); + Path createPath(Offset offset, double size, Neighbors neighbors) => Path() + ..addRect(Rect.fromLTWH(offset.dx, offset.dy, size, size)); } -mixin ShapeCircle implements Shape { +mixin ShapeCircle implements QrElementShape { abstract final double radiusFraction; @override - bool get dependOnNeighbors => false; - - @override - Path createPath(double size, Neighbors? neighbors) { - Path path = Path(); + Path createPath(Offset offset, double size, Neighbors neighbors) { double cSizeFraction = radiusFraction.coerceIn(0.0, 1.0); double padding = size * (1 - cSizeFraction)/2; - path.addOval(Rect.fromLTRB(padding, padding, size- padding, size-padding)); - return path; + var sSize = size - 2*padding; + return Path() + ..addOval(Rect.fromLTWH(offset.dx + padding, offset.dy + padding,sSize, sSize)); } } -mixin ShapeRoundCorners implements Shape { +mixin ShapeRoundCorners implements QrElementShape { abstract final double cornerFraction; @override - Path createPath(double size, Neighbors? neighbors) { + Path createPath(Offset offset, double size, Neighbors neighbors) { Path path = Path(); double corner = cornerFraction.coerceIn(0, .5) * size; - - if (!dependOnNeighbors || neighbors == null || !neighbors.hasAny()){ - path.addRRect(RRect.fromLTRBR(0, 0, size, size, Radius.circular(corner))); + if (!neighbors.hasAny()){ + path.addRRect(RRect.fromLTRBR( + offset.dx, offset.dy, + offset.dx + size, offset.dy + size, + Radius.circular(corner)) + ); } else { double topLeft =0; @@ -73,7 +68,8 @@ mixin ShapeRoundCorners implements Shape { bottomRight = corner; } - path.addRRect(RRect.fromLTRBAndCorners(0, 0, size, size, + path.addRRect(RRect.fromLTRBAndCorners( + offset.dx, offset.dy, offset.dx + size, offset.dy + size, topLeft: Radius.circular(topLeft), topRight: Radius.circular(topRight), bottomLeft: Radius.circular(bottomLeft), diff --git a/pubspec.lock b/pubspec.lock index d1eeafe..ca9b728 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -21,14 +21,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" charset: dependency: transitive description: @@ -42,21 +35,21 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -94,21 +87,28 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -120,7 +120,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -141,35 +141,28 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.4.12" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" zxing_lib: dependency: "direct main" description: @@ -178,5 +171,5 @@ packages: source: hosted version: "0.6.0" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=1.17.0" diff --git a/pubspec.yaml b/pubspec.yaml index 30bfec7..d0dae30 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: custom_qr_generator description: Custom QR generator for Flutter -version: 0.1.0 +version: 0.1.2 homepage: https://github.com/alexzhirkevich/custom-qr-generator-flutter environment: @@ -8,9 +8,9 @@ environment: flutter: ">=1.17.0" dependencies: + zxing_lib: ^0.6.0 flutter: sdk: flutter - zxing_lib: ^0.6.0 dev_dependencies: flutter_test: