├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ └── Project.xml ├── libraries │ ├── Dart_SDK.xml │ ├── Flutter_Plugins.xml │ └── Flutter_for_Android.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml ├── runConfigurations │ └── example_lib_main_dart.xml └── vcs.xml ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── rhyme │ └── r_scan │ ├── ImageScanHelper.java │ ├── MethodCallHandlerImpl.java │ ├── RScanCamera │ ├── CameraUtils.java │ ├── RScanCamera.java │ ├── RScanCameraMethodHandler.java │ ├── RScanMessenger.java │ └── RScanPermissions.java │ ├── RScanPlugin.java │ ├── RScanResultUtils.java │ └── RScanView │ ├── FlutterRScanView.java │ ├── RScanViewFactory.java │ └── RScanViewPlugin.java ├── example ├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── images │ └── qrCode.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ ├── scan_camera_dialog.dart │ └── scan_dialog.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterRScanView.h │ ├── FlutterRScanView.m │ ├── RScanCamera.h │ ├── RScanCamera.m │ ├── RScanPlugin.h │ ├── RScanPlugin.m │ ├── RScanResult.h │ ├── RScanResult.m │ ├── RScanView.h │ └── RScanView.m └── r_scan.podspec ├── lib ├── r_scan.dart └── src │ ├── r_scan_camera.dart │ └── r_scan_view.dart ├── pubspec.lock ├── pubspec.yaml ├── r_scan.iml ├── screen └── r_scan.png └── test └── r_scan_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.6+1 fix issues #52. 2 | 3 | ## 0.1.6 sound null safety and WKWebView. 4 | 5 | ## 0.1.5 fix ssl and rotate scan problem. 6 | 7 | ## 0.1.4+2 fix point problem. 8 | 9 | ## 0.1.4 add RScanCamera. 10 | 11 | ## 0.1.3 add the barcode type and the point in result. 12 | 13 | ## 0.1.2 fix android camera draw. 14 | 15 | ## 0.1.1 add turn on / turn off flash lamp and get flash lamp status. 16 | 17 | ## 0.1.0 add ios platform and finish. 18 | 19 | ## 0.0.1 init project,and android platform finish. 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of rhyme_lph authors. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # r_scan 2 | [![pub package](https://img.shields.io/pub/v/r_scan.svg)](https://pub.dartlang.org/packages/r_scan) 3 | 4 | ![](screen/r_scan.png) 5 | 6 | A flutter plugin about qr code or bar code scan , it can scan from file、url、memory and camera qr code or bar code .Welcome to feedback your issue. 7 | 8 | ## Getting Started 9 | 10 | ### Depend on it 11 | 12 | Add this to your package's pubspec.yaml file: 13 | ```yaml 14 | dependencies: 15 | r_scan: last version 16 | ``` 17 | 18 | ### Android Platform 19 | require `read storage permission` and `camera permission`, use `permission_handler` plugin. 20 | ```dart 21 | import 'package:permission_handler/permission_handler.dart'; 22 | 23 | Future canReadStorage() async { 24 | if(Platform.isIOS) return true; 25 | var status = await PermissionHandler() 26 | .checkPermissionStatus(PermissionGroup.storage); 27 | if (status != PermissionStatus.granted) { 28 | var future = await PermissionHandler() 29 | .requestPermissions([PermissionGroup.storage]); 30 | for (final item in future.entries) { 31 | if (item.value != PermissionStatus.granted) { 32 | return false; 33 | } 34 | } 35 | } else { 36 | return true; 37 | } 38 | return true; 39 | } 40 | 41 | Future canOpenCamera() async { 42 | var status = 43 | await PermissionHandler().checkPermissionStatus(PermissionGroup.camera); 44 | if (status != PermissionStatus.granted) { 45 | var future = await PermissionHandler() 46 | .requestPermissions([PermissionGroup.camera]); 47 | for (final item in future.entries) { 48 | if (item.value != PermissionStatus.granted) { 49 | return false; 50 | } 51 | } 52 | } else { 53 | return true; 54 | } 55 | return true; 56 | } 57 | ``` 58 | 59 | ### IOS Platform 60 | add the permissions in your Info.plist 61 | ```plist 62 | NSCameraUsageDescription 63 | 扫描二维码时需要使用您的相机 64 | NSPhotoLibraryUsageDescription 65 | 扫描二维码时需要访问您的相册 66 | io.flutter.embedded_views_preview 67 | 68 | ``` 69 | no another. 70 | 71 | 72 | ## Usage 73 | ### 1.scan Image File 74 | 75 | ```dart 76 | 77 | final result=await RScan.scanImagePath('your file path'); 78 | 79 | ``` 80 | 81 | ### 2.scan Image url 82 | 83 | ```dart 84 | 85 | final result=await RScan.scanImagePath('your image url'); 86 | 87 | ``` 88 | 89 | ### 3.scan Image memory 90 | 91 | ```dart 92 | 93 | ByteData data=await rootBundle.load('images/qrCode.png'); 94 | final result=await RScan.scanImageMemory(data.buffer.asUint8List()); 95 | 96 | ``` 97 | 98 | ### 4.scan camera(new! please upgrade this plugin to v0.1,4) 99 | 100 | - Step First: Get available cameras 101 | ```dart 102 | List rScanCameras = await availableRScanCameras();; 103 | ``` 104 | if you want to get it in main() method, you can use this code. 105 | ```dart 106 | List rScanCameras; 107 | 108 | void main() async { 109 | WidgetsFlutterBinding.ensureInitialized(); 110 | rScanCameras = await availableRScanCameras(); 111 | runApp(...); 112 | } 113 | ``` 114 | - Step Second:Use it. 115 | ```dart 116 | class RScanCameraDialog extends StatefulWidget { 117 | @override 118 | _RScanCameraDialogState createState() => _RScanCameraDialogState(); 119 | } 120 | 121 | class _RScanCameraDialogState extends State { 122 | RScanCameraController _controller; 123 | bool isFirst = true; 124 | 125 | @override 126 | void initState() { 127 | super.initState(); 128 | if (rScanCameras != null && rScanCameras.length > 0) { 129 | _controller = RScanCameraController( 130 | rScanCameras[1], RScanCameraResolutionPreset.max) 131 | ..addListener(() { 132 | final result = _controller.result; 133 | if (result != null) { 134 | if (isFirst) { 135 | Navigator.of(context).pop(result); 136 | isFirst = false; 137 | } 138 | } 139 | }) 140 | ..initialize().then((_) { 141 | if (!mounted) { 142 | return; 143 | } 144 | setState(() {}); 145 | }); 146 | } 147 | } 148 | 149 | @override 150 | void dispose() { 151 | _controller?.dispose(); 152 | super.dispose(); 153 | } 154 | 155 | @override 156 | Widget build(BuildContext context) { 157 | if (rScanCameras == null || rScanCameras.length == 0) { 158 | return Scaffold( 159 | body: Container( 160 | alignment: Alignment.center, 161 | child: Text('not have available camera'), 162 | ), 163 | ); 164 | } 165 | if (!_controller.value.isInitialized) { 166 | return Container(); 167 | } 168 | return Scaffold( 169 | backgroundColor: Colors.black, 170 | body: Stack( 171 | children: [ 172 | ScanImageView( 173 | child: AspectRatio( 174 | aspectRatio: _controller.value.aspectRatio, 175 | child: RScanCamera(_controller), 176 | ), 177 | ), 178 | Align( 179 | alignment: Alignment.bottomCenter, 180 | child: FutureBuilder( 181 | future: getFlashMode(), 182 | builder: _buildFlashBtn, 183 | )) 184 | ], 185 | ), 186 | ); 187 | } 188 | Future getFlashMode() async { 189 | bool isOpen = false; 190 | try { 191 | isOpen = await _controller.getFlashMode(); 192 | } catch (_) {} 193 | return isOpen; 194 | } 195 | 196 | Widget _buildFlashBtn(BuildContext context, AsyncSnapshot snapshot) { 197 | return snapshot.hasData 198 | ? Padding( 199 | padding: EdgeInsets.only(bottom:24+MediaQuery.of(context).padding.bottom), 200 | child: IconButton( 201 | icon: Icon(snapshot.data ? Icons.flash_on : Icons.flash_off), 202 | color: Colors.white, 203 | iconSize: 46, 204 | onPressed: () { 205 | if (snapshot.data) { 206 | _controller.setFlashMode(false); 207 | } else { 208 | _controller.setFlashMode(true); 209 | } 210 | setState(() {}); 211 | }), 212 | ) 213 | : Container(); 214 | } 215 | } 216 | ``` 217 | 218 | ### 5.scan view(Deprecated) 219 | 220 | ```dart 221 | import 'package:flutter/material.dart'; 222 | import 'package:permission_handler/permission_handler.dart'; 223 | import 'package:r_scan/r_scan.dart'; 224 | 225 | class RScanDialog extends StatefulWidget { 226 | @override 227 | _RScanDialogState createState() => _RScanDialogState(); 228 | } 229 | 230 | class _RScanDialogState extends State { 231 | RScanController _controller; 232 | 233 | @override 234 | void initState() { 235 | super.initState(); 236 | initController(); 237 | } 238 | bool isFirst=true; 239 | 240 | 241 | Future initController() async { 242 | _controller = RScanController(); 243 | _controller.addListener(() { 244 | 245 | final result = _controller.result; 246 | if (result != null) { 247 | if(isFirst){ 248 | Navigator.of(context).pop(result); 249 | isFirst=false; 250 | } 251 | } 252 | }); 253 | } 254 | 255 | @override 256 | void dispose() { 257 | _controller.dispose(); 258 | super.dispose(); 259 | } 260 | 261 | @override 262 | Widget build(BuildContext context) { 263 | return MaterialApp( 264 | home: Scaffold( 265 | backgroundColor: Colors.black, 266 | body: FutureBuilder( 267 | future: canOpenCameraView(), 268 | builder: (BuildContext context, AsyncSnapshot snapshot) { 269 | if (snapshot.hasData && snapshot.data == true) { 270 | return ScanImageView( 271 | child: RScanView( 272 | controller: _controller, 273 | ), 274 | ); 275 | } else { 276 | return Container(); 277 | } 278 | }, 279 | ), 280 | ), 281 | ); 282 | } 283 | 284 | Future canOpenCameraView() async { 285 | var status = 286 | await PermissionHandler().checkPermissionStatus(PermissionGroup.camera); 287 | if (status != PermissionStatus.granted) { 288 | var future = await PermissionHandler() 289 | .requestPermissions([PermissionGroup.camera]); 290 | for (final item in future.entries) { 291 | if (item.value != PermissionStatus.granted) { 292 | return false; 293 | } 294 | } 295 | } else { 296 | return true; 297 | } 298 | return true; 299 | } 300 | } 301 | 302 | ``` 303 | 304 | ### 6. open flash lamp / get flash lamp status. 305 | You can use `RScanController` class. 306 | ```dart 307 | //turn off the flash lamp. 308 | await _controller.setFlashMode(false); 309 | 310 | //turn on the flash lamp. 311 | await _controller.setFlashMode(true); 312 | 313 | // get the flash lamp status. 314 | 315 | bool isOpen = await _controller.getFlashMode(); 316 | 317 | ``` 318 | 319 | ### 7. RScanResult 320 | 321 | when you scan finish,will return the RScanResult... 322 | 323 | ```dart 324 | class RScanResult { 325 | /// barcode type 326 | final RScanBarType type; 327 | 328 | ///barcode message 329 | final String message; 330 | 331 | ///barcode points include [x , y] 332 | final List points; 333 | } 334 | 335 | ``` 336 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.rhyme.r_scan' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.2.1' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 28 26 | 27 | defaultConfig { 28 | minSdkVersion 21 29 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | compileOptions { 35 | sourceCompatibility = 1.8 36 | targetCompatibility = 1.8 37 | } 38 | } 39 | 40 | dependencies { 41 | 42 | def camerax_version = "1.0.0-alpha05" 43 | implementation "androidx.camera:camera-core:${camerax_version}" 44 | implementation "androidx.camera:camera-camera2:${camerax_version}" 45 | 46 | implementation 'com.google.zxing:core:3.4.0' 47 | } 48 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'r_scan' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/ImageScanHelper.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan; 2 | 3 | import android.content.Context; 4 | import android.content.ContextWrapper; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.os.Handler; 8 | import android.util.Log; 9 | 10 | 11 | import com.google.zxing.BarcodeFormat; 12 | import com.google.zxing.BinaryBitmap; 13 | import com.google.zxing.DecodeHintType; 14 | import com.google.zxing.MultiFormatReader; 15 | import com.google.zxing.NotFoundException; 16 | import com.google.zxing.PlanarYUVLuminanceSource; 17 | import com.google.zxing.RGBLuminanceSource; 18 | import com.google.zxing.Result; 19 | import com.google.zxing.common.GlobalHistogramBinarizer; 20 | import com.google.zxing.common.HybridBinarizer; 21 | import com.google.zxing.qrcode.QRCodeReader; 22 | 23 | import java.io.File; 24 | import java.net.HttpURLConnection; 25 | import java.net.URL; 26 | import java.security.cert.CertificateException; 27 | import java.security.cert.X509Certificate; 28 | import java.util.Arrays; 29 | import java.util.EnumMap; 30 | import java.util.Map; 31 | import java.util.concurrent.Executor; 32 | import java.util.concurrent.Executors; 33 | 34 | import javax.net.ssl.HttpsURLConnection; 35 | import javax.net.ssl.SSLContext; 36 | import javax.net.ssl.SSLSocketFactory; 37 | import javax.net.ssl.TrustManager; 38 | import javax.net.ssl.X509TrustManager; 39 | 40 | import io.flutter.plugin.common.MethodCall; 41 | import io.flutter.plugin.common.MethodChannel; 42 | 43 | public class ImageScanHelper extends ContextWrapper { 44 | 45 | private MultiFormatReader reader = new MultiFormatReader(); 46 | private Executor executor = Executors.newSingleThreadExecutor(); 47 | private Handler handler = new Handler(); 48 | 49 | public ImageScanHelper(Context base) { 50 | super(base); 51 | } 52 | 53 | private static byte[] yuvs; 54 | 55 | /** 56 | * 根据Bitmap的ARGB值生成YUV420SP数据。 57 | * 58 | * @param inputWidth image width 59 | * @param inputHeight image height 60 | * @param scaled bmp 61 | * @return YUV420SP数组 62 | */ 63 | public byte[] getYUV420sp(int inputWidth, int inputHeight, Bitmap scaled) { 64 | int[] argb = new int[inputWidth * inputHeight]; 65 | scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight); 66 | /** 67 | * 需要转换成偶数的像素点,否则编码YUV420的时候有可能导致分配的空间大小不够而溢出。 68 | */ 69 | int requiredWidth = inputWidth % 2 == 0 ? inputWidth : inputWidth + 1; 70 | int requiredHeight = inputHeight % 2 == 0 ? inputHeight : inputHeight + 1; 71 | int byteLength = requiredWidth * requiredHeight * 3 / 2; 72 | if (yuvs == null || yuvs.length < byteLength) { 73 | yuvs = new byte[byteLength]; 74 | } else { 75 | Arrays.fill(yuvs, (byte) 0); 76 | } 77 | encodeYUV420SP(yuvs, argb, inputWidth, inputHeight); 78 | scaled.recycle(); 79 | return yuvs; 80 | } 81 | 82 | /** 83 | * RGB转YUV420sp 84 | * 85 | * @param yuv420sp inputWidth * inputHeight * 3 / 2 86 | * @param argb inputWidth * inputHeight 87 | * @param width image width 88 | * @param height image height 89 | */ 90 | private void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) { 91 | // 帧图片的像素大小 92 | final int frameSize = width * height; 93 | // ---YUV数据--- 94 | int Y, U, V; 95 | // Y的index从0开始 96 | int yIndex = 0; 97 | // UV的index从frameSize开始 98 | int uvIndex = frameSize; 99 | // ---颜色数据--- 100 | int R, G, B; 101 | int rgbIndex = 0; 102 | // ---循环所有像素点,RGB转YUV--- 103 | for (int j = 0; j < height; j++) { 104 | for (int i = 0; i < width; i++) { 105 | R = (argb[rgbIndex] & 0xff0000) >> 16; 106 | G = (argb[rgbIndex] & 0xff00) >> 8; 107 | B = (argb[rgbIndex] & 0xff); 108 | // 109 | rgbIndex++; 110 | // well known RGB to YUV algorithm 111 | Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16; 112 | U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128; 113 | V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; 114 | Y = Math.max(0, Math.min(Y, 255)); 115 | U = Math.max(0, Math.min(U, 255)); 116 | V = Math.max(0, Math.min(V, 255)); 117 | // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2 118 | // meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other 119 | // pixel AND every other scan line. 120 | // ---Y--- 121 | yuv420sp[yIndex++] = (byte) Y; 122 | // ---UV--- 123 | if ((j % 2 == 0) && (i % 2 == 0)) { 124 | // 125 | yuv420sp[uvIndex++] = (byte) V; 126 | // 127 | yuv420sp[uvIndex++] = (byte) U; 128 | } 129 | } 130 | } 131 | } 132 | 133 | private Result scanBitmapToResult(Bitmap bitmap) { 134 | int height = bitmap.getHeight(); 135 | int width = bitmap.getWidth(); 136 | Map hints = new EnumMap<>(DecodeHintType.class); 137 | hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); 138 | byte[] array = getYUV420sp(width, height, bitmap); 139 | PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(array, 140 | width, 141 | height, 142 | 0, 143 | 0, 144 | width, 145 | height, 146 | false); 147 | BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); 148 | try { 149 | return reader.decode(binaryBitmap, hints); 150 | } catch (NotFoundException e) { 151 | // e.printStackTrace(); 152 | binaryBitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source)); 153 | try { 154 | return reader.decode(binaryBitmap, hints); 155 | } catch (NotFoundException ex) { 156 | // ex.printStackTrace(); 157 | } 158 | } finally { 159 | bitmap.recycle(); 160 | } 161 | return null; 162 | } 163 | 164 | private void scanBitmap(Bitmap bitmap, MethodChannel.Result result) { 165 | Result scanResult = scanBitmapToResult(bitmap); 166 | if (scanResult != null) { 167 | Log.d("result", "analyze: decode:" + scanResult.toString()); 168 | } 169 | handler.post(new Runnable() { 170 | @Override 171 | public void run() { 172 | result.success(RScanResultUtils.toMap(scanResult)); 173 | } 174 | }); 175 | } 176 | 177 | public void scanImagePath(MethodCall call, final MethodChannel.Result result) { 178 | final String path = call.argument("path"); 179 | if (path == null) { 180 | result.error("1001", "please enter your file path", null); 181 | return; 182 | } 183 | final File file = new File(path); 184 | if (file.isFile()) { 185 | executor.execute(new Runnable() { 186 | @Override 187 | public void run() { 188 | Bitmap bitmap = BitmapFactory.decodeFile(path); 189 | scanBitmap(bitmap, result); 190 | } 191 | }); 192 | } else { 193 | result.success(""); 194 | } 195 | } 196 | 197 | public void scanImageUrl(MethodCall call, final MethodChannel.Result result) { 198 | final String url = call.argument("url"); 199 | if (url == null) { 200 | result.error("1002", "please enter your url", null); 201 | return; 202 | } 203 | executor.execute(new Runnable() { 204 | @Override 205 | public void run() { 206 | try { 207 | URL myUrl = new URL(url); 208 | Bitmap bitmap; 209 | 210 | if (url.startsWith("https")) { 211 | HttpsURLConnection connection = (HttpsURLConnection) myUrl.openConnection(); 212 | connection.setReadTimeout(6 * 60 * 1000); 213 | connection.setConnectTimeout(6 * 60 * 1000); 214 | connection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault()); 215 | connection.connect(); 216 | bitmap = BitmapFactory.decodeStream(connection.getInputStream()); 217 | } else { 218 | HttpURLConnection connection = (HttpURLConnection) myUrl.openConnection(); 219 | connection.setReadTimeout(6 * 60 * 1000); 220 | connection.setConnectTimeout(6 * 60 * 1000); 221 | connection.connect(); 222 | bitmap = BitmapFactory.decodeStream(connection.getInputStream()); 223 | } 224 | scanBitmap(bitmap, result); 225 | } catch (Exception e) { 226 | Log.d("result", "analyze: error"); 227 | handler.post(new Runnable() { 228 | @Override 229 | public void run() { 230 | result.success(null); 231 | } 232 | }); 233 | } 234 | } 235 | }); 236 | } 237 | 238 | public void scanImageMemory(MethodCall call, final MethodChannel.Result result) { 239 | final byte[] uint8list = call.argument("uint8list"); 240 | if (uint8list == null) { 241 | result.error("1003", "uint8list is not null", null); 242 | return; 243 | } 244 | executor.execute(new Runnable() { 245 | @Override 246 | public void run() { 247 | try { 248 | Bitmap bitmap = BitmapFactory.decodeByteArray(uint8list, 0, uint8list.length); 249 | scanBitmap(bitmap, result); 250 | } catch (Exception e) { 251 | Log.d("result", "analyze: error"); 252 | handler.post(new Runnable() { 253 | @Override 254 | public void run() { 255 | result.success(null); 256 | } 257 | }); 258 | } 259 | } 260 | }); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/MethodCallHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan; 2 | 3 | import android.app.Activity; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.rhyme.r_scan.RScanCamera.RScanCameraMethodHandler; 8 | import com.rhyme.r_scan.RScanCamera.RScanPermissions; 9 | import com.rhyme.r_scan.RScanView.RScanViewPlugin; 10 | 11 | import io.flutter.plugin.common.BinaryMessenger; 12 | import io.flutter.plugin.common.MethodCall; 13 | import io.flutter.plugin.common.MethodChannel; 14 | import io.flutter.plugin.platform.PlatformViewRegistry; 15 | import io.flutter.view.TextureRegistry; 16 | 17 | public class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { 18 | private ImageScanHelper scanHelper; 19 | private final Activity activity; 20 | private final BinaryMessenger messenger; 21 | private final RScanPermissions cameraPermissions; 22 | private final RScanPermissions.PermissionsRegistry permissionsRegistry; 23 | private final TextureRegistry textureRegistry; 24 | private final MethodChannel methodChannel; 25 | private final PlatformViewRegistry platformViewRegistry; 26 | 27 | public MethodCallHandlerImpl( 28 | Activity activity, 29 | BinaryMessenger messenger, 30 | RScanPermissions cameraPermissions, 31 | RScanPermissions.PermissionsRegistry permissionsAdder, 32 | TextureRegistry textureRegistry, 33 | PlatformViewRegistry platformViewRegistry) { 34 | this.activity = activity; 35 | this.messenger = messenger; 36 | this.cameraPermissions = cameraPermissions; 37 | this.permissionsRegistry = permissionsAdder; 38 | this.textureRegistry = textureRegistry; 39 | this.platformViewRegistry = platformViewRegistry; 40 | 41 | scanHelper = new ImageScanHelper(activity); 42 | methodChannel = new MethodChannel(messenger, "com.rhyme_lph/r_scan"); 43 | methodChannel.setMethodCallHandler(this); 44 | 45 | //注册老方式 46 | RScanViewPlugin.registerWith(this.platformViewRegistry, this.messenger); 47 | 48 | //注册新的方式 49 | new RScanCameraMethodHandler( 50 | activity, 51 | messenger, 52 | cameraPermissions, 53 | permissionsAdder, 54 | textureRegistry 55 | ); 56 | 57 | } 58 | 59 | void stopListening() { 60 | methodChannel.setMethodCallHandler(null); 61 | } 62 | 63 | @Override 64 | public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) { 65 | if (call.method.equals("scanImagePath")) { 66 | scanHelper.scanImagePath(call, result); 67 | } else if (call.method.equals("scanImageUrl")) { 68 | scanHelper.scanImageUrl(call, result); 69 | } else if (call.method.equals("scanImageMemory")) { 70 | scanHelper.scanImageMemory(call, result); 71 | } else { 72 | result.notImplemented(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanCamera/CameraUtils.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan.RScanCamera; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.hardware.camera2.CameraAccessException; 6 | import android.hardware.camera2.CameraCharacteristics; 7 | import android.hardware.camera2.CameraManager; 8 | import android.hardware.camera2.CameraMetadata; 9 | import android.media.CamcorderProfile; 10 | import android.util.Size; 11 | 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class CameraUtils { 18 | 19 | public static List> getAvailableCameras(Activity activity) 20 | throws CameraAccessException { 21 | CameraManager cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 22 | String[] cameraNames = cameraManager.getCameraIdList(); 23 | List> cameras = new ArrayList<>(); 24 | for (String cameraName : cameraNames) { 25 | HashMap details = new HashMap<>(); 26 | CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); 27 | details.put("name", cameraName); 28 | 29 | int lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING); 30 | switch (lensFacing) { 31 | case CameraMetadata.LENS_FACING_FRONT: 32 | details.put("lensFacing", "front"); 33 | break; 34 | case CameraMetadata.LENS_FACING_BACK: 35 | details.put("lensFacing", "back"); 36 | break; 37 | case CameraMetadata.LENS_FACING_EXTERNAL: 38 | details.put("lensFacing", "external"); 39 | break; 40 | } 41 | cameras.add(details); 42 | } 43 | return cameras; 44 | } 45 | 46 | static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset( 47 | String cameraName, RScanCamera.ResolutionPreset preset) { 48 | int cameraId = Integer.parseInt(cameraName); 49 | switch (preset) { 50 | // All of these cases deliberately fall through to get the best available profile. 51 | case max: 52 | if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) { 53 | return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); 54 | } 55 | case ultraHigh: 56 | if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { 57 | return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P); 58 | } 59 | case veryHigh: 60 | if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { 61 | return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P); 62 | } 63 | case high: 64 | if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { 65 | return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); 66 | } 67 | case medium: 68 | if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { 69 | return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); 70 | } 71 | case low: 72 | if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { 73 | return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA); 74 | } 75 | default: 76 | if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) { 77 | return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); 78 | } else { 79 | throw new IllegalArgumentException( 80 | "No capture session available for current capture session."); 81 | } 82 | } 83 | } 84 | 85 | static Size computeBestPreviewSize(String cameraName, RScanCamera.ResolutionPreset preset) { 86 | if (preset.ordinal() > RScanCamera.ResolutionPreset.high.ordinal()) { 87 | preset = RScanCamera.ResolutionPreset.high; 88 | } 89 | 90 | CamcorderProfile profile = 91 | getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset); 92 | return new Size(profile.videoFrameWidth, profile.videoFrameHeight); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanCamera/RScanCameraMethodHandler.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan.RScanCamera; 2 | 3 | 4 | import android.app.Activity; 5 | import android.hardware.camera2.CameraAccessException; 6 | import android.util.Log; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.camera.core.CameraX; 10 | import androidx.lifecycle.Lifecycle; 11 | import androidx.lifecycle.LifecycleOwner; 12 | 13 | 14 | import io.flutter.plugin.common.BinaryMessenger; 15 | import io.flutter.plugin.common.MethodCall; 16 | import io.flutter.plugin.common.MethodChannel; 17 | import io.flutter.view.TextureRegistry; 18 | 19 | public class RScanCameraMethodHandler implements MethodChannel.MethodCallHandler { 20 | private static final String scanViewType = "com.rhyme_lph/r_scan_camera"; 21 | private final TextureRegistry textureRegistry; 22 | private final Activity activity; 23 | private final RScanPermissions rScanPermissions; 24 | private final RScanPermissions.PermissionsRegistry permissionsRegistry; 25 | private final BinaryMessenger messenger; 26 | private RScanCamera rScanCamera; 27 | 28 | 29 | public RScanCameraMethodHandler( 30 | Activity activity, 31 | BinaryMessenger messenger, 32 | RScanPermissions rScanPermissions, 33 | RScanPermissions.PermissionsRegistry permissionsAdder, 34 | TextureRegistry textureRegistry) { 35 | this.activity = activity; 36 | this.messenger = messenger; 37 | 38 | this.rScanPermissions = rScanPermissions; 39 | this.permissionsRegistry = permissionsAdder; 40 | this.textureRegistry = textureRegistry; 41 | 42 | MethodChannel methodChannel = new MethodChannel(messenger, scanViewType + "/method"); 43 | methodChannel.setMethodCallHandler(this); 44 | 45 | // Log.d(TAG, "FlutterRScanView: " + outMetrics.toString()); 46 | // mPreview = buildPreView(outMetrics.widthPixels, outMetrics.heightPixels); 47 | // CameraX.bindToLifecycle(this, mPreview, buildImageAnalysis()); 48 | 49 | 50 | } 51 | 52 | @Override 53 | public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) { 54 | switch (call.method) { 55 | case "availableCameras": 56 | try { 57 | result.success(CameraUtils.getAvailableCameras(activity)); 58 | } catch (Exception e) { 59 | handleException(e, result); 60 | } 61 | break; 62 | case "initialize": 63 | if (rScanCamera != null) { 64 | rScanCamera.close(); 65 | } 66 | //请求权限 67 | rScanPermissions.requestPermissions( 68 | activity, 69 | permissionsRegistry, 70 | (String errCode, String errDesc) -> { 71 | if (errCode == null) { 72 | try { 73 | instantiateCamera(call, result); 74 | } catch (Exception e) { 75 | handleException(e, result); 76 | } 77 | } else { 78 | result.error(errCode, errDesc, null); 79 | } 80 | }); 81 | break; 82 | case "startScan": 83 | if (rScanCamera != null) { 84 | rScanCamera.startScan(); 85 | } 86 | result.success(null); 87 | break; 88 | case "stopScan": 89 | if (rScanCamera != null) { 90 | rScanCamera.stopScan(); 91 | } 92 | result.success(null); 93 | break; 94 | case "setAutoFlashMode": 95 | Boolean isAuto = call.argument("isAuto"); 96 | if (rScanCamera != null) { 97 | rScanCamera.setAutoFlash(isAuto == Boolean.TRUE); 98 | result.success(true); 99 | } else { 100 | result.success(true); 101 | } 102 | break; 103 | case "setFlashMode": 104 | Boolean isOpen = call.argument("isOpen"); 105 | if (rScanCamera != null) { 106 | try { 107 | rScanCamera.enableTorch(isOpen == Boolean.TRUE); 108 | } catch (CameraAccessException e) { 109 | e.printStackTrace(); 110 | } 111 | result.success(true); 112 | } else { 113 | result.success(true); 114 | } 115 | break; 116 | case "getFlashMode": 117 | if (rScanCamera != null) { 118 | result.success(rScanCamera.isTorchOn()); 119 | } else { 120 | result.success(false); 121 | } 122 | break; 123 | case "dispose": { 124 | if (rScanCamera != null) { 125 | rScanCamera.dispose(); 126 | } 127 | result.success(null); 128 | break; 129 | } 130 | default: 131 | result.notImplemented(); 132 | break; 133 | } 134 | } 135 | 136 | //初始化相机 137 | private void instantiateCamera(MethodCall call, MethodChannel.Result result) throws CameraAccessException { 138 | String cameraName = call.argument("cameraName"); 139 | String resolutionPreset = call.argument("resolutionPreset"); 140 | TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = 141 | textureRegistry.createSurfaceTexture(); 142 | 143 | RScanMessenger rScanMessenger = new RScanMessenger(messenger, flutterSurfaceTexture.id()); 144 | 145 | rScanCamera = new RScanCamera(activity, flutterSurfaceTexture, rScanMessenger, cameraName, resolutionPreset); 146 | rScanCamera.open(result); 147 | } 148 | 149 | // We move catching CameraAccessException out of onMethodCall because it causes a crash 150 | // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to 151 | // to be able to compile with <21 sdks for apps that want the camera and support earlier version. 152 | @SuppressWarnings("ConstantConditions") 153 | private void handleException(Exception exception, MethodChannel.Result result) { 154 | if (exception instanceof CameraAccessException) { 155 | result.error("CameraAccess", exception.getMessage(), null); 156 | } 157 | throw (RuntimeException) exception; 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanCamera/RScanMessenger.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan.RScanCamera; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.google.zxing.Result; 6 | import com.rhyme.r_scan.RScanResultUtils; 7 | 8 | import io.flutter.plugin.common.BinaryMessenger; 9 | import io.flutter.plugin.common.EventChannel; 10 | 11 | class RScanMessenger { 12 | @Nullable 13 | private EventChannel.EventSink eventSink; 14 | private static final String scanViewType = "com.rhyme_lph/r_scan_camera"; 15 | 16 | 17 | RScanMessenger(BinaryMessenger messenger, long eventChannelId) { 18 | new EventChannel(messenger, scanViewType+"_" + eventChannelId+"/event") 19 | .setStreamHandler( 20 | new EventChannel.StreamHandler() { 21 | @Override 22 | public void onListen(Object arguments, EventChannel.EventSink sink) { 23 | eventSink = sink; 24 | } 25 | 26 | @Override 27 | public void onCancel(Object arguments) { 28 | eventSink = null; 29 | } 30 | }); 31 | } 32 | 33 | 34 | void send(Result decode) { 35 | if (eventSink == null) { 36 | return; 37 | } 38 | eventSink.success(RScanResultUtils.toMap(decode)); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanCamera/RScanPermissions.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan.RScanCamera; 2 | 3 | import android.Manifest.permission; 4 | import android.app.Activity; 5 | import android.content.pm.PackageManager; 6 | 7 | import androidx.core.app.ActivityCompat; 8 | import androidx.core.content.ContextCompat; 9 | 10 | import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener; 11 | 12 | public final class RScanPermissions { 13 | public interface PermissionsRegistry { 14 | void addListener(RequestPermissionsResultListener handler); 15 | } 16 | 17 | interface ResultCallback { 18 | void onResult(String errorCode, String errorDescription); 19 | } 20 | 21 | private static final int CAMERA_REQUEST_ID = 9796; 22 | private boolean ongoing = false; 23 | 24 | void requestPermissions( 25 | Activity activity, 26 | PermissionsRegistry permissionsRegistry, 27 | final ResultCallback callback) { 28 | if (ongoing) { 29 | callback.onResult("cameraPermission", "Camera permission request ongoing"); 30 | } 31 | if (!hasCameraPermission(activity)) { 32 | permissionsRegistry.addListener( 33 | new RScanRequestPermissionsListener(new ResultCallback() { 34 | @Override 35 | public void onResult(String errorCode, String errorDescription) { 36 | ongoing = false; 37 | callback.onResult(errorCode, errorDescription); 38 | } 39 | })); 40 | ongoing = true; 41 | ActivityCompat.requestPermissions( 42 | activity, new String[]{permission.CAMERA}, 43 | CAMERA_REQUEST_ID); 44 | } else { 45 | // Permissions already exist. Call the callback with success. 46 | callback.onResult(null, null); 47 | } 48 | } 49 | 50 | private boolean hasCameraPermission(Activity activity) { 51 | return ContextCompat.checkSelfPermission(activity, permission.CAMERA) 52 | == PackageManager.PERMISSION_GRANTED; 53 | } 54 | 55 | 56 | private static class RScanRequestPermissionsListener 57 | implements RequestPermissionsResultListener { 58 | 59 | final ResultCallback callback; 60 | 61 | private RScanRequestPermissionsListener(ResultCallback callback) { 62 | this.callback = callback; 63 | } 64 | 65 | @Override 66 | public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) { 67 | if (id == CAMERA_REQUEST_ID) { 68 | if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { 69 | callback.onResult("rScanPermission", "MediaRecorderCamera permission not granted"); 70 | } else { 71 | callback.onResult(null, null); 72 | } 73 | return true; 74 | } 75 | return false; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanPlugin.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan; 2 | 3 | 4 | import android.app.Activity; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.rhyme.r_scan.RScanCamera.RScanPermissions; 9 | 10 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 11 | import io.flutter.embedding.engine.plugins.activity.ActivityAware; 12 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; 13 | import io.flutter.plugin.common.BinaryMessenger; 14 | import io.flutter.plugin.common.PluginRegistry.Registrar; 15 | import io.flutter.plugin.platform.PlatformViewRegistry; 16 | import io.flutter.view.TextureRegistry; 17 | 18 | /** 19 | * RScanPlugin 20 | */ 21 | //public class RScanPlugin implements MethodChannel.MethodCallHandler { 22 | // private ImageScanHelper scanHelper; 23 | // 24 | // private RScanPlugin(Registrar registrar) { 25 | // scanHelper = new ImageScanHelper(registrar.context()); 26 | // } 27 | // 28 | // public static void registerWith(Registrar registrar) { 29 | // final MethodChannel channel = new MethodChannel(registrar.messenger(), "r_scan"); 30 | // channel.setMethodCallHandler(new RScanPlugin(registrar)); 31 | // RScanViewPlugin.registerWith(registrar); 32 | // } 33 | // 34 | // @Override 35 | // public void onMethodCall(MethodCall call, Result result) { 36 | // if (call.method.equals("scanImagePath")) { 37 | // scanHelper.scanImagePath(call,result); 38 | // } else if(call.method.equals("scanImageUrl")){ 39 | // scanHelper.scanImageUrl(call,result); 40 | // } else if(call.method.equals("scanImageMemory")){ 41 | // scanHelper.scanImageMemory(call,result); 42 | // } else { 43 | // result.notImplemented(); 44 | // } 45 | // } 46 | //} 47 | 48 | /** 49 | * RScanPlugin 50 | */ 51 | public class RScanPlugin implements FlutterPlugin, ActivityAware { 52 | private MethodCallHandlerImpl methodCallHandler; 53 | private FlutterPluginBinding flutterPluginBinding; 54 | 55 | 56 | public static void registerWith(Registrar registrar) { 57 | RScanPlugin plugin = new RScanPlugin(); 58 | plugin.maybeStartListening( 59 | registrar.activity(), 60 | registrar.messenger(), 61 | registrar::addRequestPermissionsResultListener, 62 | registrar.view(), registrar.platformViewRegistry()); 63 | 64 | } 65 | 66 | private void maybeStartListening( 67 | Activity activity, 68 | BinaryMessenger messenger, 69 | RScanPermissions.PermissionsRegistry permissionsRegistry, 70 | TextureRegistry textureRegistry, PlatformViewRegistry platformViewRegistry) { 71 | methodCallHandler = 72 | new MethodCallHandlerImpl( 73 | activity, messenger, new RScanPermissions(), permissionsRegistry, textureRegistry, platformViewRegistry); 74 | } 75 | 76 | @Override 77 | public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { 78 | this.flutterPluginBinding = binding; 79 | 80 | } 81 | 82 | @Override 83 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 84 | this.flutterPluginBinding = null; 85 | 86 | } 87 | 88 | @Override 89 | public void onAttachedToActivity(ActivityPluginBinding binding) { 90 | 91 | maybeStartListening( 92 | binding.getActivity(), 93 | flutterPluginBinding.getBinaryMessenger(), 94 | binding::addRequestPermissionsResultListener, 95 | flutterPluginBinding.getTextureRegistry(), 96 | flutterPluginBinding.getPlatformViewRegistry()); 97 | } 98 | 99 | @Override 100 | public void onDetachedFromActivityForConfigChanges() { 101 | onDetachedFromActivity(); 102 | 103 | } 104 | 105 | @Override 106 | public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { 107 | onAttachedToActivity(binding); 108 | 109 | } 110 | 111 | @Override 112 | public void onDetachedFromActivity() { 113 | if (methodCallHandler == null) { 114 | // Could be on too low of an SDK to have started listening originally. 115 | return; 116 | } 117 | methodCallHandler.stopListening(); 118 | methodCallHandler = null; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanResultUtils.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan; 2 | 3 | import com.google.zxing.Result; 4 | import com.google.zxing.ResultPoint; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class RScanResultUtils { 12 | 13 | 14 | public static Map toMap(Result result){ 15 | if(result == null) return null; 16 | Map data = new HashMap<>(); 17 | data.put("message",result.getText()); 18 | data.put("type",result.getBarcodeFormat().ordinal()); 19 | if(result.getResultPoints()!=null){ 20 | List> resultPoints = new ArrayList<>(); 21 | for (ResultPoint point :result.getResultPoints()){ 22 | Map pointMap = new HashMap<>(); 23 | pointMap.put("X",point.getX()); 24 | pointMap.put("Y",point.getY()); 25 | resultPoints.add(pointMap); 26 | } 27 | data.put("points",resultPoints); 28 | } 29 | return data; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanView/FlutterRScanView.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan.RScanView; 2 | 3 | import android.content.Context; 4 | import android.graphics.ImageFormat; 5 | import android.util.DisplayMetrics; 6 | import android.util.Log; 7 | import android.util.Rational; 8 | import android.util.Size; 9 | import android.view.TextureView; 10 | import android.view.View; 11 | import android.view.WindowManager; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.camera.core.CameraX; 15 | import androidx.camera.core.ImageAnalysis; 16 | import androidx.camera.core.ImageAnalysisConfig; 17 | import androidx.camera.core.ImageProxy; 18 | import androidx.camera.core.Preview; 19 | import androidx.camera.core.PreviewConfig; 20 | import androidx.camera.core.SessionConfig; 21 | import androidx.camera.core.UseCase; 22 | import androidx.lifecycle.Lifecycle; 23 | import androidx.lifecycle.LifecycleOwner; 24 | import androidx.lifecycle.LifecycleRegistry; 25 | 26 | import com.google.zxing.BinaryBitmap; 27 | import com.google.zxing.MultiFormatReader; 28 | import com.google.zxing.PlanarYUVLuminanceSource; 29 | import com.google.zxing.Result; 30 | import com.google.zxing.common.HybridBinarizer; 31 | import com.rhyme.r_scan.RScanResultUtils; 32 | 33 | import java.nio.ByteBuffer; 34 | import java.util.Map; 35 | 36 | import io.flutter.plugin.common.BinaryMessenger; 37 | import io.flutter.plugin.common.EventChannel; 38 | import io.flutter.plugin.common.MethodCall; 39 | import io.flutter.plugin.common.MethodChannel; 40 | import io.flutter.plugin.platform.PlatformView; 41 | 42 | public class FlutterRScanView implements PlatformView, LifecycleOwner, EventChannel.StreamHandler, MethodChannel.MethodCallHandler { 43 | private static final String TAG = "FlutterRScanView"; 44 | 45 | private LifecycleRegistry lifecycleRegistry; 46 | private TextureView textureView; 47 | private boolean isPlay; 48 | private static final String scanViewType = "com.rhyme_lph/r_scan_view"; 49 | private EventChannel.EventSink eventSink; 50 | private long lastCurrentTimestamp = 0L;//最后一次的扫描 51 | private Preview mPreview; 52 | 53 | FlutterRScanView(Context context, BinaryMessenger messenger, int i, Object o) { 54 | Map map = (Map) o; 55 | Boolean _isPlay = (Boolean) map.get("isPlay"); 56 | isPlay = _isPlay == Boolean.TRUE; 57 | 58 | new EventChannel(messenger, scanViewType + "_" + i + "/event") 59 | .setStreamHandler(this); 60 | MethodChannel methodChannel = new MethodChannel(messenger, scanViewType + "_" + i + "/method"); 61 | methodChannel.setMethodCallHandler(this); 62 | textureView = new TextureView(context); 63 | lifecycleRegistry = new LifecycleRegistry(this); 64 | DisplayMetrics outMetrics = new DisplayMetrics(); 65 | 66 | WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 67 | manager.getDefaultDisplay().getMetrics(outMetrics); 68 | Log.d(TAG, "FlutterRScanView: " + outMetrics.toString()); 69 | mPreview = buildPreView(outMetrics.widthPixels, outMetrics.heightPixels); 70 | CameraX.bindToLifecycle(this, mPreview, buildImageAnalysis()); 71 | 72 | } 73 | 74 | @Override 75 | public View getView() { 76 | if (lifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED) { 77 | lifecycleRegistry.markState(Lifecycle.State.RESUMED); 78 | } 79 | return textureView; 80 | } 81 | 82 | @Override 83 | public void dispose() { 84 | Log.d("CameraX", "dispose"); 85 | lifecycleRegistry.markState(Lifecycle.State.DESTROYED); 86 | CameraX.unbindAll(); 87 | } 88 | 89 | @Override 90 | public void onListen(Object o, EventChannel.EventSink eventSink) { 91 | this.eventSink = eventSink; 92 | } 93 | 94 | @Override 95 | public void onMethodCall(MethodCall methodCall, @NonNull MethodChannel.Result result) { 96 | switch (methodCall.method) { 97 | case "startScan": 98 | isPlay = true; 99 | result.success(null); 100 | break; 101 | case "stopScan": 102 | isPlay = false; 103 | result.success(null); 104 | break; 105 | case "setFlashMode": 106 | Boolean isOpen = methodCall.argument("isOpen"); 107 | mPreview.enableTorch(isOpen == Boolean.TRUE); 108 | result.success(true); 109 | break; 110 | case "getFlashMode": 111 | result.success(mPreview.isTorchOn()); 112 | break; 113 | default: 114 | result.notImplemented(); 115 | break; 116 | } 117 | } 118 | 119 | @Override 120 | public void onCancel(Object o) { 121 | Log.d("CameraX", "onCancel"); 122 | eventSink = null; 123 | } 124 | 125 | @NonNull 126 | @Override 127 | public Lifecycle getLifecycle() { 128 | Log.d("CameraX", "getLifecycle" + lifecycleRegistry.getCurrentState().name()); 129 | return lifecycleRegistry; 130 | } 131 | 132 | private Preview buildPreView(int width, int height) { 133 | PreviewConfig config = new PreviewConfig.Builder() 134 | .setTargetAspectRatio(Rational.parseRational(width + ":" + height)) 135 | .setTargetResolution(new Size(width, height)) 136 | .build(); 137 | Preview preview = new Preview(config); 138 | preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() { 139 | @Override 140 | public void onUpdated(@NonNull Preview.PreviewOutput output) { 141 | if (textureView != null) { 142 | textureView.setSurfaceTexture(output.getSurfaceTexture()); 143 | } 144 | } 145 | }); 146 | return preview; 147 | } 148 | 149 | private UseCase buildImageAnalysis() { 150 | ImageAnalysisConfig config = new ImageAnalysisConfig.Builder() 151 | .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE) 152 | .build(); 153 | 154 | ImageAnalysis analysis = new ImageAnalysis(config); 155 | analysis.setAnalyzer(new QRCodeAnalyzer()); 156 | 157 | return analysis; 158 | } 159 | 160 | 161 | private class QRCodeAnalyzer implements ImageAnalysis.Analyzer { 162 | private static final String TAG = "QRCodeAnalyzer"; 163 | private MultiFormatReader reader = new MultiFormatReader(); 164 | 165 | @Override 166 | public void analyze(ImageProxy image, int rotationDegrees) { 167 | long currentTimestamp = System.currentTimeMillis(); 168 | if (currentTimestamp - lastCurrentTimestamp >= 1L && isPlay == Boolean.TRUE) { 169 | if (ImageFormat.YUV_420_888 != image.getFormat()) { 170 | Log.d(TAG, "analyze: " + image.getFormat()); 171 | return; 172 | } 173 | ByteBuffer buffer = image.getPlanes()[0].getBuffer(); 174 | byte[] array = new byte[buffer.remaining()]; 175 | buffer.get(array); 176 | int height = image.getHeight(); 177 | int width = image.getWidth(); 178 | PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(array, 179 | width, 180 | height, 181 | 0, 182 | 0, 183 | width, 184 | height, 185 | false); 186 | BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); 187 | try { 188 | final Result decode = reader.decode(binaryBitmap); 189 | if (decode != null && eventSink != null) { 190 | textureView.post(new Runnable() { 191 | @Override 192 | public void run() { 193 | if (eventSink != null) 194 | eventSink.success(RScanResultUtils.toMap(decode)); 195 | } 196 | }); 197 | } 198 | } catch (Exception e) { 199 | buffer.clear(); 200 | // Log.d(TAG, "analyze: error "); 201 | } 202 | lastCurrentTimestamp = currentTimestamp; 203 | } 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanView/RScanViewFactory.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan.RScanView; 2 | 3 | import android.content.Context; 4 | 5 | import io.flutter.plugin.common.BinaryMessenger; 6 | import io.flutter.plugin.common.StandardMessageCodec; 7 | import io.flutter.plugin.platform.PlatformView; 8 | import io.flutter.plugin.platform.PlatformViewFactory; 9 | 10 | public class RScanViewFactory extends PlatformViewFactory { 11 | private final BinaryMessenger messenger; 12 | 13 | RScanViewFactory(BinaryMessenger messenger) { 14 | super(StandardMessageCodec.INSTANCE); 15 | this.messenger=messenger; 16 | } 17 | 18 | @Override 19 | public PlatformView create(Context context, int i, Object o) { 20 | return new FlutterRScanView(context,messenger,i,o); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/src/main/java/com/rhyme/r_scan/RScanView/RScanViewPlugin.java: -------------------------------------------------------------------------------- 1 | package com.rhyme.r_scan.RScanView; 2 | 3 | import io.flutter.plugin.common.BinaryMessenger; 4 | import io.flutter.plugin.platform.PlatformViewRegistry; 5 | 6 | public class RScanViewPlugin { 7 | 8 | public static void registerWith(PlatformViewRegistry platformViewRegistry, BinaryMessenger messenger) { 9 | platformViewRegistry 10 | .registerViewFactory("com.rhyme_lph/r_scan_view", new RScanViewFactory(messenger)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"image_picker","path":"/Users/lipenghui/.pub-cache/hosted/pub.flutter-io.cn/image_picker-0.6.7+14/","dependencies":[]},{"name":"permission_handler","path":"/Users/lipenghui/.pub-cache/hosted/pub.flutter-io.cn/permission_handler-3.3.0/","dependencies":[]},{"name":"r_scan","path":"/Users/lipenghui/Documents/flutter_project/other/r_scan/","dependencies":[]}],"android":[{"name":"flutter_plugin_android_lifecycle","path":"/Users/lipenghui/.pub-cache/hosted/pub.flutter-io.cn/flutter_plugin_android_lifecycle-1.0.11/","dependencies":[]},{"name":"image_picker","path":"/Users/lipenghui/.pub-cache/hosted/pub.flutter-io.cn/image_picker-0.6.7+14/","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"permission_handler","path":"/Users/lipenghui/.pub-cache/hosted/pub.flutter-io.cn/permission_handler-3.3.0/","dependencies":[]},{"name":"r_scan","path":"/Users/lipenghui/Documents/flutter_project/other/r_scan/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"image_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"permission_handler","dependencies":[]},{"name":"r_scan","dependencies":[]}],"date_created":"2021-06-11 20:23:05.203628","version":"2.2.0"} -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ```dart 2 | import 'package:flutter/material.dart'; 3 | import 'package:permission_handler/permission_handler.dart'; 4 | import 'package:r_scan/r_scan.dart'; 5 | 6 | class RScanDialog extends StatefulWidget { 7 | @override 8 | _RScanDialogState createState() => _RScanDialogState(); 9 | } 10 | 11 | class _RScanDialogState extends State { 12 | RScanController _controller; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | initController(); 18 | } 19 | bool isFirst=true; 20 | 21 | 22 | Future initController() async { 23 | _controller = RScanController(); 24 | _controller.addListener(() { 25 | 26 | final result = _controller.result; 27 | if (result != null) { 28 | if(isFirst){ 29 | Navigator.of(context).pop(result); 30 | isFirst=false; 31 | } 32 | } 33 | }); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return MaterialApp( 39 | home: Scaffold( 40 | backgroundColor: Colors.black, 41 | body: FutureBuilder( 42 | future: canOpenCameraView(), 43 | builder: (BuildContext context, AsyncSnapshot snapshot) { 44 | if (snapshot.hasData && snapshot.data == true) { 45 | return Stack( 46 | children: [ 47 | ScanImageView( 48 | child: RScanView( 49 | controller: _controller, 50 | ), 51 | ), 52 | Positioned( 53 | top: 16, 54 | right: 16, 55 | child: FutureBuilder(future: getFlashMode(),builder: _buildFlashBtn,)) 56 | ], 57 | ); 58 | } else { 59 | return Container(); 60 | } 61 | }, 62 | ), 63 | ), 64 | ); 65 | } 66 | 67 | Future getFlashMode()async{ 68 | bool isOpen = false; 69 | try{ 70 | isOpen = await _controller.getFlashMode(); 71 | 72 | }catch(_){ 73 | 74 | } 75 | return isOpen; 76 | } 77 | 78 | Future canOpenCameraView() async { 79 | var status = 80 | await PermissionHandler().checkPermissionStatus(PermissionGroup.camera); 81 | if (status != PermissionStatus.granted) { 82 | var future = await PermissionHandler() 83 | .requestPermissions([PermissionGroup.camera]); 84 | for (final item in future.entries) { 85 | if (item.value != PermissionStatus.granted) { 86 | return false; 87 | } 88 | } 89 | } else { 90 | return true; 91 | } 92 | return true; 93 | } 94 | 95 | Widget _buildFlashBtn(BuildContext context, AsyncSnapshot snapshot) { 96 | return snapshot.hasData?IconButton(icon: Icon(snapshot.data?Icons.flash_on:Icons.flash_off),color: Colors.white, onPressed: (){ 97 | if(snapshot.data){ 98 | _controller.setFlashMode(false); 99 | }else{ 100 | _controller.setFlashMode(true); 101 | } 102 | setState(() {}); 103 | }):Container(); 104 | 105 | } 106 | } 107 | 108 | class ScanImageView extends StatefulWidget { 109 | final Widget child; 110 | 111 | const ScanImageView({Key key, this.child}) : super(key: key); 112 | 113 | @override 114 | _ScanImageViewState createState() => _ScanImageViewState(); 115 | } 116 | 117 | class _ScanImageViewState extends State 118 | with TickerProviderStateMixin { 119 | AnimationController controller; 120 | 121 | @override 122 | void initState() { 123 | super.initState(); 124 | controller = AnimationController( 125 | vsync: this, duration: Duration(milliseconds: 1000)); 126 | controller.repeat(reverse: true); 127 | } 128 | 129 | @override 130 | void dispose() { 131 | controller.dispose(); 132 | super.dispose(); 133 | } 134 | 135 | @override 136 | Widget build(BuildContext context) { 137 | return AnimatedBuilder( 138 | animation: controller, 139 | builder: (BuildContext context, Widget child) => CustomPaint( 140 | foregroundPainter: 141 | _ScanPainter(controller.value, Colors.white, Colors.green), 142 | child: widget.child, 143 | willChange: true, 144 | )); 145 | } 146 | } 147 | 148 | ``` -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.rhyme.r_scan_example" 38 | minSdkVersion 21 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/images/qrCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/images/qrCode.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | r_scan_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | 扫描二维码时需要使用您的相机 27 | NSPhotoLibraryUsageDescription 28 | 扫描二维码时需要访问您的相册 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | io.flutter.embedded_views_preview 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:image_picker/image_picker.dart'; 6 | import 'package:permission_handler/permission_handler.dart'; 7 | import 'package:r_scan_example/scan_dialog.dart'; 8 | import 'scan_camera_dialog.dart'; 9 | import 'package:r_scan/r_scan.dart'; 10 | 11 | void main() async { 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | 14 | runApp(MyApp()); 15 | } 16 | 17 | class MyApp extends StatefulWidget { 18 | @override 19 | _MyAppState createState() => _MyAppState(); 20 | } 21 | 22 | class _MyAppState extends State { 23 | @override 24 | Widget build(BuildContext context) { 25 | return MaterialApp( 26 | home: MyPage(), 27 | ); 28 | } 29 | } 30 | 31 | class MyPage extends StatefulWidget { 32 | @override 33 | _MyPageState createState() => _MyPageState(); 34 | } 35 | 36 | class _MyPageState extends State { 37 | RScanResult result; 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Scaffold( 42 | appBar: AppBar( 43 | title: Text('scan example'), 44 | ), 45 | body: Column( 46 | mainAxisAlignment: MainAxisAlignment.center, 47 | crossAxisAlignment: CrossAxisAlignment.center, 48 | children: [ 49 | Center( 50 | child: Text(result == null 51 | ? '点击下方按钮开始扫码' 52 | : '扫码结果${result.toString().split(',').join('\n')}')), 53 | Center( 54 | child: FlatButton( 55 | onPressed: () async { 56 | final result = await Navigator.of(context).push( 57 | MaterialPageRoute( 58 | builder: (BuildContext context) => 59 | RScanCameraDialog())); 60 | setState(() { 61 | this.result = result; 62 | }); 63 | }, 64 | child: Text('RScanCamera开始扫码'), 65 | ), 66 | ), 67 | Center( 68 | child: FlatButton( 69 | onPressed: () async { 70 | final result = await Navigator.of(context).push( 71 | MaterialPageRoute( 72 | builder: (BuildContext context) => RScanDialog())); 73 | setState(() { 74 | this.result = result; 75 | }); 76 | }, 77 | child: Text('RScanView开始扫码(已弃用)'), 78 | ), 79 | ), 80 | Center( 81 | child: FlatButton( 82 | onPressed: () async { 83 | if (await canReadStorage()) { 84 | var image = 85 | await ImagePicker.pickImage(source: ImageSource.gallery); 86 | if (image != null) { 87 | final result = await RScan.scanImagePath(image.path); 88 | setState(() { 89 | this.result = result; 90 | }); 91 | } 92 | } 93 | }, 94 | child: Text('选择图片扫描'), 95 | ), 96 | ), 97 | Center( 98 | child: FlatButton( 99 | onPressed: () async { 100 | final result = await RScan.scanImageUrl( 101 | "https://s.cn.bing.net/th?id=OJ.5F0gxqWmxskS0Q&w=75&h=75&pid=MSNJVFeeds"); 102 | setState(() { 103 | this.result = result; 104 | }); 105 | }, 106 | child: Text('网络图片解析'), 107 | ), 108 | ), 109 | Center( 110 | child: FlatButton( 111 | onPressed: () async { 112 | ByteData data = await rootBundle.load('images/qrCode.png'); 113 | final result = 114 | await RScan.scanImageMemory(data.buffer.asUint8List()); 115 | setState(() { 116 | this.result = result; 117 | }); 118 | }, 119 | child: Text('内存图片解析'), 120 | ), 121 | ), 122 | ], 123 | ), 124 | ); 125 | } 126 | 127 | Future canReadStorage() async { 128 | if (Platform.isIOS) return true; 129 | var status = await PermissionHandler() 130 | .checkPermissionStatus(PermissionGroup.storage); 131 | if (status != PermissionStatus.granted) { 132 | var future = await PermissionHandler() 133 | .requestPermissions([PermissionGroup.storage]); 134 | for (final item in future.entries) { 135 | if (item.value != PermissionStatus.granted) { 136 | return false; 137 | } 138 | } 139 | } else { 140 | return true; 141 | } 142 | return true; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /example/lib/scan_camera_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:permission_handler/permission_handler.dart'; 3 | import 'package:r_scan_example/scan_dialog.dart'; 4 | import 'package:r_scan/src/r_scan_camera.dart'; 5 | 6 | List rScanCameras; 7 | 8 | class RScanCameraDialog extends StatefulWidget { 9 | @override 10 | _RScanCameraDialogState createState() => _RScanCameraDialogState(); 11 | } 12 | 13 | class _RScanCameraDialogState extends State { 14 | RScanCameraController _controller; 15 | bool isFirst = true; 16 | 17 | void initCamera() async { 18 | if (rScanCameras == null || rScanCameras.length == 0) { 19 | final result = await PermissionHandler() 20 | .checkPermissionStatus(PermissionGroup.camera); 21 | if (result == PermissionStatus.granted) { 22 | rScanCameras = await availableRScanCameras(); 23 | print('返回可用的相机:${rScanCameras.join('\n')}'); 24 | } else { 25 | final resultMap = await PermissionHandler() 26 | .requestPermissions([PermissionGroup.camera]); 27 | if (resultMap[PermissionGroup.camera] == PermissionStatus.granted) { 28 | rScanCameras = await availableRScanCameras(); 29 | } else { 30 | print('相机权限被拒绝,无法使用'); 31 | } 32 | } 33 | } 34 | if (rScanCameras != null && rScanCameras.length > 0) { 35 | _controller = RScanCameraController( 36 | rScanCameras[0], RScanCameraResolutionPreset.high) 37 | ..addListener(() { 38 | final result = _controller.result; 39 | if (result != null) { 40 | if (isFirst) { 41 | Navigator.of(context).pop(result); 42 | isFirst = false; 43 | } 44 | } 45 | }) 46 | ..initialize().then((_) { 47 | if (!mounted) { 48 | return; 49 | } 50 | setState(() {}); 51 | }); 52 | } 53 | } 54 | 55 | @override 56 | void initState() { 57 | super.initState(); 58 | initCamera(); 59 | } 60 | 61 | @override 62 | void dispose() { 63 | _controller?.dispose(); 64 | super.dispose(); 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | if (rScanCameras == null || rScanCameras.length == 0) { 70 | return Scaffold( 71 | body: Container( 72 | alignment: Alignment.center, 73 | child: Text('not have available camera'), 74 | ), 75 | ); 76 | } 77 | if (!_controller.value.isInitialized) { 78 | return Container(); 79 | } 80 | return Scaffold( 81 | backgroundColor: Colors.black, 82 | body: Stack( 83 | children: [ 84 | ScanImageView( 85 | child: AspectRatio( 86 | aspectRatio: _controller.value.aspectRatio, 87 | child: RScanCamera(_controller), 88 | ), 89 | ), 90 | Align( 91 | alignment: Alignment.bottomCenter, 92 | child: FutureBuilder( 93 | future: getFlashMode(), 94 | builder: _buildFlashBtn, 95 | )) 96 | ], 97 | ), 98 | ); 99 | } 100 | 101 | Future getFlashMode() async { 102 | bool isOpen = false; 103 | try { 104 | isOpen = await _controller.getFlashMode(); 105 | } catch (_) {} 106 | return isOpen; 107 | } 108 | 109 | Widget _buildFlashBtn(BuildContext context, AsyncSnapshot snapshot) { 110 | return snapshot.hasData 111 | ? Padding( 112 | padding: EdgeInsets.only( 113 | bottom: 24 + MediaQuery.of(context).padding.bottom), 114 | child: IconButton( 115 | icon: Icon(snapshot.data ? Icons.flash_on : Icons.flash_off), 116 | color: Colors.white, 117 | iconSize: 46, 118 | onPressed: () { 119 | if (snapshot.data) { 120 | _controller.setFlashMode(false); 121 | } else { 122 | _controller.setFlashMode(true); 123 | } 124 | setState(() {}); 125 | }), 126 | ) 127 | : Container(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /example/lib/scan_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:permission_handler/permission_handler.dart'; 3 | import 'package:r_scan/r_scan.dart'; 4 | 5 | class RScanDialog extends StatefulWidget { 6 | @override 7 | _RScanDialogState createState() => _RScanDialogState(); 8 | } 9 | 10 | class _RScanDialogState extends State { 11 | RScanController _controller; 12 | 13 | @override 14 | void initState() { 15 | super.initState(); 16 | initController(); 17 | } 18 | 19 | bool isFirst = true; 20 | 21 | Future initController() async { 22 | _controller = RScanController(); 23 | _controller.addListener(() { 24 | final result = _controller.result; 25 | if (result != null) { 26 | if (isFirst) { 27 | Navigator.of(context).pop(result); 28 | isFirst = false; 29 | } 30 | } 31 | }); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Scaffold( 37 | backgroundColor: Colors.black, 38 | body: FutureBuilder( 39 | future: canOpenCameraView(), 40 | builder: (BuildContext context, AsyncSnapshot snapshot) { 41 | if (snapshot.hasData && snapshot.data == true) { 42 | return Stack( 43 | children: [ 44 | ScanImageView( 45 | child: RScanView( 46 | controller: _controller, 47 | ), 48 | ), 49 | Align( 50 | alignment: Alignment.bottomCenter, 51 | child: FutureBuilder( 52 | future: getFlashMode(), 53 | builder: _buildFlashBtn, 54 | )) 55 | ], 56 | ); 57 | } else { 58 | return Container(); 59 | } 60 | }, 61 | ), 62 | ); 63 | } 64 | 65 | Future getFlashMode() async { 66 | bool isOpen = false; 67 | try { 68 | isOpen = await _controller.getFlashMode(); 69 | } catch (_) {} 70 | return isOpen; 71 | } 72 | 73 | Future canOpenCameraView() async { 74 | var status = 75 | await PermissionHandler().checkPermissionStatus(PermissionGroup.camera); 76 | if (status != PermissionStatus.granted) { 77 | var future = await PermissionHandler() 78 | .requestPermissions([PermissionGroup.camera]); 79 | for (final item in future.entries) { 80 | if (item.value != PermissionStatus.granted) { 81 | return false; 82 | } 83 | } 84 | } else { 85 | return true; 86 | } 87 | return true; 88 | } 89 | 90 | Widget _buildFlashBtn(BuildContext context, AsyncSnapshot snapshot) { 91 | return snapshot.hasData 92 | ? Padding( 93 | padding: EdgeInsets.only( 94 | bottom: 24 + MediaQuery.of(context).padding.bottom), 95 | child: IconButton( 96 | icon: Icon(snapshot.data ? Icons.flash_on : Icons.flash_off), 97 | color: Colors.white, 98 | iconSize: 46, 99 | onPressed: () { 100 | if (snapshot.data) { 101 | _controller.setFlashMode(false); 102 | } else { 103 | _controller.setFlashMode(true); 104 | } 105 | setState(() {}); 106 | }), 107 | ) 108 | : Container(); 109 | } 110 | } 111 | 112 | class ScanImageView extends StatefulWidget { 113 | final Widget child; 114 | 115 | const ScanImageView({Key key, this.child}) : super(key: key); 116 | 117 | @override 118 | _ScanImageViewState createState() => _ScanImageViewState(); 119 | } 120 | 121 | class _ScanImageViewState extends State 122 | with TickerProviderStateMixin { 123 | AnimationController controller; 124 | 125 | @override 126 | void initState() { 127 | super.initState(); 128 | controller = AnimationController( 129 | vsync: this, duration: Duration(milliseconds: 1000)); 130 | controller.repeat(reverse: true); 131 | } 132 | 133 | @override 134 | void dispose() { 135 | controller.dispose(); 136 | super.dispose(); 137 | } 138 | 139 | @override 140 | Widget build(BuildContext context) { 141 | return AnimatedBuilder( 142 | animation: controller, 143 | builder: (BuildContext context, Widget child) => CustomPaint( 144 | foregroundPainter: 145 | _ScanPainter(controller.value, Colors.white, Colors.green), 146 | child: widget.child, 147 | willChange: true, 148 | )); 149 | } 150 | } 151 | 152 | class _ScanPainter extends CustomPainter { 153 | final double value; 154 | final Color borderColor; 155 | final Color scanColor; 156 | 157 | _ScanPainter(this.value, this.borderColor, this.scanColor); 158 | 159 | Paint _paint; 160 | 161 | @override 162 | void paint(Canvas canvas, Size size) { 163 | if (_paint == null) { 164 | initPaint(); 165 | } 166 | double width = size.width; 167 | double height = size.height; 168 | 169 | double boxWidth = size.width * 2 / 3; 170 | double boxHeight = height / 4; 171 | 172 | double left = (width - boxWidth) / 2; 173 | double top = boxHeight; 174 | double bottom = boxHeight * 2; 175 | double right = left + boxWidth; 176 | _paint.color = borderColor; 177 | final rect = Rect.fromLTWH(left, top, boxWidth, boxHeight); 178 | canvas.drawRect(rect, _paint); 179 | 180 | _paint.strokeWidth = 3; 181 | 182 | Path path1 = Path() 183 | ..moveTo(left, top + 10) 184 | ..lineTo(left, top) 185 | ..lineTo(left + 10, top); 186 | canvas.drawPath(path1, _paint); 187 | Path path2 = Path() 188 | ..moveTo(left, bottom - 10) 189 | ..lineTo(left, bottom) 190 | ..lineTo(left + 10, bottom); 191 | canvas.drawPath(path2, _paint); 192 | Path path3 = Path() 193 | ..moveTo(right, bottom - 10) 194 | ..lineTo(right, bottom) 195 | ..lineTo(right - 10, bottom); 196 | canvas.drawPath(path3, _paint); 197 | Path path4 = Path() 198 | ..moveTo(right, top + 10) 199 | ..lineTo(right, top) 200 | ..lineTo(right - 10, top); 201 | canvas.drawPath(path4, _paint); 202 | 203 | _paint.color = scanColor; 204 | 205 | final scanRect = Rect.fromLTWH( 206 | left + 10, top + 10 + (value * (boxHeight - 20)), boxWidth - 20, 3); 207 | 208 | _paint.shader = LinearGradient(colors: [ 209 | Colors.white54, 210 | Colors.white, 211 | Colors.white54, 212 | ], stops: [ 213 | 0.0, 214 | 0.5, 215 | 1, 216 | ]).createShader(scanRect); 217 | canvas.drawRect(scanRect, _paint); 218 | } 219 | 220 | @override 221 | bool shouldRepaint(CustomPainter oldDelegate) { 222 | return true; 223 | } 224 | 225 | void initPaint() { 226 | _paint = Paint() 227 | ..style = PaintingStyle.stroke 228 | ..strokeWidth = 1 229 | ..isAntiAlias = true 230 | ..strokeCap = StrokeCap.round 231 | ..strokeJoin = StrokeJoin.round; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.6.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "0.1.3" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.2.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_plugin_android_lifecycle: 66 | dependency: transitive 67 | description: 68 | name: flutter_plugin_android_lifecycle 69 | url: "https://pub.flutter-io.cn" 70 | source: hosted 71 | version: "1.0.11" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | http: 78 | dependency: transitive 79 | description: 80 | name: http 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "0.12.2" 84 | http_parser: 85 | dependency: transitive 86 | description: 87 | name: http_parser 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "3.1.4" 91 | image_picker: 92 | dependency: "direct main" 93 | description: 94 | name: image_picker 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "0.6.7+14" 98 | image_picker_platform_interface: 99 | dependency: transitive 100 | description: 101 | name: image_picker_platform_interface 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.1.1" 105 | matcher: 106 | dependency: transitive 107 | description: 108 | name: matcher 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "0.12.10" 112 | meta: 113 | dependency: transitive 114 | description: 115 | name: meta 116 | url: "https://pub.flutter-io.cn" 117 | source: hosted 118 | version: "1.3.0" 119 | path: 120 | dependency: transitive 121 | description: 122 | name: path 123 | url: "https://pub.flutter-io.cn" 124 | source: hosted 125 | version: "1.8.0" 126 | pedantic: 127 | dependency: transitive 128 | description: 129 | name: pedantic 130 | url: "https://pub.flutter-io.cn" 131 | source: hosted 132 | version: "1.9.2" 133 | permission_handler: 134 | dependency: "direct main" 135 | description: 136 | name: permission_handler 137 | url: "https://pub.flutter-io.cn" 138 | source: hosted 139 | version: "3.3.0" 140 | plugin_platform_interface: 141 | dependency: transitive 142 | description: 143 | name: plugin_platform_interface 144 | url: "https://pub.flutter-io.cn" 145 | source: hosted 146 | version: "1.0.3" 147 | r_scan: 148 | dependency: "direct dev" 149 | description: 150 | path: ".." 151 | relative: true 152 | source: path 153 | version: "0.1.6" 154 | sky_engine: 155 | dependency: transitive 156 | description: flutter 157 | source: sdk 158 | version: "0.0.99" 159 | source_span: 160 | dependency: transitive 161 | description: 162 | name: source_span 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "1.8.1" 166 | stack_trace: 167 | dependency: transitive 168 | description: 169 | name: stack_trace 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "1.10.0" 173 | stream_channel: 174 | dependency: transitive 175 | description: 176 | name: stream_channel 177 | url: "https://pub.flutter-io.cn" 178 | source: hosted 179 | version: "2.1.0" 180 | string_scanner: 181 | dependency: transitive 182 | description: 183 | name: string_scanner 184 | url: "https://pub.flutter-io.cn" 185 | source: hosted 186 | version: "1.1.0" 187 | term_glyph: 188 | dependency: transitive 189 | description: 190 | name: term_glyph 191 | url: "https://pub.flutter-io.cn" 192 | source: hosted 193 | version: "1.2.0" 194 | test_api: 195 | dependency: transitive 196 | description: 197 | name: test_api 198 | url: "https://pub.flutter-io.cn" 199 | source: hosted 200 | version: "0.3.0" 201 | typed_data: 202 | dependency: transitive 203 | description: 204 | name: typed_data 205 | url: "https://pub.flutter-io.cn" 206 | source: hosted 207 | version: "1.3.0" 208 | vector_math: 209 | dependency: transitive 210 | description: 211 | name: vector_math 212 | url: "https://pub.flutter-io.cn" 213 | source: hosted 214 | version: "2.1.0" 215 | sdks: 216 | dart: ">=2.12.0 <3.0.0" 217 | flutter: ">=1.12.13" 218 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: r_scan_example 2 | description: Demonstrates how to use the r_scan plugin. 3 | publish_to: 'none' 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | #权限请求 13 | permission_handler: ^3.0.0 14 | # The following adds the Cupertino Icons font to your application. 15 | # Use with the CupertinoIcons class for iOS style icons. 16 | cupertino_icons: ^0.1.2 17 | image_picker: ^0.6.1+10 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | r_scan: 23 | path: ../ 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://dart.dev/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | 31 | # The following line ensures that the Material Icons font is 32 | # included with your application, so that you can use the icons in 33 | # the material Icons class. 34 | uses-material-design: true 35 | assets: 36 | - images/qrCode.png 37 | # To add assets to your application, add an assets section, like this: 38 | # assets: 39 | # - images/a_dot_burr.jpeg 40 | # - images/a_dot_ham.jpeg 41 | 42 | # An image asset can refer to one or more resolution-specific "variants", see 43 | # https://flutter.dev/assets-and-images/#resolution-aware. 44 | 45 | # For details regarding adding assets from package dependencies, see 46 | # https://flutter.dev/assets-and-images/#from-packages 47 | 48 | # To add custom fonts to your application, add a fonts section here, 49 | # in this "flutter" section. Each entry in this list should have a 50 | # "family" key with the font family name, and a "fonts" key with a 51 | # list giving the asset and other descriptors for the font. For 52 | # example: 53 | # fonts: 54 | # - family: Schyler 55 | # fonts: 56 | # - asset: fonts/Schyler-Regular.ttf 57 | # - asset: fonts/Schyler-Italic.ttf 58 | # style: italic 59 | # - family: Trajan Pro 60 | # fonts: 61 | # - asset: fonts/TrajanPro.ttf 62 | # - asset: fonts/TrajanPro_Bold.ttf 63 | # weight: 700 64 | # 65 | # For details regarding fonts from package dependencies, 66 | # see https://flutter.dev/custom-fonts/#from-packages 67 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:r_scan_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FlutterRScanView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterRScanView.h 3 | // r_scan 4 | // 5 | // Created by rhymelph on 2019/11/21. 6 | // 7 | 8 | 9 | #import 10 | #import 11 | 12 | @interface FlutterRScanView : NSObject 13 | 14 | -(instancetype _Nullable )initWithFrame:(CGRect)frame viewindentifier:(int64_t)viewId arguments:(id _Nullable)args binaryMessenger:(NSObject *_Nonnull)messenger; 15 | 16 | -(nonnull UIView*) view; 17 | 18 | @end 19 | 20 | @interface FlutterRScanViewFactory : NSObject 21 | 22 | -(instancetype _Nullable )initWithMessenger:(NSObject*_Nonnull)messenger; 23 | 24 | @end 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ios/Classes/FlutterRScanView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterRScanView.m 3 | // r_scan 4 | // 5 | // Created by rhymelph on 2019/11/21. 6 | // 7 | 8 | #import "FlutterRScanView.h" 9 | #import "RScanView.h" 10 | static NSString * scanViewType=@"com.rhyme_lph/r_scan_view"; 11 | 12 | @implementation FlutterRScanViewFactory{ 13 | 14 | NSObject* _messenger; 15 | } 16 | 17 | - (instancetype)initWithMessenger:(NSObject *)messenger{ 18 | self = [super init]; 19 | if (self) { 20 | _messenger=messenger; 21 | } 22 | return self; 23 | } 24 | 25 | - (NSObject *)createArgsCodec{ 26 | return [FlutterStandardMessageCodec sharedInstance]; 27 | } 28 | - (NSObject *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{ 29 | FlutterRScanView * scanView=[[FlutterRScanView alloc]initWithFrame:frame viewindentifier:viewId arguments:args binaryMessenger:_messenger]; 30 | return scanView; 31 | 32 | } 33 | @end 34 | 35 | @interface FlutterRScanView() 36 | @property(nonatomic , strong)RScanView * view; 37 | 38 | 39 | @end 40 | @implementation FlutterRScanView{ 41 | 42 | } 43 | 44 | - (instancetype)initWithFrame:(CGRect)frame viewindentifier:(int64_t)viewId arguments:(id)args binaryMessenger:(NSObject *)messenger{ 45 | if(self = [super init]){ 46 | _view=[[RScanView alloc]initWithFrame:frame viewIdentifier:viewId arguments:args binaryMessenger:messenger]; 47 | _view.backgroundColor=[UIColor clearColor]; 48 | _view.frame=frame; 49 | 50 | } 51 | return self; 52 | } 53 | - (nonnull UIView *)view { 54 | return _view; 55 | } 56 | 57 | 58 | @end 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /ios/Classes/RScanCamera.h: -------------------------------------------------------------------------------- 1 | // 2 | // RScanCamera.h 3 | // r_scan 4 | // 5 | // Created by 李鹏辉 on 2020/2/24. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface RScanCamera : NSObject 14 | @property(readonly, nonatomic) NSObject *registry; 15 | @property(readonly, nonatomic) NSObject *messenger; 16 | 17 | - (instancetype)initWithRegistry:(NSObject *)registry 18 | messenger:(NSObject *)messenger; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /ios/Classes/RScanCamera.m: -------------------------------------------------------------------------------- 1 | // 2 | // RScanCamera.m 3 | // r_scan 4 | // 5 | // Created by 李鹏辉 on 2020/2/24. 6 | // 7 | 8 | #import "RScanCamera.h" 9 | #import 10 | #import 11 | #import 12 | #import "RScanResult.h" 13 | // Mirrors ResolutionPreset in camera.dart 14 | typedef enum { 15 | veryLow, 16 | low, 17 | medium, 18 | high, 19 | veryHigh, 20 | ultraHigh, 21 | max, 22 | } ResolutionPreset; 23 | 24 | static ResolutionPreset getResolutionPresetForString(NSString *preset) { 25 | if ([preset isEqualToString:@"veryLow"]) { 26 | return veryLow; 27 | } else if ([preset isEqualToString:@"low"]) { 28 | return low; 29 | } else if ([preset isEqualToString:@"medium"]) { 30 | return medium; 31 | } else if ([preset isEqualToString:@"high"]) { 32 | return high; 33 | } else if ([preset isEqualToString:@"veryHigh"]) { 34 | return veryHigh; 35 | } else if ([preset isEqualToString:@"ultraHigh"]) { 36 | return ultraHigh; 37 | } else if ([preset isEqualToString:@"max"]) { 38 | return max; 39 | } else { 40 | NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain 41 | code:NSURLErrorUnknown 42 | userInfo:@{ 43 | NSLocalizedDescriptionKey : [NSString 44 | stringWithFormat:@"Unknown resolution preset %@", preset] 45 | }]; 46 | @throw error; 47 | } 48 | } 49 | //获取错误 50 | static FlutterError *getFlutterError(NSError *error) { 51 | return [FlutterError errorWithCode:[NSString stringWithFormat:@"%d", (int)error.code] 52 | message:error.localizedDescription 53 | details:error.domain]; 54 | } 55 | 56 | @interface RScanFLTCam : NSObject 57 | @property(readonly, nonatomic) int64_t textureId; 58 | @property(assign, nonatomic) ResolutionPreset resolutionPreset; 59 | //链接相机用的 60 | @property(readonly, nonatomic) AVCaptureSession *captureSession; 61 | //获取相机设备 62 | @property(readonly, nonatomic) AVCaptureDevice *captureDevice; 63 | //视频输入 64 | @property(readonly, nonatomic) AVCaptureInput *captureVideoInput; 65 | //视频输出 66 | @property(readonly, nonatomic) AVCaptureMetadataOutput * captureOutput; 67 | //视频输出2 68 | @property(readonly, nonatomic) AVCaptureVideoDataOutput *captureVideoOutput; 69 | @property(readonly, nonatomic) CGSize previewSize; 70 | 71 | @property(nonatomic) FlutterEventSink eventSink; 72 | @property(readonly) CVPixelBufferRef volatile latestPixelBuffer; 73 | //第一帧回掉 74 | @property(nonatomic, copy) void (^onFrameAvailable)(void); 75 | //channel用于返回数据给data 76 | @property(nonatomic) FlutterEventChannel *eventChannel; 77 | @end 78 | 79 | @implementation RScanFLTCam{ 80 | dispatch_queue_t _dispatchQueue; 81 | } 82 | FourCharCode const rScanVideoFormat = kCVPixelFormatType_32BGRA; 83 | - (instancetype)initWitchCameraName:(NSString*)cameraName resolutionPreset:(NSString*)resolutionPreset dispatchQueue:(dispatch_queue_t)dispatchQueue error:(NSError **)error{ 84 | self = [super init]; 85 | NSAssert(self, @"super init cannot be nil"); 86 | @try { 87 | _resolutionPreset =getResolutionPresetForString(resolutionPreset); 88 | } @catch (NSError *e) { 89 | *error = e; 90 | } 91 | _dispatchQueue =dispatchQueue; 92 | _captureSession=[[AVCaptureSession alloc]init]; 93 | _captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName]; 94 | 95 | NSError *localError =nil; 96 | _captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice error:&localError]; 97 | 98 | if(localError){ 99 | *error = localError; 100 | return nil; 101 | } 102 | 103 | _captureVideoOutput = [AVCaptureVideoDataOutput new]; 104 | _captureVideoOutput.videoSettings = @{(NSString*)kCVPixelBufferPixelFormatTypeKey: @(rScanVideoFormat)}; 105 | [_captureVideoOutput setAlwaysDiscardsLateVideoFrames:YES]; 106 | [_captureVideoOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; 107 | 108 | //链接 109 | AVCaptureConnection* connection =[AVCaptureConnection connectionWithInputPorts:_captureVideoInput.ports output:_captureVideoOutput]; 110 | 111 | if ([_captureDevice position] == AVCaptureDevicePositionFront) { 112 | connection.videoMirrored = YES; 113 | } 114 | if([connection isVideoOrientationSupported]){ 115 | connection.videoOrientation =AVCaptureVideoOrientationPortrait; 116 | } 117 | [_captureSession addInputWithNoConnections:_captureVideoInput]; 118 | [_captureSession addOutputWithNoConnections:_captureVideoOutput]; 119 | 120 | _captureOutput=[[AVCaptureMetadataOutput alloc]init]; 121 | 122 | 123 | //设置代理,在主线程刷新 124 | [_captureOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; 125 | [_captureSession addOutput:_captureOutput]; 126 | _captureOutput.metadataObjectTypes=_captureOutput.availableMetadataObjectTypes; 127 | //扫码区域的大小 128 | AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession]; 129 | layer.videoGravity = AVLayerVideoGravityResizeAspectFill; 130 | // layer.frame = CGRectMake(left, top, size, size); 131 | // [_captureOutput rectOfInterest]; 132 | [_captureOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; 133 | [_captureOutput setMetadataObjectTypes:@[AVMetadataObjectTypeAztecCode,AVMetadataObjectTypeCode39Code, 134 | AVMetadataObjectTypeCode93Code,AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeDataMatrixCode,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeITF14Code,AVMetadataObjectTypePDF417Code,AVMetadataObjectTypeQRCode,AVMetadataObjectTypeUPCECode]]; 135 | [_captureSession addConnection:connection]; 136 | [self setCaptureSessionPreset:_resolutionPreset]; 137 | 138 | return self; 139 | } 140 | - (void)setCaptureSessionPreset:(ResolutionPreset)resolutionPreset { 141 | switch (resolutionPreset) { 142 | case max: 143 | if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) { 144 | _captureSession.sessionPreset = AVCaptureSessionPresetHigh; 145 | _previewSize = 146 | CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width, 147 | _captureDevice.activeFormat.highResolutionStillImageDimensions.height); 148 | break; 149 | } 150 | case ultraHigh: 151 | if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { 152 | _captureSession.sessionPreset = AVCaptureSessionPreset3840x2160; 153 | _previewSize = CGSizeMake(3840, 2160); 154 | break; 155 | } 156 | case veryHigh: 157 | if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) { 158 | _captureSession.sessionPreset = AVCaptureSessionPreset1920x1080; 159 | _previewSize = CGSizeMake(1920, 1080); 160 | break; 161 | } 162 | case high: 163 | if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) { 164 | _captureSession.sessionPreset = AVCaptureSessionPreset1280x720; 165 | _previewSize = CGSizeMake(1280, 720); 166 | break; 167 | } 168 | case medium: 169 | if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) { 170 | _captureSession.sessionPreset = AVCaptureSessionPreset640x480; 171 | _previewSize = CGSizeMake(640, 480); 172 | break; 173 | } 174 | case low: 175 | if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset352x288]) { 176 | _captureSession.sessionPreset = AVCaptureSessionPreset352x288; 177 | _previewSize = CGSizeMake(352, 288); 178 | break; 179 | } 180 | default: 181 | if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetLow]) { 182 | _captureSession.sessionPreset = AVCaptureSessionPresetLow; 183 | _previewSize = CGSizeMake(352, 288); 184 | } else { 185 | NSError *error = 186 | [NSError errorWithDomain:NSCocoaErrorDomain 187 | code:NSURLErrorUnknown 188 | userInfo:@{ 189 | NSLocalizedDescriptionKey : 190 | @"No capture session available for current capture session." 191 | }]; 192 | @throw error; 193 | } 194 | } 195 | } 196 | 197 | - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{ 198 | if (metadataObjects.count>0) { 199 | AVMetadataMachineReadableCodeObject * metaObject=metadataObjects[0]; 200 | NSString * value=metaObject.stringValue; 201 | if(value.length&&_eventSink){ 202 | _eventSink([RScanResult toMap:metaObject]); 203 | } 204 | } 205 | 206 | } 207 | 208 | - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{ 209 | if (output == _captureVideoOutput) { 210 | CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 211 | CFRetain(newBuffer); 212 | CVPixelBufferRef old = _latestPixelBuffer; 213 | while (!OSAtomicCompareAndSwapPtrBarrier(old, newBuffer, (void **)&_latestPixelBuffer)) { 214 | old = _latestPixelBuffer; 215 | } 216 | if (old != nil) { 217 | CFRelease(old); 218 | } 219 | if (_onFrameAvailable) { 220 | _onFrameAvailable(); 221 | } 222 | } 223 | } 224 | 225 | 226 | - (void)start{ 227 | [_captureSession startRunning]; 228 | } 229 | 230 | - (void)stop{ 231 | [_captureSession stopRunning]; 232 | } 233 | -(void)resume{ 234 | if(![_captureSession isRunning]){ 235 | [_captureSession startRunning]; 236 | } 237 | } 238 | 239 | -(void)pause{ 240 | if ([_captureSession isRunning]) { 241 | [_captureSession stopRunning]; 242 | } 243 | } 244 | 245 | -(BOOL)setFlashMode:(BOOL) isOpen{ 246 | [_captureDevice lockForConfiguration:nil]; 247 | BOOL isSuccess = YES; 248 | if ([_captureDevice hasFlash]) { 249 | if (isOpen) { 250 | _captureDevice.flashMode=AVCaptureFlashModeOn; 251 | _captureDevice.torchMode=AVCaptureTorchModeOn; 252 | }else{ 253 | _captureDevice.flashMode = AVCaptureFlashModeOff; 254 | _captureDevice.torchMode = AVCaptureTorchModeOff; 255 | } 256 | }else{ 257 | isSuccess=NO; 258 | } 259 | [_captureDevice unlockForConfiguration]; 260 | 261 | return isSuccess; 262 | 263 | } 264 | -(BOOL)getFlashMode{ 265 | [_captureDevice lockForConfiguration:nil]; 266 | BOOL isSuccess = _captureDevice.flashMode==AVCaptureFlashModeOn&& 267 | _captureDevice.torchMode==AVCaptureTorchModeOn; 268 | [_captureDevice unlockForConfiguration]; 269 | return isSuccess; 270 | } 271 | 272 | - (void)close { 273 | [_captureSession stopRunning]; 274 | for (AVCaptureInput *input in [_captureSession inputs]) { 275 | [_captureSession removeInput:input]; 276 | } 277 | for (AVCaptureOutput *output in [_captureSession outputs]) { 278 | [_captureSession removeOutput:output]; 279 | } 280 | } 281 | - (CVPixelBufferRef _Nullable)copyPixelBuffer { 282 | CVPixelBufferRef pixelBuffer = _latestPixelBuffer; 283 | while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, nil, (void **)&_latestPixelBuffer)) { 284 | pixelBuffer = _latestPixelBuffer; 285 | } 286 | 287 | return pixelBuffer; 288 | } 289 | 290 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 291 | _eventSink = nil; 292 | [_eventChannel setStreamHandler:nil]; 293 | return nil; 294 | } 295 | 296 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 297 | _eventSink =events; 298 | return nil; 299 | } 300 | 301 | @end 302 | 303 | @interface RScanCamera() 304 | @property(readonly, nonatomic) RScanFLTCam *camera; 305 | @end 306 | 307 | @implementation RScanCamera{ 308 | dispatch_queue_t _dispatchQueue; 309 | } 310 | 311 | - (instancetype)initWithRegistry:(NSObject *)registry 312 | messenger:(NSObject *)messenger { 313 | self = [super init]; 314 | NSAssert(self, @"super init cannot be nil"); 315 | _registry = registry; 316 | _messenger = messenger; 317 | return self; 318 | } 319 | 320 | + (void)registerWithRegistrar:(nonnull NSObject *)registrar { 321 | 322 | } 323 | - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{ 324 | if ([@"availableCameras" isEqualToString:call.method]) { 325 | [self availableCameras:call result:result]; 326 | }else if([@"initialize" isEqualToString:call.method]){ 327 | [self initialize:call result:result]; 328 | }else if([@"dispose" isEqualToString:call.method]){ 329 | [self dispose:call result:result]; 330 | }else if ([call.method isEqualToString:@"startScan"]) { 331 | [_camera resume]; 332 | result(nil); 333 | }else if([call.method isEqualToString:@"stopScan"]){ 334 | [_camera pause]; 335 | result(nil); 336 | }else if ([call.method isEqualToString:@"setFlashMode"]){ 337 | NSNumber * isOpen = [call.arguments valueForKey:@"isOpen"]; 338 | result([NSNumber numberWithBool:[_camera setFlashMode:[isOpen boolValue]]]); 339 | }else if ([call.method isEqualToString:@"getFlashMode"]){ 340 | result([NSNumber numberWithBool:[_camera getFlashMode]]); 341 | }else{ 342 | result(FlutterMethodNotImplemented); 343 | } 344 | } 345 | 346 | 347 | /** 348 | 获取可用的摄像头 349 | */ 350 | -(void)availableCameras:(FlutterMethodCall *)call result:(FlutterResult)result{ 351 | AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession 352 | discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInWideAngleCamera ] 353 | mediaType:AVMediaTypeVideo 354 | position:AVCaptureDevicePositionUnspecified]; 355 | NSArray *devices = discoverySession.devices; 356 | NSMutableArray *> *reply = 357 | [[NSMutableArray alloc] initWithCapacity:devices.count]; 358 | for (AVCaptureDevice *device in devices) { 359 | NSString *lensFacing; 360 | switch ([device position]) { 361 | case AVCaptureDevicePositionBack: 362 | lensFacing = @"back"; 363 | break; 364 | case AVCaptureDevicePositionFront: 365 | lensFacing = @"front"; 366 | break; 367 | case AVCaptureDevicePositionUnspecified: 368 | lensFacing = @"external"; 369 | break; 370 | } 371 | [reply addObject:@{ 372 | @"name" : [device uniqueID], 373 | @"lensFacing" : lensFacing 374 | }]; 375 | } 376 | result(reply); 377 | } 378 | 379 | /** 380 | 初始化相机 381 | */ 382 | -(void)initialize:(FlutterMethodCall *)call result:(FlutterResult)result { 383 | NSString *cameraName = call.arguments[@"cameraName"]; 384 | NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; 385 | NSError * error; 386 | RScanFLTCam* cam =[[RScanFLTCam alloc]initWitchCameraName:cameraName resolutionPreset:resolutionPreset dispatchQueue:_dispatchQueue error:&error]; 387 | if(error){ 388 | result(getFlutterError(error)); 389 | return; 390 | }else{ 391 | if(_camera){ 392 | [_camera close]; 393 | } 394 | int64_t textureId = [_registry registerTexture:cam]; 395 | _camera =cam; 396 | cam.onFrameAvailable = ^{ 397 | [self->_registry textureFrameAvailable:textureId]; 398 | }; 399 | 400 | FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:[NSString stringWithFormat:@"com.rhyme_lph/r_scan_camera_%lld/event",textureId] binaryMessenger:_messenger]; 401 | [eventChannel setStreamHandler:cam]; 402 | cam.eventChannel = eventChannel; 403 | result(@{ 404 | @"textureId":@(textureId), 405 | @"previewWidth":@(cam.previewSize.width), 406 | @"previewHeight":@(cam.previewSize.height) 407 | }); 408 | 409 | [cam start]; 410 | } 411 | 412 | 413 | } 414 | 415 | /** 416 | 销毁相机 417 | */ 418 | -(void)dispose:(FlutterMethodCall *)call result:(FlutterResult)result { 419 | NSDictionary *argsMap = call.arguments; 420 | NSUInteger textureId = ((NSNumber *)argsMap[@"textureId"]).unsignedIntegerValue; 421 | [_registry unregisterTexture:textureId]; 422 | [_camera close]; 423 | _dispatchQueue = nil; 424 | result(nil); 425 | } 426 | 427 | 428 | @end 429 | 430 | 431 | -------------------------------------------------------------------------------- /ios/Classes/RScanPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | @interface RScanPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/RScanPlugin.m: -------------------------------------------------------------------------------- 1 | #import "RScanPlugin.h" 2 | #import "FlutterRScanView.h" 3 | #import "RScanResult.h" 4 | #import "RScanCamera.h" 5 | #import "ZBarSDK.h" 6 | #import "ZXingObjC.h" 7 | @implementation RScanPlugin 8 | + (void)registerWithRegistrar:(NSObject*)registrar { 9 | FlutterMethodChannel* channel = [FlutterMethodChannel 10 | methodChannelWithName:@"com.rhyme_lph/r_scan" 11 | binaryMessenger:[registrar messenger]]; 12 | RScanPlugin* instance = [[RScanPlugin alloc] init]; 13 | [registrar addMethodCallDelegate:instance channel:channel]; 14 | 15 | FlutterRScanViewFactory * rScanView=[[FlutterRScanViewFactory alloc]initWithMessenger:registrar.messenger]; 16 | [registrar registerViewFactory:rScanView withId:@"com.rhyme_lph/r_scan_view"]; 17 | 18 | FlutterMethodChannel* cameraChannel = [FlutterMethodChannel 19 | methodChannelWithName:@"com.rhyme_lph/r_scan_camera/method" binaryMessenger:[registrar messenger]]; 20 | 21 | RScanCamera* camera = [[RScanCamera alloc]initWithRegistry:[registrar textures] messenger:[registrar messenger]]; 22 | [registrar addMethodCallDelegate:camera channel:cameraChannel]; 23 | 24 | } 25 | 26 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 27 | if ([@"scanImagePath" isEqualToString:call.method]) { 28 | [self scanImagePath:call result:result]; 29 | }else if ([@"scanImageUrl" isEqualToString:call.method]) { 30 | [self scanImageUrl:call result:result]; 31 | }if ([@"scanImageMemory" isEqualToString:call.method]) { 32 | [self scanImageMemory:call result:result]; 33 | } else { 34 | result(FlutterMethodNotImplemented); 35 | } 36 | } 37 | 38 | 39 | - (void)scanImagePath:(FlutterMethodCall*)call result:(FlutterResult)result{ 40 | NSString * path=[call.arguments valueForKey:@"path"]; 41 | if([path isKindOfClass:[NSNull class]]){ 42 | result(@""); 43 | return; 44 | } 45 | //加载文件 46 | NSFileHandle * fh=[NSFileHandle fileHandleForReadingAtPath:path]; 47 | NSData * data=[fh readDataToEndOfFile]; 48 | result([self getQrCode:data]); 49 | } 50 | 51 | -(NSDictionary *)zXingScan:(NSData *)data{ 52 | CGImageRef cgImage; 53 | 54 | // Fallback on earlier versions 55 | CGImageSourceRef sourceRef = CGImageSourceCreateWithData((CFDataRef)data, NULL); 56 | 57 | cgImage = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL); 58 | 59 | ZXLuminanceSource *source = [[ZXCGImageLuminanceSource alloc] initWithCGImage:cgImage]; 60 | ZXBinaryBitmap *bitmap = [ZXBinaryBitmap binaryBitmapWithBinarizer:[ZXHybridBinarizer binarizerWithSource:source]]; 61 | 62 | NSError *error = nil; 63 | 64 | ZXDecodeHints *hints = [ZXDecodeHints hints]; 65 | 66 | ZXMultiFormatReader *reader = [ZXMultiFormatReader reader]; 67 | ZXResult *result = [reader decode:bitmap hints:hints error:&error]; 68 | NSLog(@"zXing scan start"); 69 | if(result){ 70 | NSMutableDictionary *dict =[NSMutableDictionary dictionary]; 71 | NSString* contents = result.text; 72 | ZXBarcodeFormat format = result.barcodeFormat; 73 | [dict setValue:contents forKey:@"message"]; 74 | [dict setValue:[RScanResult getZXingType:format] forKey:@"type"]; 75 | NSArray* points = result.resultPoints; 76 | NSMutableArray * pointMap = [NSMutableArray array]; 77 | for(ZXResultPoint* poin in points){ 78 | [pointMap addObject:[self pointsToMap2:poin.x y:poin.y]]; 79 | } 80 | [dict setValue:pointMap forKey:@"points"]; 81 | return dict; 82 | } 83 | return nil; 84 | } 85 | 86 | 87 | -(NSDictionary *)zbarScan:(NSData *)data{ 88 | CGImageRef cgImage; 89 | 90 | // Fallback on earlier versions 91 | CGImageSourceRef sourceRef = CGImageSourceCreateWithData((CFDataRef)data, NULL); 92 | 93 | cgImage = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL); 94 | 95 | ZBarImage * zbarImage =[[ZBarImage alloc]initWithCGImage:cgImage]; 96 | ZBarImageScanner* scanner = [[ZBarImageScanner alloc]init]; 97 | 98 | [scanner setSymbology:ZBAR_NONE config:ZBAR_CFG_ENABLE to:1]; 99 | NSInteger resultInt = [scanner scanImage:zbarImage]; 100 | 101 | NSLog(@"zbar scan count:%ld",(long)resultInt); 102 | 103 | if (resultInt == 0) { 104 | return nil; 105 | }else{ 106 | ZBarSymbolSet* symbols = scanner.results; 107 | ZBarSymbol *symbol = nil; 108 | 109 | for(symbol in symbols){ 110 | break; 111 | } 112 | 113 | NSString* resultStr = symbol.data; 114 | NSLog(@"zbar scan result data:%@ , type:%@",resultStr,symbol.typeName); 115 | if (resultStr!=nil) { 116 | NSMutableDictionary *dict =[NSMutableDictionary dictionary]; 117 | [dict setValue:resultStr forKey:@"message"]; 118 | [dict setValue:[RScanResult getZBarType:symbol.type] forKey:@"type"]; 119 | NSMutableArray * points = [NSMutableArray array]; 120 | CGPoint topLeft=symbol.bounds.origin; 121 | CGPoint topRight=symbol.bounds.origin; 122 | topRight.x +=symbol.bounds.size.width; 123 | CGPoint bottomLeft=symbol.bounds.origin; 124 | bottomLeft.y +=symbol.bounds.size.height; 125 | 126 | CGPoint bottomRight=symbol.bounds.origin; 127 | bottomRight.x+=symbol.bounds.size.width; 128 | bottomRight.y+=symbol.bounds.size.height; 129 | 130 | [points addObject:[self pointsToMap:topLeft]]; 131 | [points addObject:[self pointsToMap:topRight]]; 132 | [points addObject:[self pointsToMap:bottomLeft]]; 133 | [points addObject:[self pointsToMap:bottomRight]]; 134 | [dict setValue:points forKey:@"points"]; 135 | return dict; 136 | } 137 | } 138 | return nil; 139 | } 140 | 141 | -(NSDictionary *)nativeScan:(NSData *)data{ 142 | CIImage * detectImage=[CIImage imageWithData:data]; 143 | CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}]; 144 | 145 | NSArray* feature = [detector featuresInImage:detectImage options: nil]; 146 | NSLog(@"native scan count:%lu",(unsigned long)feature.count); 147 | if(feature.count==0){ 148 | return nil; 149 | }else{ 150 | for(int index=0;index<[feature count];index ++){ 151 | CIQRCodeFeature * qrCode=[feature objectAtIndex:index]; 152 | NSString *resultStr=qrCode.messageString; 153 | if(resultStr!=nil){ 154 | NSMutableDictionary *dict =[NSMutableDictionary dictionary]; 155 | [dict setValue:resultStr forKey:@"message"]; 156 | [dict setValue:[RScanResult getType:AVMetadataObjectTypeQRCode] forKey:@"type"]; 157 | NSMutableArray * points = [NSMutableArray array]; 158 | CGPoint topLeft=qrCode.topLeft; 159 | CGPoint topRight=qrCode.topRight; 160 | CGPoint bottomLeft=qrCode.bottomLeft; 161 | CGPoint bottomRight=qrCode.bottomRight; 162 | [points addObject:[self pointsToMap:topLeft]]; 163 | [points addObject:[self pointsToMap:topRight]]; 164 | [points addObject:[self pointsToMap:bottomLeft]]; 165 | [points addObject:[self pointsToMap:bottomRight]]; 166 | [dict setValue:points forKey:@"points"]; 167 | return dict; 168 | } 169 | 170 | } 171 | } 172 | return nil; 173 | } 174 | 175 | -(NSDictionary *) getQrCode:(NSData *)data{ 176 | if (data) { 177 | NSDictionary * result = [self zbarScan:data]; 178 | if(result==nil){ 179 | result =[self zXingScan:data]; 180 | } 181 | if(result==nil){ 182 | result = [self nativeScan:data]; 183 | } 184 | return result; 185 | } 186 | return nil; 187 | } 188 | 189 | -(NSDictionary*) pointsToMap:(CGPoint) point{ 190 | NSMutableDictionary * dict = [NSMutableDictionary dictionary]; 191 | [dict setValue:@(point.x) forKey:@"X"]; 192 | [dict setValue:@(point.y) forKey:@"Y"]; 193 | return dict; 194 | } 195 | 196 | -(NSDictionary*) pointsToMap2:(float)x y:(float)y{ 197 | NSMutableDictionary * dict = [NSMutableDictionary dictionary]; 198 | [dict setValue:@(x) forKey:@"X"]; 199 | [dict setValue:@(y) forKey:@"Y"]; 200 | return dict; 201 | } 202 | 203 | - (void)scanImageUrl:(FlutterMethodCall*)call result:(FlutterResult)result{ 204 | NSString * url = [call.arguments valueForKey:@"url"]; 205 | NSURL* nsUrl=[NSURL URLWithString:url]; 206 | NSData * data=[NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:nsUrl] returningResponse:nil error:nil]; 207 | result([self getQrCode:data]); 208 | } 209 | 210 | - (void)scanImageMemory:(FlutterMethodCall*)call result:(FlutterResult)result{ 211 | FlutterStandardTypedData * uint8list=[call.arguments valueForKey:@"uint8list"]; 212 | result([self getQrCode:uint8list.data]); 213 | } 214 | @end 215 | 216 | -------------------------------------------------------------------------------- /ios/Classes/RScanResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // RScanResult.h 3 | // r_scan 4 | // 5 | // Created by 李鹏辉 on 2019/12/28. 6 | // 7 | 8 | #import 9 | #import 10 | #import "ZXingObjC/ZXingObjC.h" 11 | #import "ATBarSDK/ZBarSDK.h" 12 | @interface RScanResult : NSObject 13 | 14 | +(NSDictionary*) toMap:(AVMetadataMachineReadableCodeObject*) obj; 15 | 16 | +(NSNumber*) getType:(AVMetadataObjectType)type; 17 | 18 | +(NSNumber*) getZXingType:(ZXBarcodeFormat)format; 19 | 20 | +(NSNumber*) getZBarType:(zbar_symbol_type_t)format; 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /ios/Classes/RScanResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // RScanResult.m 3 | // r_scan 4 | // 5 | // Created by 李鹏辉 on 2019/12/28. 6 | // 7 | 8 | #import "RScanResult.h" 9 | @implementation RScanResult 10 | 11 | +(NSDictionary*) toMap:(AVMetadataMachineReadableCodeObject*) obj{ 12 | if (obj == nil) { 13 | return nil; 14 | } 15 | NSMutableDictionary * result =[NSMutableDictionary dictionary]; 16 | [result setValue:obj.stringValue forKey:@"message"]; 17 | [result setValue:[self getType:obj.type] forKey:@"type"]; 18 | [result setValue:obj.corners forKey:@"points"]; 19 | return result; 20 | } 21 | 22 | +(NSNumber*) getType:(AVMetadataObjectType)type{ 23 | if (type == AVMetadataObjectTypeAztecCode) { 24 | return @(0); 25 | }else if (type == AVMetadataObjectTypeCode39Code) { 26 | return @(2); 27 | }else if (type == AVMetadataObjectTypeCode93Code) { 28 | return @(3); 29 | }else if (type == AVMetadataObjectTypeCode128Code) { 30 | return @(4); 31 | }else if (type == AVMetadataObjectTypeDataMatrixCode) { 32 | return @(5); 33 | }else if (type == AVMetadataObjectTypeEAN8Code) { 34 | return @(6); 35 | }else if (type == AVMetadataObjectTypeEAN13Code) { 36 | return @(7); 37 | }else if (type == AVMetadataObjectTypeITF14Code) { 38 | return @(8); 39 | }else if (type == AVMetadataObjectTypePDF417Code) { 40 | return @(10); 41 | }else if (type == AVMetadataObjectTypeQRCode) { 42 | return @(11); 43 | }else if (type == AVMetadataObjectTypeUPCECode) { 44 | return @(15); 45 | }else{ 46 | return nil; 47 | } 48 | } 49 | 50 | +(NSNumber*) getZXingType:(ZXBarcodeFormat)format{ 51 | switch (format) { 52 | case kBarcodeFormatAztec: 53 | return @(0); 54 | case kBarcodeFormatCodabar: 55 | return @(1); 56 | case kBarcodeFormatCode39: 57 | return @(2); 58 | case kBarcodeFormatCode93: 59 | return @(3); 60 | case kBarcodeFormatCode128: 61 | return @(4); 62 | case kBarcodeFormatDataMatrix: 63 | return @(5); 64 | case kBarcodeFormatEan8: 65 | return @(6); 66 | case kBarcodeFormatEan13: 67 | return @(7); 68 | case kBarcodeFormatITF: 69 | return @(8); 70 | case kBarcodeFormatMaxiCode: 71 | return @(9); 72 | case kBarcodeFormatPDF417: 73 | return @(10); 74 | case kBarcodeFormatQRCode: 75 | return @(11); 76 | case kBarcodeFormatRSS14: 77 | return @(12); 78 | case kBarcodeFormatRSSExpanded: 79 | return @(13); 80 | case kBarcodeFormatUPCA: 81 | return @(14); 82 | case kBarcodeFormatUPCE: 83 | return @(15); 84 | case kBarcodeFormatUPCEANExtension: 85 | return @(16); 86 | } 87 | return nil; 88 | } 89 | 90 | + (NSNumber *)getZBarType:(zbar_symbol_type_t)format{ 91 | switch (format) { 92 | // case kBarcodeFormatAztec: 93 | // return @(0); 94 | case ZBAR_CODABAR: 95 | return @(1); 96 | case ZBAR_CODE39: 97 | return @(2); 98 | case ZBAR_CODE93: 99 | return @(3); 100 | case ZBAR_CODE128: 101 | return @(4); 102 | case ZBAR_DATABAR_EXP: 103 | return @(5); 104 | case ZBAR_EAN8: 105 | return @(6); 106 | case ZBAR_EAN13: 107 | return @(7); 108 | case ZBAR_COMPOSITE: 109 | return @(8); 110 | // case kBarcodeFormatMaxiCode: 111 | // return @(9); 112 | case ZBAR_PDF417: 113 | return @(10); 114 | case ZBAR_QRCODE: 115 | return @(11); 116 | // case kBarcodeFormatRSS14: 117 | // return @(12); 118 | // case kBarcodeFormatRSSExpanded: 119 | // return @(13); 120 | case ZBAR_UPCA: 121 | return @(14); 122 | case ZBAR_UPCE: 123 | return @(15); 124 | // case kBarcodeFormatUPCEANExtension: 125 | // return @(16); 126 | default: 127 | break; 128 | } 129 | return nil; 130 | } 131 | @end 132 | -------------------------------------------------------------------------------- /ios/Classes/RScanView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RScanView.h 3 | // r_scan 4 | // 5 | // Created by rhymelph on 2019/11/23. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @interface RScanView : UIView 12 | 13 | -(instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args binaryMessenger:(NSObject *)messenger; 14 | 15 | 16 | @end 17 | 18 | @interface FlutterRScanViewEventChannel : NSObject 19 | 20 | @property(nonatomic , strong)FlutterEventSink events; 21 | @property(nonatomic , strong)RScanView* rsView; 22 | 23 | -(void)getResult:(NSDictionary *)msg; 24 | 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ios/Classes/RScanView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RScanView.m 3 | // r_scan 4 | // 5 | // Created by 李鹏辉 on 2019/11/23. 6 | // 7 | 8 | #import "RScanView.h" 9 | #import 10 | #import "RScanResult.h" 11 | @interface RScanView() 12 | 13 | @property(nonatomic , strong)AVCaptureSession * session; 14 | @property(nonatomic , strong)FlutterMethodChannel * _channel; 15 | @property(nonatomic , strong)FlutterRScanViewEventChannel * _event; 16 | @property(nonatomic , strong)AVCaptureVideoPreviewLayer * captureLayer; 17 | @property(nonatomic , strong)AVCaptureDevice * _device; 18 | 19 | - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; 20 | 21 | @end 22 | 23 | @implementation RScanView 24 | 25 | - (AVCaptureSession *)session{ 26 | if(!_session){ 27 | _session=[[AVCaptureSession alloc]init]; 28 | } 29 | return _session; 30 | } 31 | 32 | - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args binaryMessenger:(NSObject *)messenger{ 33 | if(self = [super initWithFrame:frame]){ 34 | 35 | NSString * channelName=[NSString stringWithFormat:@"com.rhyme_lph/r_scan_view_%lld/method",viewId]; 36 | self._channel=[FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger]; 37 | __weak __typeof__(self) weakSelf = self; 38 | [weakSelf._channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { 39 | [weakSelf onMethodCall:call result:result]; 40 | }]; 41 | 42 | NSString * eventChannelName=[NSString stringWithFormat:@"com.rhyme_lph/r_scan_view_%lld/event",viewId]; 43 | FlutterEventChannel * _evenChannel = [FlutterEventChannel eventChannelWithName:eventChannelName binaryMessenger:messenger]; 44 | self._event=[FlutterRScanViewEventChannel new]; 45 | // [self._event setRsView:self]; 46 | [_evenChannel setStreamHandler:self._event]; 47 | 48 | AVCaptureVideoPreviewLayer * layer=[AVCaptureVideoPreviewLayer layerWithSession:self.session]; 49 | self.captureLayer=layer; 50 | 51 | layer.backgroundColor=[UIColor blackColor].CGColor; 52 | [self.layer addSublayer:layer]; 53 | layer.videoGravity=AVLayerVideoGravityResizeAspectFill; 54 | 55 | self._device=[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 56 | AVCaptureDeviceInput * input=[[AVCaptureDeviceInput alloc] initWithDevice:self._device error:nil]; 57 | AVCaptureMetadataOutput * output=[[AVCaptureMetadataOutput alloc]init]; 58 | [self.session addInput:input]; 59 | [self.session addOutput:output]; 60 | self.session.sessionPreset=AVCaptureSessionPresetHigh; 61 | 62 | output.metadataObjectTypes=output.availableMetadataObjectTypes; 63 | [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; 64 | [output setMetadataObjectTypes:@[AVMetadataObjectTypeAztecCode,AVMetadataObjectTypeCode39Code, 65 | AVMetadataObjectTypeCode93Code,AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeDataMatrixCode,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeITF14Code,AVMetadataObjectTypePDF417Code,AVMetadataObjectTypeQRCode,AVMetadataObjectTypeUPCECode]]; 66 | 67 | [self.session startRunning]; 68 | } 69 | return self; 70 | } 71 | 72 | - (void)layoutSubviews{ 73 | [super layoutSubviews]; 74 | self.captureLayer.frame=self.bounds; 75 | } 76 | 77 | -(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 78 | if ([call.method isEqualToString:@"startScan"]) { 79 | [self resume]; 80 | result(nil); 81 | }else if([call.method isEqualToString:@"stopScan"]){ 82 | [self pause]; 83 | result(nil); 84 | }else if ([call.method isEqualToString:@"setFlashMode"]){ 85 | NSNumber * isOpen = [call.arguments valueForKey:@"isOpen"]; 86 | result([NSNumber numberWithBool:[self setFlashMode:[isOpen boolValue]]]); 87 | }else if ([call.method isEqualToString:@"getFlashMode"]){ 88 | result([NSNumber numberWithBool:[self getFlashMode]]); 89 | }else { 90 | result(FlutterMethodNotImplemented); 91 | } 92 | } 93 | 94 | -(void)resume{ 95 | if(![self.session isRunning]){ 96 | [self.session startRunning]; 97 | } 98 | } 99 | 100 | -(void)pause{ 101 | if ([self.session isRunning]) { 102 | [self.session stopRunning]; 103 | } 104 | } 105 | 106 | -(BOOL)setFlashMode:(BOOL) isOpen{ 107 | [self._device lockForConfiguration:nil]; 108 | BOOL isSuccess = YES; 109 | if ([self._device hasFlash]) { 110 | if (isOpen) { 111 | self._device.flashMode=AVCaptureFlashModeOn; 112 | self._device.torchMode=AVCaptureTorchModeOn; 113 | }else{ 114 | self._device.flashMode = AVCaptureFlashModeOff; 115 | self._device.torchMode = AVCaptureTorchModeOff; 116 | } 117 | }else{ 118 | isSuccess=NO; 119 | } 120 | [self._device unlockForConfiguration]; 121 | 122 | return isSuccess; 123 | 124 | } 125 | -(BOOL)getFlashMode{ 126 | [self._device lockForConfiguration:nil]; 127 | BOOL isSuccess = self._device.flashMode==AVCaptureFlashModeOn&& 128 | self._device.torchMode==AVCaptureTorchModeOn; 129 | [self._device unlockForConfiguration]; 130 | return isSuccess; 131 | 132 | } 133 | - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{ 134 | if (metadataObjects.count>0) { 135 | AVMetadataMachineReadableCodeObject * metaObject=metadataObjects[0]; 136 | NSString * value=metaObject.stringValue; 137 | if(value.length&&self._event){ 138 | [self._event getResult:[RScanResult toMap:metaObject]]; 139 | } 140 | } 141 | } 142 | @end 143 | 144 | 145 | 146 | @implementation FlutterRScanViewEventChannel 147 | 148 | - (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events{ 149 | self.events = events; 150 | if(self.rsView){ 151 | NSNumber * isPlay=[arguments valueForKey:@"isPlay"]; 152 | if(isPlay){ 153 | if (isPlay.boolValue) { 154 | [self.rsView resume]; 155 | }else{ 156 | [self.rsView pause]; 157 | } 158 | } 159 | } 160 | return nil; 161 | } 162 | 163 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 164 | if(self.rsView){ 165 | [self.rsView pause]; 166 | } 167 | return nil; 168 | } 169 | 170 | - (void)getResult:(NSDictionary *)msg{ 171 | if(self.events){ 172 | self.events(msg); 173 | } 174 | } 175 | 176 | 177 | @end 178 | -------------------------------------------------------------------------------- /ios/r_scan.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'r_scan' 6 | s.version = '0.0.1' 7 | s.summary = 'A new Flutter project.' 8 | s.description = <<-DESC 9 | A new Flutter project. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | s.dependency 'ATBarSDK','~>1.4.1' 19 | s.dependency 'ZXingObjC','~>3.6.5' 20 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'armv7 arm64 x86_64' } 21 | s.ios.deployment_target = '8.0' 22 | 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lib/r_scan.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:typed_data'; 7 | import 'package:flutter/services.dart'; 8 | export 'package:r_scan/src/r_scan_view.dart'; 9 | export 'package:r_scan/src/r_scan_camera.dart'; 10 | 11 | /// qr scan 12 | class RScan { 13 | static const MethodChannel _channel = 14 | const MethodChannel('com.rhyme_lph/r_scan'); 15 | 16 | /// scan qr image in path 17 | /// 18 | /// [path] your qr image path 19 | /// 20 | /// It will return your scan result.if not found qr,will return empty. 21 | static Future scanImagePath(String path) async => 22 | RScanResult.formMap(await _channel.invokeMethod('scanImagePath', { 23 | "path": path, 24 | })); 25 | 26 | /// scan qr image in url 27 | /// 28 | /// [url] your qr image url 29 | /// 30 | /// It will return your scan result.if not found qr,will return empty. 31 | static Future scanImageUrl(String url) async => 32 | RScanResult.formMap(await _channel.invokeMethod('scanImageUrl', { 33 | "url": url, 34 | })); 35 | 36 | /// scan qr image in memory 37 | /// 38 | /// [uint8list] your qr image memory 39 | /// 40 | /// It will return your scan result.if not found qr,will return empty. 41 | static Future scanImageMemory(Uint8List uint8list) async => 42 | RScanResult.formMap(await _channel.invokeMethod('scanImageMemory', { 43 | "uint8list": uint8list, 44 | })); 45 | } 46 | 47 | /// barcode type 48 | enum RScanBarType { 49 | azetc, 50 | codabar, // ios not found 51 | code_39, 52 | code_93, 53 | code_128, 54 | data_matrix, 55 | ean_8, 56 | ean_13, // ios include upc_a 57 | itf, 58 | maxicode, // ios not found 59 | pdf_417, 60 | qr_code, 61 | rss_14, // ios not found 62 | rss_expanded, // ios not found 63 | upc_a, // ios not found 64 | upc_e, 65 | upc_ean_extension, // ios not found 66 | } 67 | 68 | /// barcode point 69 | class RScanPoint { 70 | /// barcode point x 71 | final double? x; 72 | 73 | /// barcode point y 74 | final double? y; 75 | 76 | RScanPoint(this.x, this.y); 77 | 78 | @override 79 | String toString() { 80 | return 'RScanPoint{x: $x, y: $y}'; 81 | } 82 | 83 | @override 84 | bool operator ==(Object other) => 85 | identical(this, other) || 86 | other is RScanPoint && 87 | runtimeType == other.runtimeType && 88 | x == other.x && 89 | y == other.y; 90 | 91 | @override 92 | int get hashCode => x.hashCode ^ y.hashCode; 93 | } 94 | 95 | /// scan result 96 | class RScanResult { 97 | /// barcode type 98 | final RScanBarType? type; 99 | 100 | ///barcode message 101 | final String? message; 102 | 103 | ///barcode points 104 | final List? points; 105 | 106 | const RScanResult({this.type, this.message, this.points}); 107 | 108 | factory RScanResult.formMap(Map? map) { 109 | return map == null 110 | ? RScanResult() 111 | : RScanResult( 112 | type: map['type'] != null 113 | ? RScanBarType.values[map['type'] as int] 114 | : null, 115 | message: map['message'] as String?, 116 | points: map['points'] != null 117 | ? (map['points'] as List) 118 | .map( 119 | (data) => RScanPoint( 120 | data['X'], 121 | data['Y'], 122 | ), 123 | ) 124 | .toList() 125 | : null, 126 | ); 127 | } 128 | 129 | @override 130 | String toString() { 131 | return 'RScanResult{type: $type, message: $message, points: $points}'; 132 | } 133 | 134 | @override 135 | bool operator ==(Object other) => 136 | identical(this, other) || 137 | other is RScanResult && 138 | runtimeType == other.runtimeType && 139 | type == other.type && 140 | message == other.message && 141 | points == other.points; 142 | 143 | @override 144 | int get hashCode => type.hashCode ^ message.hashCode ^ points.hashCode; 145 | } 146 | -------------------------------------------------------------------------------- /lib/src/r_scan_camera.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | import 'dart:async'; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:r_scan/r_scan.dart'; 9 | 10 | const _scanType = 'com.rhyme_lph/r_scan_camera'; 11 | final MethodChannel _channel = const MethodChannel('$_scanType/method'); 12 | 13 | Future> availableRScanCameras() async { 14 | try { 15 | final List> cameras = await (_channel 16 | .invokeListMethod>('availableCameras') 17 | as Future>>); 18 | return cameras.map((Map camera) { 19 | return RScanCameraDescription( 20 | name: camera['name'], 21 | lensDirection: _parseCameraLensDirection(camera['lensFacing']), 22 | ); 23 | }).toList(); 24 | } on PlatformException catch (e) { 25 | throw RScanCameraException(e.code, e.message); 26 | } 27 | } 28 | 29 | class RScanCameraController extends ValueNotifier { 30 | final RScanCameraDescription description; 31 | final RScanCameraResolutionPreset resolutionPreset; 32 | RScanResult? result; // qr code result 33 | int? _textureId; // init finish will return id 34 | bool _isDisposed = false; // when the widget dispose will set true 35 | Completer? _creatingCompleter; // when the camera create finish 36 | StreamSubscription? _resultSubscription; //the result subscription 37 | 38 | RScanCameraController(this.description, this.resolutionPreset) 39 | : super(const RScanCameraValue.uninitialized()); 40 | 41 | Future initialize() async { 42 | if (_isDisposed) return Future.value(); 43 | 44 | _creatingCompleter = Completer(); 45 | 46 | try { 47 | final Map reply = 48 | await (_channel.invokeMapMethod('initialize', { 49 | 'cameraName': description.name, 50 | 'resolutionPreset': _serializeResolutionPreset(resolutionPreset), 51 | }) as FutureOr>); 52 | _textureId = reply['textureId']; 53 | value = value.copyWith( 54 | isInitialized: true, 55 | previewSize: Size(reply['previewWidth'].toDouble(), 56 | reply['previewHeight'].toDouble())); 57 | _resultSubscription = EventChannel('${_scanType}_$_textureId/event') 58 | .receiveBroadcastStream() 59 | .listen(_handleResult); 60 | } on PlatformException catch (e) { 61 | //当发生权限问题的异常时会抛出 62 | throw RScanCameraException(e.code, e.message); 63 | } 64 | _creatingCompleter!.complete(); 65 | return _creatingCompleter!.future; 66 | } 67 | 68 | //处理返回值 69 | void _handleResult(event) { 70 | if (_isDisposed) return; 71 | this.result = RScanResult.formMap(event); 72 | notifyListeners(); 73 | } 74 | 75 | //开始扫描 76 | Future startScan() async { 77 | await _channel.invokeMethod('startScan'); 78 | } 79 | 80 | //停止扫描 81 | Future stopScan() async { 82 | await _channel.invokeMethod('stopScan'); 83 | } 84 | 85 | /// flash mode open or close. 86 | /// 87 | /// [isOpen] if false will close flash mode. 88 | /// 89 | /// It will return is success. 90 | Future setFlashMode(bool isOpen) async => 91 | await _channel.invokeMethod('setFlashMode', { 92 | 'isOpen': isOpen, 93 | }); 94 | 95 | /// flash mode open or close. 96 | /// 97 | /// [isOpen] if false will close flash mode. 98 | /// 99 | /// It will return is success. 100 | Future getFlashMode() async => 101 | await _channel.invokeMethod('getFlashMode'); 102 | 103 | /// flash auto open when brightness value less then 600. 104 | /// 105 | /// [isAuto] auto 106 | Future setAutoFlashMode(bool isAuto) async => 107 | await _channel.invokeMethod('setAutoFlashMode', { 108 | 'isAuto': isAuto, 109 | }); 110 | 111 | @override 112 | Future dispose() async { 113 | if (_isDisposed) { 114 | return; 115 | } 116 | _isDisposed = true; 117 | super.dispose(); 118 | if (_creatingCompleter != null) { 119 | await _creatingCompleter!.future; 120 | await _channel.invokeMethod('dispose', { 121 | 'textureId': _textureId, 122 | }); 123 | await _resultSubscription?.cancel(); 124 | } 125 | } 126 | } 127 | 128 | /// camera value info 129 | class RScanCameraValue { 130 | final bool? isInitialized; 131 | final String? errorDescription; 132 | final Size? previewSize; 133 | 134 | const RScanCameraValue( 135 | {this.isInitialized, this.errorDescription, this.previewSize}); 136 | 137 | const RScanCameraValue.uninitialized() 138 | : this( 139 | isInitialized: false, 140 | ); 141 | 142 | double get aspectRatio => previewSize!.height / previewSize!.width; 143 | 144 | bool get hasError => errorDescription != null; 145 | 146 | RScanCameraValue copyWith({ 147 | bool? isInitialized, 148 | String? errorDescription, 149 | Size? previewSize, 150 | }) { 151 | return RScanCameraValue( 152 | isInitialized: isInitialized ?? this.isInitialized, 153 | errorDescription: errorDescription ?? this.errorDescription, 154 | previewSize: previewSize ?? this.previewSize, 155 | ); 156 | } 157 | 158 | @override 159 | String toString() { 160 | return '$runtimeType(' 161 | 'isInitialized: $isInitialized, ' 162 | 'errorDescription: $errorDescription, ' 163 | 'previewSize: $previewSize)'; 164 | } 165 | } 166 | 167 | class RScanCamera extends StatelessWidget { 168 | final RScanCameraController controller; 169 | 170 | const RScanCamera(this.controller, {Key? key}) : super(key: key); 171 | 172 | @override 173 | Widget build(BuildContext context) { 174 | return controller.value.isInitialized! 175 | ? Texture(textureId: controller._textureId!) 176 | : Container(); 177 | } 178 | } 179 | 180 | /// camera description 181 | class RScanCameraDescription { 182 | RScanCameraDescription({ 183 | this.name, 184 | this.lensDirection, 185 | }); 186 | 187 | final String? name; 188 | final RScanCameraLensDirection? lensDirection; 189 | 190 | @override 191 | bool operator ==(Object o) { 192 | return o is RScanCameraDescription && 193 | o.name == name && 194 | o.lensDirection == lensDirection; 195 | } 196 | 197 | @override 198 | int get hashCode { 199 | return hashValues(name, lensDirection); 200 | } 201 | 202 | @override 203 | String toString() { 204 | return '$runtimeType($name, $lensDirection)'; 205 | } 206 | } 207 | 208 | /// camera lens direction 209 | enum RScanCameraLensDirection { front, back, external } 210 | 211 | RScanCameraLensDirection _parseCameraLensDirection(String? string) { 212 | switch (string) { 213 | case 'front': 214 | return RScanCameraLensDirection.front; 215 | case 'back': 216 | return RScanCameraLensDirection.back; 217 | case 'external': 218 | return RScanCameraLensDirection.external; 219 | } 220 | throw ArgumentError('Unknown CameraLensDirection value'); 221 | } 222 | 223 | /// Affect the quality of video recording and image capture: 224 | /// 225 | /// If a preset is not available on the camera being used a preset of lower quality will be selected automatically. 226 | enum RScanCameraResolutionPreset { 227 | /// 352x288 on iOS, 240p (320x240) on Android 228 | low, 229 | 230 | /// 480p (640x480 on iOS, 720x480 on Android) 231 | medium, 232 | 233 | /// 720p (1280x720) 234 | high, 235 | 236 | /// 1080p (1920x1080) 237 | veryHigh, 238 | 239 | /// 2160p (3840x2160) 240 | ultraHigh, 241 | 242 | /// The highest resolution available. 243 | max, 244 | } 245 | 246 | /// Returns the resolution preset as a String. 247 | String _serializeResolutionPreset( 248 | RScanCameraResolutionPreset resolutionPreset) { 249 | switch (resolutionPreset) { 250 | case RScanCameraResolutionPreset.max: 251 | return 'max'; 252 | case RScanCameraResolutionPreset.ultraHigh: 253 | return 'ultraHigh'; 254 | case RScanCameraResolutionPreset.veryHigh: 255 | return 'veryHigh'; 256 | case RScanCameraResolutionPreset.high: 257 | return 'high'; 258 | case RScanCameraResolutionPreset.medium: 259 | return 'medium'; 260 | case RScanCameraResolutionPreset.low: 261 | return 'low'; 262 | } 263 | throw ArgumentError('Unknown ResolutionPreset value'); 264 | } 265 | 266 | /// exception 267 | class RScanCameraException implements Exception { 268 | RScanCameraException(this.code, this.description); 269 | 270 | String code; 271 | String? description; 272 | 273 | @override 274 | String toString() => '$runtimeType($code, $description)'; 275 | } 276 | -------------------------------------------------------------------------------- /lib/src/r_scan_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | import 'dart:async'; 5 | import 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | 10 | import '../r_scan.dart'; 11 | 12 | const _scanType = 'com.rhyme_lph/r_scan_view'; 13 | 14 | typedef void ScanResultCallback(String result); 15 | 16 | /// qr scan view , it need to require camera permission. 17 | @Deprecated("please use 'RScanCamera'") 18 | class RScanView extends StatefulWidget { 19 | final RScanController controller; 20 | 21 | const RScanView({required this.controller}) : assert(controller != null); 22 | 23 | @override 24 | State createState() => _RScanViewState(); 25 | } 26 | 27 | class _RScanViewState extends State { 28 | late RScanController _controller; 29 | 30 | void onPlatformViewCreated(int id) { 31 | _controller.attach(id); 32 | } 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _controller = widget.controller; 38 | SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | super.dispose(); 44 | _controller.detach(); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | dynamic params = { 50 | "isPlay": _controller.isPlay, 51 | }; 52 | Widget child; 53 | if (Platform.isAndroid) { 54 | child = AndroidView( 55 | viewType: _scanType, 56 | onPlatformViewCreated: onPlatformViewCreated, 57 | creationParams: params, 58 | creationParamsCodec: StandardMessageCodec(), 59 | ); 60 | } else if (Platform.isIOS) { 61 | child = UiKitView( 62 | viewType: _scanType, 63 | onPlatformViewCreated: onPlatformViewCreated, 64 | creationParams: params, 65 | creationParamsCodec: StandardMessageCodec(), 66 | ); 67 | } else { 68 | child = Container( 69 | child: Text('Not support ${Platform.operatingSystem} platform.'), 70 | ); 71 | } 72 | 73 | return child; 74 | } 75 | } 76 | 77 | /// qr scan view controller . 78 | /// can startScan or stopScan . 79 | @Deprecated("please use 'RScanCameraController'") 80 | class RScanController extends ChangeNotifier { 81 | late Stream _stream; 82 | StreamSubscription? _subscription; 83 | RScanResult? result; 84 | late EventChannel _channel; 85 | bool isPlay; 86 | late MethodChannel _methodChannel; 87 | 88 | RScanController({this.isPlay: true}) 89 | : assert(isPlay != null), 90 | super(); 91 | 92 | void attach(int id) { 93 | _channel = EventChannel('${_scanType}_$id/event'); 94 | _methodChannel = MethodChannel('${_scanType}_$id/method'); 95 | _stream = _channel.receiveBroadcastStream( 96 | { 97 | "isPlay": isPlay, 98 | }, 99 | ); 100 | _subscription = _stream.listen((data) { 101 | this.result = RScanResult.formMap(data); 102 | notifyListeners(); 103 | }); 104 | } 105 | 106 | //开始扫描 107 | Future startScan() async { 108 | await _methodChannel.invokeMethod('startScan'); 109 | } 110 | 111 | //停止扫描 112 | Future stopScan() async { 113 | await _methodChannel.invokeMethod('stopScan'); 114 | } 115 | 116 | /// flash mode open or close. 117 | /// 118 | /// [isOpen] if false will close flash mode. 119 | /// 120 | /// It will return is success. 121 | Future setFlashMode(bool isOpen) async => 122 | await _methodChannel.invokeMethod('setFlashMode', { 123 | 'isOpen': isOpen, 124 | }); 125 | 126 | /// flash mode open or close. 127 | /// 128 | /// [isOpen] if false will close flash mode. 129 | /// 130 | /// It will return is success. 131 | Future getFlashMode() async => 132 | await _methodChannel.invokeMethod('getFlashMode'); 133 | 134 | void detach() { 135 | _subscription?.cancel(); 136 | notifyListeners(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.6.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.12.10" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "1.3.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.8.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.flutter-io.cn" 94 | source: hosted 95 | version: "1.8.1" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.1.0" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.2.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "0.3.0" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.3.0" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.0" 145 | sdks: 146 | dart: ">=2.12.0 <3.0.0" 147 | flutter: ">=1.12.0" 148 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: r_scan 2 | description: A flutter plugin about qr code scan,it can scan from file, url, memory and camera qr code. 3 | version: 0.1.6+1 4 | homepage: https://github.com/rhymelph/r_scan 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=1.12.0" 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | # For information on the generic Dart part of this file, see the 17 | # following page: https://dart.dev/tools/pub/pubspec 18 | 19 | # The following section is specific to Flutter. 20 | flutter: 21 | # This section identifies this Flutter project as a plugin project. 22 | # The androidPackage and pluginClass identifiers should not ordinarily 23 | # be modified. They are used by the tooling to maintain consistency when 24 | # adding or updating assets for this project. 25 | plugin: 26 | platforms: 27 | android: 28 | package: com.rhyme.r_scan 29 | pluginClass: RScanPlugin 30 | ios: 31 | pluginClass: RScanPlugin 32 | # To add assets to your plugin package, add an assets section, like this: 33 | # assets: 34 | # - images/a_dot_burr.jpeg 35 | # - images/a_dot_ham.jpeg 36 | # 37 | # For details regarding assets in packages, see 38 | # https://flutter.dev/assets-and-images/#from-packages 39 | # 40 | # An image asset can refer to one or more resolution-specific "variants", see 41 | # https://flutter.dev/assets-and-images/#resolution-aware. 42 | 43 | # To add custom fonts to your plugin package, add a fonts section here, 44 | # in this "flutter" section. Each entry in this list should have a 45 | # "family" key with the font family name, and a "fonts" key with a 46 | # list giving the asset and other descriptors for the font. For 47 | # example: 48 | # fonts: 49 | # - family: Schyler 50 | # fonts: 51 | # - asset: fonts/Schyler-Regular.ttf 52 | # - asset: fonts/Schyler-Italic.ttf 53 | # style: italic 54 | # - family: Trajan Pro 55 | # fonts: 56 | # - asset: fonts/TrajanPro.ttf 57 | # - asset: fonts/TrajanPro_Bold.ttf 58 | # weight: 700 59 | # 60 | # For details regarding fonts in packages, see 61 | # https://flutter.dev/custom-fonts/#from-packages 62 | -------------------------------------------------------------------------------- /r_scan.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /screen/r_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_scan/a122b35bc4fdfef6c355860247d6d1b98a285349/screen/r_scan.png -------------------------------------------------------------------------------- /test/r_scan_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:r_scan/r_scan.dart'; 4 | 5 | void main() { 6 | const MethodChannel channel = MethodChannel('r_scan'); 7 | 8 | setUp(() { 9 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 10 | return '42'; 11 | }); 12 | }); 13 | 14 | tearDown(() { 15 | channel.setMockMethodCallHandler(null); 16 | }); 17 | 18 | test('getPlatformVersion', () async {}); 19 | } 20 | --------------------------------------------------------------------------------