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 Dmitry: @kelegorm
Pedro Massango: @pedromassango Pedro Massango: @pedromassango
Luke Pighetti: @lukepighetti 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 # 4.0.1
- Bump `qr` dependency (from `^2.0.0` to `^3.0.0`). - Bump `qr` dependency (from `^2.0.0` to `^3.0.0`).
- **BREAKING**: Rename `QrImage` to `QrImageView` - **BREAKING**: Rename `QrImage` to `QrImageView`

View File

@ -28,7 +28,7 @@ You should add the following to your `pubspec.yaml` file:
```yaml ```yaml
dependencies: 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`: **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: dependencies:
qr_flutter: qr_flutter:
git: 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. 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): Next, to render a basic QR code you can use the following code (or something like it):
```dart ```dart
QrImage( QrImageView(
data: "1234567890", data: '1234567890',
version: QrVersions.auto, version: QrVersions.auto,
size: 200.0, 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: Depending on your data requirements you may want to tweak the QR code output. The following options are available:
| Property | Type | Description | | 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. | | `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`. | | `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. | | `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. | | `padding` | EdgeInsets | Padding surrounding the QR code data. |
| `backgroundColor` | Color | The background color (default is none). | | `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. |
| `gapless` | bool | Adds an extra pixel in size to prevent gaps (default is true). | | `dataModuleStyle` | QrDataModuleStyle | Configures the shape and the color of the dots. |
| `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). | | `gapless` | bool | Adds an extra pixel in size to prevent gaps (default is true). |
| `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. | | `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). |
| `embeddedImage` | ImageProvider | An `ImageProvider` that defines an image to be overlaid in the center of the QR code. | | `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. |
| `embeddedImageStyle` | QrEmbeddedImageStyle | Properties to style the embedded image. | | `embeddedImage` | ImageProvider | An `ImageProvider` that defines an image to be overlaid in the center of the QR code. |
| `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. | | `embeddedImageStyle` | QrEmbeddedImageStyle | Properties to style the embedded image. |
|`semanticsLabel`|String|`semanticsLabel` will be used by screen readers to describe the content of the QR code.| | `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 # 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: A basic QR code will look something like:
```dart ```dart
QrImage( QrImageView(
data: 'This is a simple QR code', data: 'This is a simple QR code',
version: QrVersions.auto, version: QrVersions.auto,
size: 320, size: 320,
@ -101,7 +102,7 @@ QrImage(
A QR code with an image (from your application's assets) will look like: A QR code with an image (from your application's assets) will look like:
```dart ```dart
QrImage( QrImageView(
data: 'This QR code has an embedded image as well', data: 'This QR code has an embedded image as well',
version: QrVersions.auto, version: QrVersions.auto,
size: 320, size: 320,
@ -116,7 +117,7 @@ QrImage(
To show an error state in the event that the QR code can't be validated: To show an error state in the event that the QR code can't be validated:
```dart ```dart
QrImage( QrImageView(
data: 'This QR code will show the error state instead', data: 'This QR code will show the error state instead',
version: 1, version: 1,
size: 320, size: 320,
@ -125,7 +126,7 @@ QrImage(
return Container( return Container(
child: Center( child: Center(
child: Text( child: Text(
"Uh oh! Something went wrong...", 'Uh oh! Something went wrong...',
textAlign: TextAlign.center, 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 # Outro
## Credits ## Credits
Thanks to Kevin Moore for his awesome [QR - Dart](https://github.com/kevmoo/qr.dart) library. It's the core of this library. 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 # libraries
# classes # classes
- one_member_abstracts # avoid - one_member_abstracts # avoid
- avoid_classes_with_only_static_members # avoid
- public_member_api_docs - public_member_api_docs
# constructors # constructors
- prefer_constructors_over_static_methods - prefer_constructors_over_static_methods

View File

@ -26,7 +26,16 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 33 compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
@ -39,7 +48,7 @@ android {
defaultConfig { defaultConfig {
applicationId "app.yakka.example" applicationId "app.yakka.example"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 33 targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -59,7 +68,7 @@ flutter {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.yakka.example"> 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. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>

View File

@ -8,21 +8,16 @@
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<application <application
android:label="example" android:label="example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" 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:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
@ -32,5 +27,10 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </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> </application>
</manifest> </manifest>

View File

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

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.yakka.example"> 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. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>

View File

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

View File

@ -1,6 +1,5 @@
#Sun Mar 14 10:07:12 PDT 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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' include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
def plugins = new Properties() assert localPropertiesFile.exists()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path -> def flutterSdkPath = properties.getProperty("flutter.sdk")
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
include ":$name" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
project(":$name").projectDir = pluginDirectory
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,12 +16,14 @@ class ExampleApp extends StatelessWidget {
// This widget is the root of your application. // This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
statusBarColor: Colors.white, const SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.dark, statusBarColor: Colors.white,
systemNavigationBarColor: Colors.white, statusBarIconBrightness: Brightness.dark,
systemNavigationBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.white,
)); systemNavigationBarIconBrightness: Brightness.dark,
),
);
return MaterialApp( return MaterialApp(
title: 'QR.Flutter', title: 'QR.Flutter',
theme: ThemeData.light(), 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 /// This is the screen that you'll see when the app starts
class MainScreen extends StatefulWidget { class MainScreen extends StatefulWidget {
@override @override
_MainScreenState createState() => _MainScreenState(); State<MainScreen> createState() => _MainScreenState();
} }
class _MainScreenState extends State<MainScreen> { class _MainScreenState extends State<MainScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final message = const String message =
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
'Hey this is a QR code. Change this value in the main_screen.dart file.'; 'Hey this is a QR code. Change this value in the main_screen.dart file.';

View File

@ -2,13 +2,12 @@ name: example
description: > description: >
The QR.Flutter example app. The QR.Flutter example app.
version: 2.0.0 version: 2.0.0
author: Yakka, LLC <hello@yakka.agency>
homepage: https://github.com/lukef/qr.flutter homepage: https://github.com/lukef/qr.flutter
publish_to: none publish_to: none
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: '>=2.19.6 <3.0.0'
flutter: ">=1.7.0" flutter: ">=3.7.0"
dependencies: dependencies:
flutter: 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'; import 'types.dart';
/// /// Caches painter objects so we do have to recreate them and waste expensive
/// cycles.
class PaintCache { class PaintCache {
final List<Paint> _pixelPaints = <Paint>[]; final List<Paint> _pixelPaints = <Paint>[];
final Map<String, Paint> _keyedPaints = <String, Paint>{}; final Map<String, Paint> _keyedPaints = <String, Paint>{};
String _cacheKey(QrCodeElement element, {FinderPatternPosition? position}) { String _cacheKey(QrCodeElement element, {FinderPatternPosition? position}) {
final posKey = position != null ? position.toString() : 'any'; 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. /// Save a [Paint] for the provided element and position into the cache.
void cache(Paint paint, QrCodeElement element, void cache(
{FinderPatternPosition? position}) { Paint paint,
QrCodeElement element, {
FinderPatternPosition? position,
}) {
if (element == QrCodeElement.codePixel) { if (element == QrCodeElement.codePixel) {
_pixelPaints.add(paint); _pixelPaints.add(paint);
} else { } else {
@ -31,23 +35,21 @@ class PaintCache {
/// Retrieve the first [Paint] object from the paint cache for the provided /// Retrieve the first [Paint] object from the paint cache for the provided
/// element and position. /// element and position.
Paint? firstPaint(QrCodeElement element, {FinderPatternPosition? position}) { Paint? firstPaint(QrCodeElement element, {FinderPatternPosition? position}) {
if (element == QrCodeElement.codePixel) { return element == QrCodeElement.codePixel
return _pixelPaints.first; ? _pixelPaints.first
} else { : _keyedPaints[_cacheKey(element, position: position)];
return _keyedPaints[_cacheKey(element, position: position)];
}
} }
/// Retrieve all [Paint] objects from the paint cache for the provided /// Retrieve all [Paint] objects from the paint cache for the provided
/// element and position. Note: Finder pattern elements can only have a max /// 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] /// one [Paint] object per position. As such they will always return a [List]
/// with a fixed size of `1`. /// with a fixed size of `1`.
List<Paint?> paints(QrCodeElement element, List<Paint?> paints(
{FinderPatternPosition? position}) { QrCodeElement element, {
if (element == QrCodeElement.codePixel) { FinderPatternPosition? position,
return _pixelPaints; }) {
} else { return element == QrCodeElement.codePixel
return <Paint?>[_keyedPaints[_cacheKey(element, position: position)]]; ? _pixelPaints
} : <Paint?>[_keyedPaints[_cacheKey(element, position: position)]];
} }
} }

View File

@ -21,10 +21,11 @@ class QrImageView extends StatefulWidget {
/// using the default options). /// using the default options).
QrImageView({ QrImageView({
required String data, required String data,
Key? key, super.key,
this.size, this.size,
this.padding = const EdgeInsets.all(10.0), this.padding = const EdgeInsets.all(10.0),
this.backgroundColor = Colors.transparent, this.backgroundColor = Colors.transparent,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.foregroundColor = Colors.black, this.foregroundColor = Colors.black,
this.version = QrVersions.auto, this.version = QrVersions.auto,
this.errorCorrectionLevel = QrErrorCorrectLevel.L, this.errorCorrectionLevel = QrErrorCorrectLevel.L,
@ -42,19 +43,22 @@ class QrImageView extends StatefulWidget {
), ),
this.embeddedImageEmitsError = false, this.embeddedImageEmitsError = false,
this.gradient, this.gradient,
}) : assert(QrVersions.isSupportedVersion(version)), }) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
),
_data = data, _data = data,
_qrCode = null, _qrCode = null;
super(key: key);
/// Create a new QR code using the [QrCode] data and the passed options (or /// Create a new QR code using the [QrCode] data and the passed options (or
/// using the default options). /// using the default options).
QrImageView.withQr({ QrImageView.withQr({
required QrCode qr, required QrCode qr,
Key? key, super.key,
this.size, this.size,
this.padding = const EdgeInsets.all(10.0), this.padding = const EdgeInsets.all(10.0),
this.backgroundColor = Colors.transparent, this.backgroundColor = Colors.transparent,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.foregroundColor = Colors.black, this.foregroundColor = Colors.black,
this.version = QrVersions.auto, this.version = QrVersions.auto,
this.errorCorrectionLevel = QrErrorCorrectLevel.L, this.errorCorrectionLevel = QrErrorCorrectLevel.L,
@ -74,13 +78,16 @@ class QrImageView extends StatefulWidget {
), ),
this.embeddedImageEmitsError = false, this.embeddedImageEmitsError = false,
this.gradient, this.gradient,
}) : assert(QrVersions.isSupportedVersion(version)), }) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
),
_data = null, _data = null,
_qrCode = qr, _qrCode = qr;
super(key: key);
// The data passed to the widget // The data passed to the widget
final String? _data; final String? _data;
// The QR code data passed to the widget // The QR code data passed to the widget
final QrCode? _qrCode; final QrCode? _qrCode;
@ -149,7 +156,7 @@ class QrImageView extends StatefulWidget {
final QrDataModuleStyle dataModuleStyle; final QrDataModuleStyle dataModuleStyle;
@override @override
_QrImageViewState createState() => _QrImageViewState(); State<QrImageView> createState() => _QrImageViewState();
} }
class _QrImageViewState extends State<QrImageView> { class _QrImageViewState extends State<QrImageView> {
@ -167,55 +174,53 @@ class _QrImageViewState extends State<QrImageView> {
version: widget.version, version: widget.version,
errorCorrectionLevel: widget.errorCorrectionLevel, errorCorrectionLevel: widget.errorCorrectionLevel,
); );
if (_validationResult.isValid) { _qr = _validationResult.isValid ? _validationResult.qrCode : null;
_qr = _validationResult.qrCode;
} else {
_qr = null;
}
} else if (widget._qrCode != null) { } else if (widget._qrCode != null) {
_qr = widget._qrCode; _qr = widget._qrCode;
_validationResult = _validationResult =
QrValidationResult(status: QrValidationStatus.valid, qrCode: _qr); QrValidationResult(status: QrValidationStatus.valid, qrCode: _qr);
} }
return LayoutBuilder(builder: (context, constraints) { return LayoutBuilder(
// validation failed, show an error state widget if builder is present. builder: (context, constraints) {
if (!_validationResult.isValid) { // validation failed, show an error state widget if builder is present.
return _errorWidget(context, constraints, _validationResult.error); if (!_validationResult.isValid) {
} return _errorWidget(context, constraints, _validationResult.error);
// no error, build the regular widget }
final widgetSize = widget.size ?? constraints.biggest.shortestSide; // no error, build the regular widget
if (widget.embeddedImage != null) { final widgetSize =
// if requesting to embed an image then we need to load via a widget.size ?? constraints.biggest.shortestSide;
// FutureBuilder because the image provider will be async. if (widget.embeddedImage != null) {
return FutureBuilder<ui.Image>( // if requesting to embed an image then we need to load via a
future: _loadQrImage(context, widget.embeddedImageStyle), // FutureBuilder because the image provider will be async.
builder: (ctx, snapshot) { return FutureBuilder<ui.Image>(
if (snapshot.error != null) { future: _loadQrImage(context, widget.embeddedImageStyle),
print("snapshot error: ${snapshot.error}"); builder: (ctx, snapshot) {
if (widget.embeddedImageEmitsError) { if (snapshot.error != null) {
return _errorWidget(context, constraints, snapshot.error); debugPrint('snapshot error: ${snapshot.error}');
} else { return widget.embeddedImageEmitsError
return _qrWidget(context, null, widgetSize); ? _errorWidget(context, constraints, snapshot.error)
: _qrWidget(null, widgetSize);
} }
} if (snapshot.hasData) {
if (snapshot.hasData) { debugPrint('loaded image');
print('loaded image'); final loadedImage = snapshot.data;
final loadedImage = snapshot.data; return _qrWidget(loadedImage, widgetSize);
return _qrWidget(context, loadedImage, widgetSize); } else {
} else { return Container();
return Container(); }
} },
}, );
); } else {
} else { return _qrWidget(null, widgetSize);
return _qrWidget(context, null, widgetSize); }
} },
}); );
} }
Widget _qrWidget(BuildContext context, ui.Image? image, double edgeLength) { Widget _qrWidget(ui.Image? image, double edgeLength) {
final painter = QrPainter.withQr( final painter = QrPainter.withQr(
qr: _qr!, qr: _qr!,
// ignore: deprecated_member_use_from_same_package
color: widget.foregroundColor, color: widget.foregroundColor,
gapless: widget.gapless, gapless: widget.gapless,
embeddedImageStyle: widget.embeddedImageStyle, embeddedImageStyle: widget.embeddedImageStyle,
@ -234,40 +239,51 @@ class _QrImageViewState extends State<QrImageView> {
} }
Widget _errorWidget( Widget _errorWidget(
BuildContext context, BoxConstraints constraints, Object? error) { BuildContext context,
BoxConstraints constraints,
Object? error,
) {
final errorWidget = widget.errorStateBuilder == null final errorWidget = widget.errorStateBuilder == null
? Container() ? Container()
: widget.errorStateBuilder!(context, error); : widget.errorStateBuilder!(context, error);
final errorSideLength = (widget.constrainErrorBounds final errorSideLength = widget.constrainErrorBounds
? widget.size ?? constraints.biggest.shortestSide ? widget.size ?? constraints.biggest.shortestSide
: constraints.biggest.longestSide); : constraints.biggest.longestSide;
return _QrContentView( return _QrContentView(
edgeLength: errorSideLength, edgeLength: errorSideLength,
backgroundColor: widget.backgroundColor, backgroundColor: widget.backgroundColor,
padding: widget.padding, padding: widget.padding,
child: errorWidget,
semanticsLabel: widget.semanticsLabel, semanticsLabel: widget.semanticsLabel,
child: errorWidget,
); );
} }
late ImageStreamListener streamListener; late ImageStreamListener streamListener;
Future<ui.Image> _loadQrImage( Future<ui.Image> _loadQrImage(
BuildContext buildContext, QrEmbeddedImageStyle? style) async { BuildContext buildContext,
QrEmbeddedImageStyle? style,
) {
if (style != null) {} if (style != null) {}
final mq = MediaQuery.of(buildContext); final mq = MediaQuery.of(buildContext);
final completer = Completer<ui.Image>(); final completer = Completer<ui.Image>();
final stream = widget.embeddedImage!.resolve(ImageConfiguration( final stream = widget.embeddedImage!.resolve(
devicePixelRatio: mq.devicePixelRatio, ImageConfiguration(
)); devicePixelRatio: mq.devicePixelRatio,
),
);
streamListener = ImageStreamListener((info, err) { streamListener = ImageStreamListener(
stream.removeListener(streamListener); (info, err) {
completer.complete(info.image); stream.removeListener(streamListener);
}, onError: (dynamic err, _) { completer.complete(info.image);
stream.removeListener(streamListener); },
completer.completeError(err); onError: (err, _) {
}); stream.removeListener(streamListener);
completer.completeError(err);
},
);
stream.addListener(streamListener); stream.addListener(streamListener);
return completer.future; return completer.future;
} }
@ -278,7 +294,7 @@ class _QrImageViewState extends State<QrImageView> {
typedef QrErrorBuilder = Widget Function(BuildContext context, Object? error); typedef QrErrorBuilder = Widget Function(BuildContext context, Object? error);
class _QrContentView extends StatelessWidget { class _QrContentView extends StatelessWidget {
_QrContentView({ const _QrContentView({
required this.edgeLength, required this.edgeLength,
required this.child, required this.child,
this.backgroundColor, this.backgroundColor,

View File

@ -19,7 +19,7 @@ import 'validator.dart';
// ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: deprecated_member_use_from_same_package
const _finderPatternLimit = 7; const int _finderPatternLimit = 7;
// default colors for the qr code pixels // default colors for the qr code pixels
const Color _qrDefaultColor = Color(0xff000000); const Color _qrDefaultColor = Color(0xff000000);
@ -32,7 +32,11 @@ class QrPainter extends CustomPainter {
required String data, required String data,
required this.version, required this.version,
this.errorCorrectionLevel = QrErrorCorrectLevel.L, this.errorCorrectionLevel = QrErrorCorrectLevel.L,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.color = _qrDefaultColor, this.color = _qrDefaultColor,
@Deprecated(
'You should use the background color value of your container widget',
)
this.emptyColor = _qrDefaultEmptyColor, this.emptyColor = _qrDefaultEmptyColor,
this.gapless = false, this.gapless = false,
this.embeddedImage, this.embeddedImage,
@ -40,7 +44,10 @@ class QrPainter extends CustomPainter {
this.eyeStyle = const QrEyeStyle(), this.eyeStyle = const QrEyeStyle(),
this.dataModuleStyle = const QrDataModuleStyle(), this.dataModuleStyle = const QrDataModuleStyle(),
this.gradient, this.gradient,
}) : assert(QrVersions.isSupportedVersion(version)) { }) : assert(
QrVersions.isSupportedVersion(version),
'QR code version $version is not supported',
) {
_init(data); _init(data);
} }
@ -49,7 +56,11 @@ class QrPainter extends CustomPainter {
/// flow or for when you need to pre-validate the QR data. /// flow or for when you need to pre-validate the QR data.
QrPainter.withQr({ QrPainter.withQr({
required QrCode qr, required QrCode qr,
@Deprecated('use colors in eyeStyle and dataModuleStyle instead')
this.color = _qrDefaultColor, this.color = _qrDefaultColor,
@Deprecated(
'You should use the background color value of your container widget',
)
this.emptyColor = _qrDefaultEmptyColor, this.emptyColor = _qrDefaultEmptyColor,
this.gapless = false, this.gapless = false,
this.embeddedImage, this.embeddedImage,
@ -112,7 +123,7 @@ class QrPainter extends CustomPainter {
final double _gapSize = 0.25; final double _gapSize = 0.25;
/// Cache for all of the [Paint] objects. /// Cache for all of the [Paint] objects.
final _paintCache = PaintCache(); final PaintCache _paintCache = PaintCache();
void _init(String data) { void _init(String data) {
if (!QrVersions.isSupportedVersion(version)) { 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 // Cache the pixel paint object. For now there is only one but we might
// expand it to multiple later (e.g.: different colours). // expand it to multiple later (e.g.: different colours).
_paintCache.cache( _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 // Cache the empty pixel paint object. Empty color is deprecated and will go
// away. // away.
_paintCache.cache( _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 // Cache the finder pattern painters. We'll keep one for each one in case
// we want to provide customization options later. // we want to provide customization options later.
for (final position in FinderPatternPosition.values) { 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( _paintCache.cache(
Paint()..style = PaintingStyle.fill, QrCodeElement.finderPatternDot, Paint()..style = PaintingStyle.stroke,
position: position); 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) { void paint(Canvas canvas, Size size) {
// if the widget has a zero size side then we cannot continue painting. // if the widget has a zero size side then we cannot continue painting.
if (size.shortestSide == 0) { if (size.shortestSide == 0) {
print("[QR] WARN: width or height is zero. You should set a 'size' value " debugPrint(
"or nest this painter in a Widget that defines a non-zero size"); "[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; return;
} }
final paintMetrics = _PaintMetrics( final paintMetrics = _PaintMetrics(
containerSize: size.shortestSide, containerSize: size.shortestSide,
moduleCount: _qr!.moduleCount, moduleCount: _qr!.moduleCount,
gapSize: (gapless ? 0 : _gapSize), gapSize: gapless ? 0 : _gapSize,
); );
// draw the finder pattern elements // draw the finder pattern elements
_drawFinderPatternItem(FinderPatternPosition.topLeft, canvas, paintMetrics);
_drawFinderPatternItem( _drawFinderPatternItem(
FinderPatternPosition.bottomLeft, canvas, paintMetrics); FinderPatternPosition.topLeft,
canvas,
paintMetrics,
);
_drawFinderPatternItem( _drawFinderPatternItem(
FinderPatternPosition.topRight, canvas, paintMetrics); FinderPatternPosition.bottomLeft,
canvas,
paintMetrics,
);
_drawFinderPatternItem(
FinderPatternPosition.topRight,
canvas,
paintMetrics,
);
// DEBUG: draw the inner content boundary // DEBUG: draw the inner content boundary
// final paint = Paint()..style = ui.PaintingStyle.stroke; // 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 x = 0; x < _qr!.moduleCount; x++) {
for (var y = 0; y < _qr!.moduleCount; y++) { for (var y = 0; y < _qr!.moduleCount; y++) {
// draw the finder patterns independently // draw the finder patterns independently
if (_isFinderPatternPosition(x, y)) continue; if (_isFinderPatternPosition(x, y)) {
continue;
}
final isDark = _qrImage.isDark(y, x); final isDark = _qrImage.isDark(y, x);
final paint = isDark ? pixelPaint : emptyPixelPaint; final paint = isDark ? pixelPaint : emptyPixelPaint;
if (!isDark && !isRoundedOutsideCorners) continue; if (!isDark && !isRoundedOutsideCorners) {
continue;
}
// paint a pixel // paint a pixel
final squareRect = _createDataModuleRect(paintMetrics, x, y, gap); final squareRect = _createDataModuleRect(paintMetrics, x, y, gap);
// check safeArea // check safeArea
@ -422,21 +458,25 @@ class QrPainter extends CustomPainter {
} }
bool _hasAdjacentVerticalPixel(int x, int y, int moduleCount) { 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); return _qrImage.isDark(y + 1, x);
} }
bool _hasAdjacentHorizontalPixel(int x, int y, int moduleCount) { 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); return _qrImage.isDark(y, x + 1);
} }
bool _isFinderPatternPosition(int x, int y) { bool _isFinderPatternPosition(int x, int y) {
final isTopLeft = (y < _finderPatternLimit && x < _finderPatternLimit); final isTopLeft = y < _finderPatternLimit && x < _finderPatternLimit;
final isBottomLeft = (y < _finderPatternLimit && final isBottomLeft = y < _finderPatternLimit &&
(x >= _qr!.moduleCount - _finderPatternLimit)); (x >= _qr!.moduleCount - _finderPatternLimit);
final isTopRight = (y >= _qr!.moduleCount - _finderPatternLimit && final isTopRight = y >= _qr!.moduleCount - _finderPatternLimit &&
(x < _finderPatternLimit)); (x < _finderPatternLimit);
return isTopLeft || isBottomLeft || isTopRight; return isTopLeft || isBottomLeft || isTopRight;
} }
@ -446,9 +486,10 @@ class QrPainter extends CustomPainter {
_PaintMetrics metrics, _PaintMetrics metrics,
) { ) {
final totalGap = (_finderPatternLimit - 1) * metrics.gapSize; final totalGap = (_finderPatternLimit - 1) * metrics.gapSize;
final radius = ((_finderPatternLimit * metrics.pixelSize) + totalGap) - final radius =
metrics.pixelSize; ((_finderPatternLimit * metrics.pixelSize) + totalGap) -
final strokeAdjust = (metrics.pixelSize / 2.0); metrics.pixelSize;
final strokeAdjust = metrics.pixelSize / 2.0;
final edgePos = final edgePos =
(metrics.inset + metrics.innerContentSize) - (radius + strokeAdjust); (metrics.inset + metrics.innerContentSize) - (radius + strokeAdjust);
@ -463,31 +504,44 @@ class QrPainter extends CustomPainter {
} }
// configure the paints // configure the paints
final outerPaint = _paintCache.firstPaint(QrCodeElement.finderPatternOuter, final outerPaint = _paintCache.firstPaint(
position: position)!; QrCodeElement.finderPatternOuter,
position: position,
)!;
final color = _priorityColor(eyeStyle.color); final color = _priorityColor(eyeStyle.color);
outerPaint.strokeWidth = metrics.pixelSize; outerPaint.strokeWidth = metrics.pixelSize;
outerPaint.color = color; outerPaint.color = color;
final innerPaint = _paintCache.firstPaint(QrCodeElement.finderPatternInner, final innerPaint = _paintCache
position: position)!; .firstPaint(QrCodeElement.finderPatternInner, position: position)!;
innerPaint.strokeWidth = metrics.pixelSize; innerPaint.strokeWidth = metrics.pixelSize;
innerPaint.color = emptyColor; innerPaint.color = emptyColor;
final dotPaint = _paintCache.firstPaint(QrCodeElement.finderPatternDot, final dotPaint = _paintCache.firstPaint(
position: position); QrCodeElement.finderPatternDot,
position: position,
);
dotPaint!.color = color; 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 innerRadius = radius - (2 * metrics.pixelSize);
final innerRect = Rect.fromLTWH(offset.dx + metrics.pixelSize, final innerRect = Rect.fromLTWH(
offset.dy + metrics.pixelSize, innerRadius, innerRadius); offset.dx + metrics.pixelSize,
offset.dy + metrics.pixelSize,
innerRadius,
innerRadius,
);
final gap = metrics.pixelSize * 2; final gap = metrics.pixelSize * 2;
final dotSize = radius - gap - (2 * strokeAdjust); final dotSize = radius - gap - (2 * strokeAdjust);
final dotRect = Rect.fromLTWH(offset.dx + metrics.pixelSize + strokeAdjust, final dotRect = Rect.fromLTWH(
offset.dy + metrics.pixelSize + strokeAdjust, dotSize, dotSize); offset.dx + metrics.pixelSize + strokeAdjust,
offset.dy + metrics.pixelSize + strokeAdjust,
dotSize,
dotSize,
);
switch(eyeStyle.eyeShape) { switch(eyeStyle.eyeShape) {
case QrEyeShape.square: case QrEyeShape.square:
@ -524,7 +578,10 @@ class QrPainter extends CustomPainter {
bool _hasOneNonZeroSide(Size size) => size.longestSide > 0; bool _hasOneNonZeroSide(Size size) => size.longestSide > 0;
Size _scaledAspectSize( Size _scaledAspectSize(
Size widgetSize, Size originalSize, Size? requestedSize) { Size widgetSize,
Size originalSize,
Size? requestedSize,
) {
if (requestedSize != null && !requestedSize.isEmpty) { if (requestedSize != null && !requestedSize.isEmpty) {
return requestedSize; return requestedSize;
} else if (requestedSize != null && _hasOneNonZeroSide(requestedSize)) { } else if (requestedSize != null && _hasOneNonZeroSide(requestedSize)) {
@ -539,7 +596,11 @@ class QrPainter extends CustomPainter {
} }
void _drawImageOverlay( void _drawImageOverlay(
Canvas canvas, Offset position, Size size, QrEmbeddedImageStyle? style) { Canvas canvas,
Offset position,
Size size,
QrEmbeddedImageStyle? style,
) {
final paint = Paint() final paint = Paint()
..isAntiAlias = true ..isAntiAlias = true
..filterQuality = FilterQuality.high; ..filterQuality = FilterQuality.high;
@ -587,24 +648,26 @@ class QrPainter extends CustomPainter {
} }
/// Returns the raw QR code [ui.Image] object. /// Returns the raw QR code [ui.Image] object.
Future<ui.Image> toImage(double size, Future<ui.Image> toImage(double size) {
{ui.ImageByteFormat format = ui.ImageByteFormat.png}) async { return toPicture(size).toImage(size.toInt(), size.toInt());
return await toPicture(size).toImage(size.toInt(), size.toInt());
} }
/// Returns the raw QR code image byte data. /// Returns the raw QR code image byte data.
Future<ByteData?> toImageData(double size, Future<ByteData?> toImageData(
{ui.ImageByteFormat format = ui.ImageByteFormat.png}) async { double size, {
final image = await toImage(size, format: format); ui.ImageByteFormat format = ui.ImageByteFormat.png,
}) async {
final image = await toImage(size);
return image.toByteData(format: format); return image.toByteData(format: format);
} }
} }
class _PaintMetrics { class _PaintMetrics {
_PaintMetrics( _PaintMetrics({
{required this.containerSize, required this.containerSize,
required this.gapSize, required this.gapSize,
required this.moduleCount}) { required this.moduleCount,
}) {
_calculateMetrics(); _calculateMetrics();
} }
@ -623,7 +686,7 @@ class _PaintMetrics {
void _calculateMetrics() { void _calculateMetrics() {
final gapTotal = (moduleCount - 1) * gapSize; final gapTotal = (moduleCount - 1) * gapSize;
var pixelSize = (containerSize - gapTotal) / moduleCount; final pixelSize = (containerSize - gapTotal) / moduleCount;
_pixelSize = (pixelSize * 2).roundToDouble() / 2; _pixelSize = (pixelSize * 2).roundToDouble() / 2;
_innerContentSize = (_pixelSize * moduleCount) + gapTotal; _innerContentSize = (_pixelSize * moduleCount) + gapTotal;
_inset = (containerSize - _innerContentSize) / 2; _inset = (containerSize - _innerContentSize) / 2;

View File

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

View File

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

View File

@ -2,22 +2,21 @@ name: qr_flutter
description: > description: >
QR.Flutter is a Flutter library for simple and fast QR code rendering via a QR.Flutter is a Flutter library for simple and fast QR code rendering via a
Widget or custom painter. Widget or custom painter.
version: 4.0.1 version: 4.1.0
homepage: https://github.com/theyakka/qr.flutter homepage: https://github.com/theyakka/qr.flutter
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: '>=2.19.6 <4.0.0'
flutter: ">=1.16.0" flutter: ">=3.7.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
qr: ^3.0.0 qr: ^3.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
test: ^1.16.5
flutter: flutter:
uses-material-design: false 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'; import 'package:qr_flutter/qr_flutter.dart';
void main() { void main() {
testWidgets('QrImageView generates correct image', (tester) async { testWidgets('QrImageView generates correct image', (
tester,
) async {
final qrImage = MaterialApp( final qrImage = MaterialApp(
home: Center( home: Center(
child: RepaintBoundary( 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( testWidgets(
'QrImageView generates correct image with eye and data module sytle', 'QrImageView generates correct image with eye style',
(tester) async { (tester) async {
final qrImage = MaterialApp( 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(
home: Center( home: Center(
child: RepaintBoundary( child: RepaintBoundary(
child: QrImageView( child: QrImageView(
data: 'This is a a qr code with a logo', data: 'This is a test image',
version: QrVersions.auto, version: QrVersions.auto,
gapless: true, gapless: true,
errorCorrectionLevel: QrErrorCorrectLevel.L, 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( testWidgets(
find.byType(QrImageView), 'QrImageView generates correct image with eye and data module sytle',
matchesGoldenFile('./.golden/qr_image_logo_golden.png'), (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. /// Pre-cache images to make sure they show up in golden tests.
@ -180,19 +195,24 @@ Future<void> pumpWidgetWithImages(
) async { ) async {
Future<void>? precacheFuture; Future<void>? precacheFuture;
await tester.pumpWidget( await tester.pumpWidget(
Builder(builder: (buildContext) { Builder(
precacheFuture = tester.runAsync(() async { builder: (buildContext) {
await Future.wait([ precacheFuture = tester.runAsync(() async {
for (final assetName in assetNames) await Future.wait(<Future<void>>[
precacheImage(FileImage(File(assetName)), buildContext), for (final String assetName in assetNames)
]); precacheImage(FileImage(File(assetName)), buildContext),
}); ]);
return widget; });
}), return widget;
},
),
); );
await precacheFuture; await precacheFuture;
} }
Widget buildTestableWidget(Widget widget) { 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); imageData = await painter.toImageData(600.0);
}); });
final imageBytes = imageData!.buffer.asUint8List(); final imageBytes = imageData!.buffer.asUint8List();
final widget = Center( final Widget widget = Center(
child: RepaintBoundary( child: RepaintBoundary(
child: Container( child: SizedBox(
width: 600, width: 600,
height: 600, height: 600,
child: Image.memory(imageBytes), child: Image.memory(imageBytes),