Merge branch 'master' of https://github.com/theyakka/qr.flutter
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
1
AUTHORS
@ -6,3 +6,4 @@ Dmitry: @kelegorm
|
||||
Pedro Massango: @pedromassango
|
||||
Luke Pighetti: @lukepighetti
|
||||
Jonas Zebari: @jonas-zebari
|
||||
Ivan Semkin: @vanyasem
|
@ -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`
|
||||
|
30
README.md
@ -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,
|
||||
),
|
||||
@ -65,20 +65,21 @@ 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). |
|
||||
| `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.|
|
||||
| `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.
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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"/>
|
||||
|
@ -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>
|
||||
|
@ -8,5 +8,4 @@
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -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"/>
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
<string>11.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -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";
|
||||
|
@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -41,5 +41,9 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -16,12 +16,14 @@ class ExampleApp extends StatelessWidget {
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.white,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarColor: Colors.white,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
));
|
||||
),
|
||||
);
|
||||
return MaterialApp(
|
||||
title: 'QR.Flutter',
|
||||
theme: ThemeData.light(),
|
||||
|
@ -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.';
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
@ -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)]];
|
||||
}
|
||||
}
|
||||
|
@ -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,23 +174,21 @@ 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) {
|
||||
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;
|
||||
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.
|
||||
@ -191,31 +196,31 @@ class _QrImageViewState extends State<QrImageView> {
|
||||
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);
|
||||
}
|
||||
debugPrint('snapshot error: ${snapshot.error}');
|
||||
return widget.embeddedImageEmitsError
|
||||
? _errorWidget(context, constraints, snapshot.error)
|
||||
: _qrWidget(null, widgetSize);
|
||||
}
|
||||
if (snapshot.hasData) {
|
||||
print('loaded image');
|
||||
debugPrint('loaded image');
|
||||
final loadedImage = snapshot.data;
|
||||
return _qrWidget(context, loadedImage, widgetSize);
|
||||
return _qrWidget(loadedImage, widgetSize);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return _qrWidget(context, null, widgetSize);
|
||||
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(
|
||||
final stream = widget.embeddedImage!.resolve(
|
||||
ImageConfiguration(
|
||||
devicePixelRatio: mq.devicePixelRatio,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
streamListener = ImageStreamListener((info, err) {
|
||||
streamListener = ImageStreamListener(
|
||||
(info, err) {
|
||||
stream.removeListener(streamListener);
|
||||
completer.complete(info.image);
|
||||
}, onError: (dynamic err, _) {
|
||||
},
|
||||
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,
|
||||
|
@ -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) -
|
||||
final radius =
|
||||
((_finderPatternLimit * metrics.pixelSize) + totalGap) -
|
||||
metrics.pixelSize;
|
||||
final strokeAdjust = (metrics.pixelSize / 2.0);
|
||||
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,
|
||||
_PaintMetrics({
|
||||
required this.containerSize,
|
||||
required this.gapSize,
|
||||
required this.moduleCount}) {
|
||||
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;
|
||||
|
@ -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({
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
@ -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,7 +33,8 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('QrImageView generates correct image with eye style',
|
||||
testWidgets(
|
||||
'QrImageView generates correct image with eye style',
|
||||
(tester) async {
|
||||
final qrImage = MaterialApp(
|
||||
home: Center(
|
||||
@ -54,9 +57,11 @@ void main() {
|
||||
find.byType(QrImageView),
|
||||
matchesGoldenFile('./.golden/qr_image_eye_styled_golden.png'),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('QrImageView generates correct image with data module style',
|
||||
testWidgets(
|
||||
'QrImageView generates correct image with data module style',
|
||||
(tester) async {
|
||||
final qrImage = MaterialApp(
|
||||
home: Center(
|
||||
@ -79,7 +84,8 @@ void main() {
|
||||
find.byType(QrImageView),
|
||||
matchesGoldenFile('./.golden/qr_image_data_module_styled_golden.png'),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'QrImageView generates correct image with eye and data module sytle',
|
||||
@ -107,13 +113,17 @@ void main() {
|
||||
await tester.pumpWidget(qrImage);
|
||||
await expectLater(
|
||||
find.byType(QrImageView),
|
||||
matchesGoldenFile('./.golden/qr_image_eye_data_module_styled_golden.png'),
|
||||
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 {
|
||||
'color is also specified',
|
||||
(tester) async {
|
||||
final qrImage = MaterialApp(
|
||||
home: Center(
|
||||
child: RepaintBoundary(
|
||||
@ -121,6 +131,7 @@ void main() {
|
||||
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(
|
||||
@ -140,9 +151,12 @@ void main() {
|
||||
find.byType(QrImageView),
|
||||
matchesGoldenFile('./.golden/qr_image_foreground_colored_golden.png'),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('QrImageView generates correct image with logo', (tester) async {
|
||||
testWidgets(
|
||||
'QrImageView generates correct image with logo',
|
||||
(tester) async {
|
||||
await pumpWidgetWithImages(
|
||||
tester,
|
||||
MaterialApp(
|
||||
@ -158,7 +172,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
['test/.images/logo_yakka.png'],
|
||||
<String>['test/.images/logo_yakka.png'],
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
@ -167,7 +181,8 @@ void main() {
|
||||
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) {
|
||||
Builder(
|
||||
builder: (buildContext) {
|
||||
precacheFuture = tester.runAsync(() async {
|
||||
await Future.wait([
|
||||
for (final assetName in assetNames)
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
|