Conflicts:
	example/android/app/build.gradle
	example/android/build.gradle
	example/lib/main_screen.dart
	lib/src/qr_image_view.dart
	lib/src/qr_painter.dart
	lib/src/types.dart
This commit is contained in:
alexanderkind
2023-05-31 18:59:26 +07:00
35 changed files with 484 additions and 408 deletions

View File

@ -5,4 +5,5 @@ Charles Crete: @cretezy
Dmitry: @kelegorm
Pedro Massango: @pedromassango
Luke Pighetti: @lukepighetti
Jonas Zebari: @jonas-zebari
Jonas Zebari: @jonas-zebari
Ivan Semkin: @vanyasem

View File

@ -1,3 +1,6 @@
# 4.1.0
- Bump `qr` dependency (from `^3.0.0` to `^3.0.1`).
# 4.0.1
- Bump `qr` dependency (from `^2.0.0` to `^3.0.0`).
- **BREAKING**: Rename `QrImage` to `QrImageView`

View File

@ -28,7 +28,7 @@ You should add the following to your `pubspec.yaml` file:
```yaml
dependencies:
qr_flutter: ^4.0.0
qr_flutter: ^4.1.0
```
**Note**: If you're using the Flutter `master` channel, if you encounter build issues, or want to try the latest and greatest then you should use the `master` branch and not a specific release version. To do so, use the following configuration in your `pubspec.yaml`:
@ -37,7 +37,7 @@ dependencies:
dependencies:
qr_flutter:
git:
url: git://github.com/lukef/qr.flutter.git
url: https://github.com/theyakka/qr.flutter.git
```
Keep in mind the `master` branch could be unstable.
@ -55,8 +55,8 @@ import 'package:qr_flutter/qr_flutter.dart';
Next, to render a basic QR code you can use the following code (or something like it):
```dart
QrImage(
data: "1234567890",
QrImageView(
data: '1234567890',
version: QrVersions.auto,
size: 200.0,
),
@ -64,21 +64,22 @@ QrImage(
Depending on your data requirements you may want to tweak the QR code output. The following options are available:
| Property | Type | Description |
|----|----|----|
| `version` | int | `QrVersions.auto` or a value between 1 and 40. See http://www.qrcode.com/en/about/version.html for limitations and details. |
| `errorCorrectionLevel` | int | A value defined on `QrErrorCorrectLevel`. e.g.: `QrErrorCorrectLevel.L`. |
| `size` | double | The (square) size of the image. If not given, will auto size using shortest size constraint. |
| `padding` | EdgeInsets | Padding surrounding the QR code data. |
| `backgroundColor` | Color | The background color (default is none). |
| `foregroundColor` | Color | The foreground color (default is black). |
| `gapless` | bool | Adds an extra pixel in size to prevent gaps (default is true). |
| `errorStateBuilder` | QrErrorBuilder | Allows you to show an error state `Widget` in the event there is an error rendering the QR code (e.g.: version is too low, input is too long, etc). |
| `constrainErrorBounds` | bool | If true, the error `Widget` will be constrained to the square that the QR code was going to be drawn in. If false, the error state `Widget` will grow/shrink to whatever size it needs. |
| `embeddedImage` | ImageProvider | An `ImageProvider` that defines an image to be overlaid in the center of the QR code. |
| `embeddedImageStyle` | QrEmbeddedImageStyle | Properties to style the embedded image. |
| `embeddedImageEmitsError` | bool | If true, any failure to load the embedded image will trigger the `errorStateBuilder` or render an empty `Container`. If false, the QR code will be rendered and the embedded image will be ignored. |
|`semanticsLabel`|String|`semanticsLabel` will be used by screen readers to describe the content of the QR code.|
| Property | Type | Description |
|---------------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `version` | int | `QrVersions.auto` or a value between 1 and 40. See http://www.qrcode.com/en/about/version.html for limitations and details. |
| `errorCorrectionLevel` | int | A value defined on `QrErrorCorrectLevel`. e.g.: `QrErrorCorrectLevel.L`. |
| `size` | double | The (square) size of the image. If not given, will auto size using shortest size constraint. |
| `padding` | EdgeInsets | Padding surrounding the QR code data. |
| `backgroundColor` | Color | The background color (default is none). |
| `eyeStyle` | QrEyeStyle | Configures the QR code eyes' (corners') shape and color. |
| `dataModuleStyle` | QrDataModuleStyle | Configures the shape and the color of the dots. |
| `gapless` | bool | Adds an extra pixel in size to prevent gaps (default is true). |
| `errorStateBuilder` | QrErrorBuilder | Allows you to show an error state `Widget` in the event there is an error rendering the QR code (e.g.: version is too low, input is too long, etc). |
| `constrainErrorBounds` | bool | If true, the error `Widget` will be constrained to the square that the QR code was going to be drawn in. If false, the error state `Widget` will grow/shrink to whatever size it needs. |
| `embeddedImage` | ImageProvider | An `ImageProvider` that defines an image to be overlaid in the center of the QR code. |
| `embeddedImageStyle` | QrEmbeddedImageStyle | Properties to style the embedded image. |
| `embeddedImageEmitsError` | bool | If true, any failure to load the embedded image will trigger the `errorStateBuilder` or render an empty `Container`. If false, the QR code will be rendered and the embedded image will be ignored. |
| `semanticsLabel` | String | `semanticsLabel` will be used by screen readers to describe the content of the QR code. |
# Examples
@ -90,7 +91,7 @@ Also, the following examples give you a quick overview on how to use the library
A basic QR code will look something like:
```dart
QrImage(
QrImageView(
data: 'This is a simple QR code',
version: QrVersions.auto,
size: 320,
@ -101,7 +102,7 @@ QrImage(
A QR code with an image (from your application's assets) will look like:
```dart
QrImage(
QrImageView(
data: 'This QR code has an embedded image as well',
version: QrVersions.auto,
size: 320,
@ -116,7 +117,7 @@ QrImage(
To show an error state in the event that the QR code can't be validated:
```dart
QrImage(
QrImageView(
data: 'This QR code will show the error state instead',
version: 1,
size: 320,
@ -125,7 +126,7 @@ QrImage(
return Container(
child: Center(
child: Text(
"Uh oh! Something went wrong...",
'Uh oh! Something went wrong...',
textAlign: TextAlign.center,
),
),
@ -134,13 +135,6 @@ QrImage(
)
```
# FAQ
## Has it been tested in production? Can I use it in production?
Yep! It's stable and ready to rock. It's currently in use in quite a few production applications including:
- Sixpoint: [Android](https://play.google.com/store/apps/details?id=com.sixpoint.sixpoint&hl=en_US) & [iOS](https://itunes.apple.com/us/app/sixpoint/id663008674?mt=8)
# Outro
## Credits
Thanks to Kevin Moore for his awesome [QR - Dart](https://github.com/kevmoo/qr.dart) library. It's the core of this library.

View File

@ -80,7 +80,6 @@ linter:
# libraries
# classes
- one_member_abstracts # avoid
- avoid_classes_with_only_static_members # avoid
- public_member_api_docs
# constructors
- prefer_constructors_over_static_methods

View File

@ -26,7 +26,16 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 33
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@ -39,7 +48,7 @@ android {
defaultConfig {
applicationId "app.yakka.example"
minSdkVersion 16
targetSdkVersion 33
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -59,7 +68,7 @@ flutter {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.yakka.example">
<!-- Flutter needs it to communicate with the running application
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>

View File

@ -8,21 +8,16 @@
FlutterApplication and put your custom class here. -->
<application
android:label="example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
@ -32,5 +27,10 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -8,5 +8,4 @@
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.yakka.example">
<!-- Flutter needs it to communicate with the running application
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>

View File

@ -1,20 +1,23 @@
buildscript {
ext.kotlin_version = '1.6.21'
ext {
buildGradleVersion = '7.4.2'
kotlinVersion = '1.7.21'
}
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.android.tools.build:gradle:$buildGradleVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -1,6 +1,5 @@
#Sun Mar 14 10:07:12 PDT 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

View File

@ -1,15 +1,11 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>11.0</string>
</dict>
</plist>

View File

@ -3,17 +3,13 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
@ -27,8 +23,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -39,13 +33,11 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -58,8 +50,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -69,9 +59,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
@ -148,7 +136,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@ -193,6 +181,7 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -203,10 +192,11 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -255,7 +245,6 @@
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -295,7 +284,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@ -331,7 +320,6 @@
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -377,7 +365,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -387,7 +375,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -427,7 +414,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";

View File

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -41,5 +41,9 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@ -16,12 +16,14 @@ class ExampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.white,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark,
));
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.white,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark,
),
);
return MaterialApp(
title: 'QR.Flutter',
theme: ThemeData.light(),

View File

@ -10,13 +10,13 @@ import 'package:qr_flutter/qr_flutter.dart';
/// This is the screen that you'll see when the app starts
class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
@override
Widget build(BuildContext context) {
final message =
const String message =
// ignore: lines_longer_than_80_chars
'Hey this is a QR code. Change this value in the main_screen.dart file.';

View File

@ -2,13 +2,12 @@ name: example
description: >
The QR.Flutter example app.
version: 2.0.0
author: Yakka, LLC <hello@yakka.agency>
homepage: https://github.com/lukef/qr.flutter
publish_to: none
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.7.0"
sdk: '>=2.19.6 <3.0.0'
flutter: ">=3.7.0"
dependencies:
flutter:

View File

@ -1,29 +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:example/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments smoke test', (tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(ExampleApp());
// 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);
});
}

View File

@ -8,19 +8,23 @@ 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.toString()}:$posKey';
return '$element:$posKey';
}
/// Save a [Paint] for the provided element and position into the cache.
void cache(Paint paint, QrCodeElement element,
{FinderPatternPosition? position}) {
void cache(
Paint paint,
QrCodeElement element, {
FinderPatternPosition? position,
}) {
if (element == QrCodeElement.codePixel) {
_pixelPaints.add(paint);
} else {
@ -31,23 +35,21 @@ class PaintCache {
/// Retrieve the first [Paint] object from the paint cache for the provided
/// element and position.
Paint? firstPaint(QrCodeElement element, {FinderPatternPosition? position}) {
if (element == QrCodeElement.codePixel) {
return _pixelPaints.first;
} else {
return _keyedPaints[_cacheKey(element, position: 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}) {
if (element == QrCodeElement.codePixel) {
return _pixelPaints;
} else {
return <Paint?>[_keyedPaints[_cacheKey(element, position: position)]];
}
List<Paint?> paints(
QrCodeElement element, {
FinderPatternPosition? position,
}) {
return element == QrCodeElement.codePixel
? _pixelPaints
: <Paint?>[_keyedPaints[_cacheKey(element, position: position)]];
}
}

View File

@ -21,10 +21,11 @@ class QrImageView extends StatefulWidget {
/// using the default options).
QrImageView({
required String data,
Key? key,
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,
@ -42,19 +43,22 @@ class QrImageView extends StatefulWidget {
),
this.embeddedImageEmitsError = false,
this.gradient,
}) : assert(QrVersions.isSupportedVersion(version)),
}) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
),
_data = data,
_qrCode = null,
super(key: key);
_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,
Key? key,
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,
@ -74,13 +78,16 @@ class QrImageView extends StatefulWidget {
),
this.embeddedImageEmitsError = false,
this.gradient,
}) : assert(QrVersions.isSupportedVersion(version)),
}) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
),
_data = null,
_qrCode = qr,
super(key: key);
_qrCode = qr;
// The data passed to the widget
final String? _data;
// The QR code data passed to the widget
final QrCode? _qrCode;
@ -149,7 +156,7 @@ class QrImageView extends StatefulWidget {
final QrDataModuleStyle dataModuleStyle;
@override
_QrImageViewState createState() => _QrImageViewState();
State<QrImageView> createState() => _QrImageViewState();
}
class _QrImageViewState extends State<QrImageView> {
@ -167,55 +174,53 @@ class _QrImageViewState extends State<QrImageView> {
version: widget.version,
errorCorrectionLevel: widget.errorCorrectionLevel,
);
if (_validationResult.isValid) {
_qr = _validationResult.qrCode;
} else {
_qr = null;
}
_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) {
print("snapshot error: ${snapshot.error}");
if (widget.embeddedImageEmitsError) {
return _errorWidget(context, constraints, snapshot.error);
} else {
return _qrWidget(context, null, widgetSize);
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) {
print('loaded image');
final loadedImage = snapshot.data;
return _qrWidget(context, loadedImage, widgetSize);
} else {
return Container();
}
},
);
} else {
return _qrWidget(context, 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(BuildContext context, ui.Image? image, double edgeLength) {
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,
@ -234,40 +239,51 @@ class _QrImageViewState extends State<QrImageView> {
}
Widget _errorWidget(
BuildContext context, BoxConstraints constraints, Object? error) {
BuildContext context,
BoxConstraints constraints,
Object? error,
) {
final errorWidget = widget.errorStateBuilder == null
? Container()
: widget.errorStateBuilder!(context, error);
final errorSideLength = (widget.constrainErrorBounds
final errorSideLength = widget.constrainErrorBounds
? widget.size ?? constraints.biggest.shortestSide
: constraints.biggest.longestSide);
: constraints.biggest.longestSide;
return _QrContentView(
edgeLength: errorSideLength,
backgroundColor: widget.backgroundColor,
padding: widget.padding,
child: errorWidget,
semanticsLabel: widget.semanticsLabel,
child: errorWidget,
);
}
late ImageStreamListener streamListener;
Future<ui.Image> _loadQrImage(
BuildContext buildContext, QrEmbeddedImageStyle? style) async {
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,
));
final stream = widget.embeddedImage!.resolve(
ImageConfiguration(
devicePixelRatio: mq.devicePixelRatio,
),
);
streamListener = ImageStreamListener((info, err) {
stream.removeListener(streamListener);
completer.complete(info.image);
}, onError: (dynamic err, _) {
stream.removeListener(streamListener);
completer.completeError(err);
});
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;
}
@ -278,7 +294,7 @@ class _QrImageViewState extends State<QrImageView> {
typedef QrErrorBuilder = Widget Function(BuildContext context, Object? error);
class _QrContentView extends StatelessWidget {
_QrContentView({
const _QrContentView({
required this.edgeLength,
required this.child,
this.backgroundColor,

View File

@ -19,7 +19,7 @@ import 'validator.dart';
// ignore_for_file: deprecated_member_use_from_same_package
const _finderPatternLimit = 7;
const int _finderPatternLimit = 7;
// default colors for the qr code pixels
const Color _qrDefaultColor = Color(0xff000000);
@ -32,7 +32,11 @@ class QrPainter extends CustomPainter {
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,
@ -40,7 +44,10 @@ class QrPainter extends CustomPainter {
this.eyeStyle = const QrEyeStyle(),
this.dataModuleStyle = const QrDataModuleStyle(),
this.gradient,
}) : assert(QrVersions.isSupportedVersion(version)) {
}) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
) {
_init(data);
}
@ -49,7 +56,11 @@ class QrPainter extends CustomPainter {
/// 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,
@ -112,7 +123,7 @@ class QrPainter extends CustomPainter {
final double _gapSize = 0.25;
/// Cache for all of the [Paint] objects.
final _paintCache = PaintCache();
final PaintCache _paintCache = PaintCache();
void _init(String data) {
if (!QrVersions.isSupportedVersion(version)) {
@ -138,23 +149,33 @@ class QrPainter extends CustomPainter {
// 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);
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);
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);
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,
);
}
}
@ -162,23 +183,34 @@ class QrPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
// if the widget has a zero size side then we cannot continue painting.
if (size.shortestSide == 0) {
print("[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");
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),
gapSize: gapless ? 0 : _gapSize,
);
// draw the finder pattern elements
_drawFinderPatternItem(FinderPatternPosition.topLeft, canvas, paintMetrics);
_drawFinderPatternItem(
FinderPatternPosition.bottomLeft, canvas, paintMetrics);
FinderPatternPosition.topLeft,
canvas,
paintMetrics,
);
_drawFinderPatternItem(
FinderPatternPosition.topRight, canvas, paintMetrics);
FinderPatternPosition.bottomLeft,
canvas,
paintMetrics,
);
_drawFinderPatternItem(
FinderPatternPosition.topRight,
canvas,
paintMetrics,
);
// DEBUG: draw the inner content boundary
// final paint = Paint()..style = ui.PaintingStyle.stroke;
@ -271,10 +303,14 @@ class QrPainter extends CustomPainter {
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;
if (_isFinderPatternPosition(x, y)) {
continue;
}
final isDark = _qrImage.isDark(y, x);
final paint = isDark ? pixelPaint : emptyPixelPaint;
if (!isDark && !isRoundedOutsideCorners) continue;
if (!isDark && !isRoundedOutsideCorners) {
continue;
}
// paint a pixel
final squareRect = _createDataModuleRect(paintMetrics, x, y, gap);
// check safeArea
@ -422,21 +458,25 @@ class QrPainter extends CustomPainter {
}
bool _hasAdjacentVerticalPixel(int x, int y, int moduleCount) {
if (y + 1 >= moduleCount) return false;
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;
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));
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;
}
@ -446,9 +486,10 @@ class QrPainter extends CustomPainter {
_PaintMetrics metrics,
) {
final totalGap = (_finderPatternLimit - 1) * metrics.gapSize;
final radius = ((_finderPatternLimit * metrics.pixelSize) + totalGap) -
metrics.pixelSize;
final strokeAdjust = (metrics.pixelSize / 2.0);
final radius =
((_finderPatternLimit * metrics.pixelSize) + totalGap) -
metrics.pixelSize;
final strokeAdjust = metrics.pixelSize / 2.0;
final edgePos =
(metrics.inset + metrics.innerContentSize) - (radius + strokeAdjust);
@ -463,31 +504,44 @@ class QrPainter extends CustomPainter {
}
// configure the paints
final outerPaint = _paintCache.firstPaint(QrCodeElement.finderPatternOuter,
position: position)!;
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)!;
final innerPaint = _paintCache
.firstPaint(QrCodeElement.finderPatternInner, position: position)!;
innerPaint.strokeWidth = metrics.pixelSize;
innerPaint.color = emptyColor;
final dotPaint = _paintCache.firstPaint(QrCodeElement.finderPatternDot,
position: position);
final dotPaint = _paintCache.firstPaint(
QrCodeElement.finderPatternDot,
position: position,
);
dotPaint!.color = color;
final outerRect = Rect.fromLTWH(offset.dx, offset.dy, radius, radius);
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 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);
final dotRect = Rect.fromLTWH(
offset.dx + metrics.pixelSize + strokeAdjust,
offset.dy + metrics.pixelSize + strokeAdjust,
dotSize,
dotSize,
);
switch(eyeStyle.eyeShape) {
case QrEyeShape.square:
@ -524,7 +578,10 @@ class QrPainter extends CustomPainter {
bool _hasOneNonZeroSide(Size size) => size.longestSide > 0;
Size _scaledAspectSize(
Size widgetSize, Size originalSize, Size? requestedSize) {
Size widgetSize,
Size originalSize,
Size? requestedSize,
) {
if (requestedSize != null && !requestedSize.isEmpty) {
return requestedSize;
} else if (requestedSize != null && _hasOneNonZeroSide(requestedSize)) {
@ -539,7 +596,11 @@ class QrPainter extends CustomPainter {
}
void _drawImageOverlay(
Canvas canvas, Offset position, Size size, QrEmbeddedImageStyle? style) {
Canvas canvas,
Offset position,
Size size,
QrEmbeddedImageStyle? style,
) {
final paint = Paint()
..isAntiAlias = true
..filterQuality = FilterQuality.high;
@ -587,24 +648,26 @@ class QrPainter extends CustomPainter {
}
/// Returns the raw QR code [ui.Image] object.
Future<ui.Image> toImage(double size,
{ui.ImageByteFormat format = ui.ImageByteFormat.png}) async {
return await toPicture(size).toImage(size.toInt(), size.toInt());
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, format: format);
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}) {
_PaintMetrics({
required this.containerSize,
required this.gapSize,
required this.moduleCount,
}) {
_calculateMetrics();
}
@ -623,7 +686,7 @@ class _PaintMetrics {
void _calculateMetrics() {
final gapTotal = (moduleCount - 1) * gapSize;
var pixelSize = (containerSize - gapTotal) / moduleCount;
final pixelSize = (containerSize - gapTotal) / moduleCount;
_pixelSize = (pixelSize * 2).roundToDouble() / 2;
_innerContentSize = (_pixelSize * moduleCount) + gapTotal;
_inset = (containerSize - _innerContentSize) / 2;

View File

@ -4,6 +4,7 @@
* See LICENSE for distribution and usage details.
*/
import 'package:flutter/widgets.dart';
import 'dart:ui';
@ -70,6 +71,7 @@ enum EmbeddedImageShape {
}
/// Styling options for finder pattern eye.
@immutable
class QrEyeStyle {
/// Create a new set of styling options for QR Eye.
const QrEyeStyle({
@ -100,6 +102,7 @@ class QrEyeStyle {
}
/// Styling options for data module.
@immutable
class QrDataModuleStyle {
/// Create a new set of styling options for data modules.
const QrDataModuleStyle({
@ -152,6 +155,7 @@ class QrDataModuleStyle {
}
/// Styling options for any embedded image overlay
@immutable
class QrEmbeddedImageStyle {
/// Create a new set of styling options.
const QrEmbeddedImageStyle({

View File

@ -30,10 +30,14 @@ class QrValidator {
);
}
return QrValidationResult(
status: QrValidationStatus.valid, qrCode: qrCode);
} on InputTooLongException catch (itle) {
status: QrValidationStatus.valid,
qrCode: qrCode,
);
} on InputTooLongException catch (title) {
return QrValidationResult(
status: QrValidationStatus.contentTooLong, error: itle);
status: QrValidationStatus.contentTooLong,
error: title,
);
} on Exception catch (ex) {
return QrValidationResult(status: QrValidationStatus.error, error: ex);
}

View File

@ -2,22 +2,21 @@ 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.0.1
version: 4.1.0
homepage: https://github.com/theyakka/qr.flutter
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.16.0"
sdk: '>=2.19.6 <4.0.0'
flutter: ">=3.7.0"
dependencies:
flutter:
sdk: flutter
qr: ^3.0.0
qr: ^3.0.1
dev_dependencies:
flutter_test:
sdk: flutter
test: ^1.16.5
flutter:
uses-material-design: false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -11,7 +11,9 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:qr_flutter/qr_flutter.dart';
void main() {
testWidgets('QrImageView generates correct image', (tester) async {
testWidgets('QrImageView generates correct image', (
tester,
) async {
final qrImage = MaterialApp(
home: Center(
child: RepaintBoundary(
@ -31,143 +33,156 @@ void main() {
);
});
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,
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(
'QrImageView generates correct image with eye style',
(tester) async {
final qrImage = MaterialApp(
home: Center(
child: RepaintBoundary(
child: QrImageView(
data: 'This is a a qr code with a logo',
data: 'This is a test image',
version: QrVersions.auto,
gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L,
embeddedImage: FileImage(File('test/.images/logo_yakka.png')),
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.circle,
color: Colors.green,
),
),
),
),
),
['test/.images/logo_yakka.png'],
);
);
await tester.pumpWidget(qrImage);
await expectLater(
find.byType(QrImageView),
matchesGoldenFile('./.golden/qr_image_eye_styled_golden.png'),
);
},
);
await tester.pumpAndSettle();
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'),
);
},
);
await expectLater(
find.byType(QrImageView),
matchesGoldenFile('./.golden/qr_image_logo_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.
@ -180,19 +195,24 @@ Future<void> pumpWidgetWithImages(
) async {
Future<void>? precacheFuture;
await tester.pumpWidget(
Builder(builder: (buildContext) {
precacheFuture = tester.runAsync(() async {
await Future.wait([
for (final assetName in assetNames)
precacheImage(FileImage(File(assetName)), buildContext),
]);
});
return widget;
}),
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: MediaQueryData(), child: MaterialApp(home: widget));
return MediaQuery(
data: const MediaQueryData(),
child: MaterialApp(home: widget),
);
}

View File

@ -22,9 +22,9 @@ void main() {
imageData = await painter.toImageData(600.0);
});
final imageBytes = imageData!.buffer.asUint8List();
final widget = Center(
final Widget widget = Center(
child: RepaintBoundary(
child: Container(
child: SizedBox(
width: 600,
height: 600,
child: Image.memory(imageBytes),