Browse Source

refactores file structure

pull/5/head
Khoren Markosyan 3 years ago
parent
commit
2c73748e6e
  1. 2
      example/lib/main.dart
  2. 16
      example/pubspec.lock
  3. 247
      lib/flutter_zxing.dart
  4. 13
      lib/src/logic/barcode_encoder.dart
  5. 74
      lib/src/logic/barcode_reader.dart
  6. 13
      lib/src/logic/barcodes_reader.dart
  7. 15
      lib/src/logic/bindings.dart
  8. 28
      lib/src/logic/camera_stream.dart
  9. 29
      lib/src/logic/zxing.dart
  10. 17
      lib/src/ui/reader_widget.dart
  11. 0
      lib/src/ui/scanner_overlay.dart
  12. 8
      lib/src/ui/writer_widget.dart
  13. 78
      lib/src/utils/extentions.dart
  14. 4
      lib/src/utils/image_converter.dart
  15. 13
      lib/src/utils/isolate_utils.dart
  16. 6
      test/flutter_zxing_test.dart
  17. 2
      zxscanner/lib/main.dart
  18. 4
      zxscanner/lib/models/code.dart
  19. 4
      zxscanner/lib/models/encode.dart
  20. 2
      zxscanner/lib/pages/scanner_page.dart
  21. 16
      zxscanner/pubspec.lock

2
example/lib/main.dart

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
void main() {
FlutterZxing.setLogEnabled(kDebugMode);
setZxingLogEnabled(kDebugMode);
runApp(const MyApp());
}

16
example/pubspec.lock

@ -14,7 +14,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.9.0"
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
@ -28,6 +28,20 @@ packages:
name: camera
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.8"
camera_android:
dependency: transitive
description:
name: camera_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+1"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+1"
camera_platform_interface:
dependency: transitive

247
lib/flutter_zxing.dart

@ -1,242 +1,7 @@
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as imglib;
import 'generated_bindings.dart';
import 'isolate_utils.dart';
export 'generated_bindings.dart';
export 'image_converter.dart';
export 'reader_widget.dart';
export 'scanner_overlay.dart';
export 'writer_widget.dart';
// TODO: fix below warning from lint
// ignore: avoid_classes_with_only_static_members
/// The main class for reading barcodes from images or camera.
class FlutterZxing {
static const MethodChannel _channel = MethodChannel('flutter_zxing');
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
static final GeneratedBindings bindings = GeneratedBindings(dylib);
static IsolateUtils? isolateUtils;
/// Enables or disables the logging of the library
static void setLogEnabled(bool enabled) =>
bindings.setLogEnabled(enabled ? 1 : 0);
/// Returns a version of the zxing library
static String version() => bindings.version().cast<Utf8>().toDartString();
/// Starts reading barcode from the camera
static Future<void> startCameraProcessing() async {
// Spawn a new isolate
isolateUtils = IsolateUtils();
await isolateUtils?.startReadingBarcode();
}
/// Stops reading barcode from the camera
static void stopCameraProcessing() => isolateUtils?.stopReadingBarcode();
/// Reads barcode from String image path
static Future<CodeResult?> readImagePathString(
String path, {
int format = Format.Any,
int cropWidth = 0,
int cropHeight = 0,
}) =>
readImagePath(XFile(path),
format: format, cropWidth: cropWidth, cropHeight: cropHeight);
/// Reads barcode from XFile image path
static Future<CodeResult?> readImagePath(
XFile path, {
int format = Format.Any,
int cropWidth = 0,
int cropHeight = 0,
}) async {
final Uint8List imageBytes = await path.readAsBytes();
final imglib.Image? image = imglib.decodeImage(imageBytes);
if (image == null) {
return null;
}
return readBarcode(
image.getBytes(format: imglib.Format.luminance),
format,
image.width,
image.height,
cropWidth,
cropHeight,
);
}
/// Reads barcode from image url
static Future<CodeResult?> readImageUrl(
String url, {
int format = Format.Any,
int cropWidth = 0,
int cropHeight = 0,
}) async {
final Uint8List imageBytes =
(await NetworkAssetBundle(Uri.parse(url)).load(url))
.buffer
.asUint8List();
final imglib.Image? image = imglib.decodeImage(imageBytes);
if (image == null) {
return null;
}
return readBarcode(
image.getBytes(format: imglib.Format.luminance),
format,
image.width,
image.height,
cropWidth,
cropHeight,
);
}
static CodeResult readBarcode(Uint8List bytes, int format, int width,
int height, int cropWidth, int cropHeight) {
return bindings.readBarcode(
bytes.allocatePointer(), format, width, height, cropWidth, cropHeight);
}
static List<CodeResult> readBarcodes(Uint8List bytes, int format, int width,
int height, int cropWidth, int cropHeight) {
final CodeResults result = bindings.readBarcodes(
bytes.allocatePointer(), format, width, height, cropWidth, cropHeight);
final List<CodeResult> results = <CodeResult>[];
for (int i = 0; i < result.count; i++) {
results.add(result.results.elementAt(i).ref);
}
return results;
}
static EncodeResult encodeBarcode(String contents, int width, int height,
int format, int margin, int eccLevel) {
final EncodeResult result = bindings.encodeBarcode(
contents.toNativeUtf8().cast<Char>(),
width,
height,
format,
margin,
eccLevel);
return result;
}
static Future<CodeResult> processCameraImage(CameraImage image,
{int format = Format.Any, double cropPercent = 0.5}) async {
final IsolateData isolateData = IsolateData(image, format, cropPercent);
final CodeResult result = await _inference(isolateData);
return result;
}
/// Runs inference in another isolate
static Future<CodeResult> _inference(IsolateData isolateData) async {
final ReceivePort responsePort = ReceivePort();
isolateUtils?.sendPort
?.send(isolateData..responsePort = responsePort.sendPort);
// TODO: fix below warning from lint
// ignore: always_specify_types
final results = await responsePort.first;
return results;
}
static String formatName(int format) => _formatNames[format] ?? 'Unknown';
}
// Getting a library that holds needed symbols
DynamicLibrary _openDynamicLibrary() {
if (Platform.isAndroid) {
return DynamicLibrary.open('libflutter_zxing.so');
} else if (Platform.isWindows) {
return DynamicLibrary.open('flutter_zxing_windows_plugin.dll');
}
return DynamicLibrary.process();
}
DynamicLibrary dylib = _openDynamicLibrary();
extension Uint8ListBlobConversion on Uint8List {
/// Allocates a pointer filled with the Uint8List data.
Pointer<Char> allocatePointer() {
final Pointer<Int8> blob = calloc<Int8>(length);
final Int8List blobBytes = blob.asTypedList(length);
blobBytes.setAll(0, this);
return blob.cast<Char>();
}
}
extension CodeExt on CodeResult {
bool get isValidBool => isValid == 1;
String? get textString =>
text == nullptr ? null : text.cast<Utf8>().toDartString();
String get formatString => FlutterZxing.formatName(format);
}
extension EncodeExt on EncodeResult {
bool get isValidBool => isValid == 1;
String? get textString =>
text == nullptr ? null : text.cast<Utf8>().toDartString();
String get formatString => FlutterZxing.formatName(format);
Uint32List get bytes => data.cast<Uint32>().asTypedList(length);
String get errorMessage => error.cast<Utf8>().toDartString();
}
extension CodeFormat on Format {
String get name => _formatNames[this] ?? 'Unknown';
static final List<int> writerFormats = <int>[
Format.QRCode,
Format.DataMatrix,
Format.Aztec,
Format.PDF417,
Format.Codabar,
Format.Code39,
Format.Code93,
Format.Code128,
Format.EAN8,
Format.EAN13,
Format.ITF,
Format.UPCA,
Format.UPCE,
// Format.DataBar,
// Format.DataBarExpanded,
// Format.MaxiCode,
];
}
final Map<int, String> _formatNames = <int, String>{
Format.None: 'None',
Format.Aztec: 'Aztec',
Format.Codabar: 'CodaBar',
Format.Code39: 'Code39',
Format.Code93: 'Code93',
Format.Code128: 'Code128',
Format.DataBar: 'DataBar',
Format.DataBarExpanded: 'DataBarExpanded',
Format.DataMatrix: 'DataMatrix',
Format.EAN8: 'EAN8',
Format.EAN13: 'EAN13',
Format.ITF: 'ITF',
Format.MaxiCode: 'MaxiCode',
Format.PDF417: 'PDF417',
Format.QRCode: 'QR Code',
Format.UPCA: 'UPCA',
Format.UPCE: 'UPCE',
Format.OneDCodes: 'OneD',
Format.TwoDCodes: 'TwoD',
Format.Any: 'Any',
};
export 'src/logic/zxing.dart';
export 'src/ui/reader_widget.dart';
export 'src/ui/scanner_overlay.dart';
export 'src/ui/writer_widget.dart';
export 'src/utils/extentions.dart';
export 'src/utils/image_converter.dart';

13
lib/src/logic/barcode_encoder.dart

@ -0,0 +1,13 @@
part of 'zxing.dart';
EncodeResult encodeBarcode(String contents, int width, int height, int format,
int margin, int eccLevel) {
return bindings.encodeBarcode(
contents.toNativeUtf8().cast<Char>(),
width,
height,
format,
margin,
eccLevel,
);
}

74
lib/src/logic/barcode_reader.dart

@ -0,0 +1,74 @@
part of 'zxing.dart';
/// Reads barcode from String image path
Future<CodeResult?> readImagePathString(
String path, {
int format = Format.Any,
int cropWidth = 0,
int cropHeight = 0,
}) =>
readImagePath(XFile(path),
format: format, cropWidth: cropWidth, cropHeight: cropHeight);
/// Reads barcode from XFile image path
Future<CodeResult?> readImagePath(
XFile path, {
int format = Format.Any,
int cropWidth = 0,
int cropHeight = 0,
}) async {
final Uint8List imageBytes = await path.readAsBytes();
final imglib.Image? image = imglib.decodeImage(imageBytes);
if (image == null) {
return null;
}
return readBarcode(
image.getBytes(format: imglib.Format.luminance),
format,
image.width,
image.height,
cropWidth,
cropHeight,
);
}
/// Reads barcode from image url
Future<CodeResult?> readImageUrl(
String url, {
int format = Format.Any,
int cropWidth = 0,
int cropHeight = 0,
}) async {
final Uint8List imageBytes =
(await NetworkAssetBundle(Uri.parse(url)).load(url)).buffer.asUint8List();
final imglib.Image? image = imglib.decodeImage(imageBytes);
if (image == null) {
return null;
}
return readBarcode(
image.getBytes(format: imglib.Format.luminance),
format,
image.width,
image.height,
cropWidth,
cropHeight,
);
}
// Reads barcode from Uint8List image bytes
CodeResult readBarcode(
Uint8List bytes,
int format,
int width,
int height,
int cropWidth,
int cropHeight,
) =>
bindings.readBarcode(
bytes.allocatePointer(),
format,
width,
height,
cropWidth,
cropHeight,
);

13
lib/src/logic/barcodes_reader.dart

@ -0,0 +1,13 @@
part of 'zxing.dart';
// Reads barcodes from Uint8List image bytes
List<CodeResult> readBarcodes(Uint8List bytes, int format, int width,
int height, int cropWidth, int cropHeight) {
final CodeResults result = bindings.readBarcodes(
bytes.allocatePointer(), format, width, height, cropWidth, cropHeight);
final List<CodeResult> results = <CodeResult>[];
for (int i = 0; i < result.count; i++) {
results.add(result.results.elementAt(i).ref);
}
return results;
}

15
lib/src/logic/bindings.dart

@ -0,0 +1,15 @@
part of 'zxing.dart';
final GeneratedBindings bindings = GeneratedBindings(dylib);
// Getting a library that holds needed symbols
DynamicLibrary _openDynamicLibrary() {
if (Platform.isAndroid) {
return DynamicLibrary.open('libflutter_zxing.so');
} else if (Platform.isWindows) {
return DynamicLibrary.open('flutter_zxing_windows_plugin.dll');
}
return DynamicLibrary.process();
}
DynamicLibrary dylib = _openDynamicLibrary();

28
lib/src/logic/camera_stream.dart

@ -0,0 +1,28 @@
part of 'zxing.dart';
IsolateUtils? isolateUtils;
/// Starts reading barcode from the camera
Future<void> startCameraProcessing() async {
isolateUtils = IsolateUtils();
await isolateUtils?.startReadingBarcode();
}
/// Stops reading barcode from the camera
void stopCameraProcessing() => isolateUtils?.stopReadingBarcode();
Future<CodeResult> processCameraImage(CameraImage image,
{int format = Format.Any, double cropPercent = 0.5}) async {
final IsolateData isolateData = IsolateData(image, format, cropPercent);
final CodeResult result = await _inference(isolateData);
return result;
}
/// Runs inference in another isolate
Future<CodeResult> _inference(IsolateData isolateData) async {
final ReceivePort responsePort = ReceivePort();
isolateUtils?.sendPort
?.send(isolateData..responsePort = responsePort.sendPort);
final dynamic results = await responsePort.first;
return results;
}

29
lib/src/logic/zxing.dart

@ -0,0 +1,29 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as imglib;
import '../../generated_bindings.dart';
import '../utils/extentions.dart';
import '../utils/isolate_utils.dart';
part 'barcode_encoder.dart';
part 'barcode_reader.dart';
part 'barcodes_reader.dart';
part 'bindings.dart';
part 'camera_stream.dart';
/// Returns a version of the zxing library
String zxingVersion() => bindings.version().cast<Utf8>().toDartString();
/// Enables or disables the logging of the library
void setZxingLogEnabled(bool enabled) =>
bindings.setLogEnabled(enabled ? 1 : 0);
/// Returns a readable barcode format name
String barcodeFormatName(int format) => formatNames[format] ?? 'Unknown';

17
lib/reader_widget.dart → lib/src/ui/reader_widget.dart

@ -6,9 +6,12 @@ import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_beep/flutter_beep.dart';
import 'flutter_zxing.dart';
import 'isolate_utils.dart';
import '../../generated_bindings.dart';
import '../logic/zxing.dart';
import '../utils/extentions.dart';
import '../utils/isolate_utils.dart';
import 'scanner_overlay.dart';
class ReaderWidget extends StatefulWidget {
const ReaderWidget({
@ -70,7 +73,7 @@ class _ReaderWidgetState extends State<ReaderWidget>
Future<void> initStateAsync() async {
// Spawn a new isolate
await FlutterZxing.startCameraProcessing();
await startCameraProcessing();
availableCameras().then((List<CameraDescription> cameras) {
setState(() {
@ -105,7 +108,7 @@ class _ReaderWidgetState extends State<ReaderWidget>
@override
void dispose() {
FlutterZxing.stopCameraProcessing();
stopCameraProcessing();
controller?.dispose();
super.dispose();
}
@ -131,7 +134,7 @@ class _ReaderWidgetState extends State<ReaderWidget>
await cameraController.setFlashMode(FlashMode.off);
_maxZoomLevel = await cameraController.getMaxZoomLevel();
_minZoomLevel = await cameraController.getMinZoomLevel();
cameraController.startImageStream(processCameraImage);
cameraController.startImageStream(processImageStream);
} on CameraException catch (e) {
debugPrint('${e.code}: ${e.description}');
}
@ -150,11 +153,11 @@ class _ReaderWidgetState extends State<ReaderWidget>
widget.onControllerCreated?.call(cameraController);
}
Future<void> processCameraImage(CameraImage image) async {
Future<void> processImageStream(CameraImage image) async {
if (!_isProcessing) {
_isProcessing = true;
try {
final CodeResult result = await FlutterZxing.processCameraImage(
final CodeResult result = await processCameraImage(
image,
format: widget.codeFormat,
cropPercent: widget.cropPercent,

0
lib/scanner_overlay.dart → lib/src/ui/scanner_overlay.dart

8
lib/writer_widget.dart → lib/src/ui/writer_widget.dart

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as imglib;
import 'flutter_zxing.dart';
import '../../flutter_zxing.dart';
class WriterWidget extends StatefulWidget {
const WriterWidget({
@ -79,7 +79,7 @@ class _WriterWidgetState extends State<WriterWidget>
items: _supportedFormats
.map((int format) => DropdownMenuItem<int>(
value: format,
child: Text(FlutterZxing.formatName(format)),
child: Text(barcodeFormatName(format)),
))
.toList(),
onChanged: (int? format) {
@ -187,8 +187,8 @@ class _WriterWidgetState extends State<WriterWidget>
final int height = int.parse(_heightController.value.text);
final int margin = int.parse(_marginController.value.text);
final int ecc = int.parse(_eccController.value.text);
final EncodeResult result = FlutterZxing.encodeBarcode(
text, width, height, _codeFormat, margin, ecc);
final EncodeResult result =
encodeBarcode(text, width, height, _codeFormat, margin, ecc);
String? error;
if (result.isValidBool) {
try {

78
lib/src/utils/extentions.dart

@ -0,0 +1,78 @@
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import '../../flutter_zxing.dart';
extension Uint8ListBlobConversion on Uint8List {
/// Allocates a pointer filled with the Uint8List data.
Pointer<Char> allocatePointer() {
final Pointer<Int8> blob = calloc<Int8>(length);
final Int8List blobBytes = blob.asTypedList(length);
blobBytes.setAll(0, this);
return blob.cast<Char>();
}
}
extension CodeExt on CodeResult {
bool get isValidBool => isValid == 1;
String? get textString =>
text == nullptr ? null : text.cast<Utf8>().toDartString();
String get formatString => barcodeFormatName(format);
}
extension EncodeExt on EncodeResult {
bool get isValidBool => isValid == 1;
String? get textString =>
text == nullptr ? null : text.cast<Utf8>().toDartString();
String get formatString => barcodeFormatName(format);
Uint32List get bytes => data.cast<Uint32>().asTypedList(length);
String get errorMessage => error.cast<Utf8>().toDartString();
}
extension CodeFormat on Format {
String get name => formatNames[this] ?? 'Unknown';
static final List<int> writerFormats = <int>[
Format.QRCode,
Format.DataMatrix,
Format.Aztec,
Format.PDF417,
Format.Codabar,
Format.Code39,
Format.Code93,
Format.Code128,
Format.EAN8,
Format.EAN13,
Format.ITF,
Format.UPCA,
Format.UPCE,
// Format.DataBar,
// Format.DataBarExpanded,
// Format.MaxiCode,
];
}
final Map<int, String> formatNames = <int, String>{
Format.None: 'None',
Format.Aztec: 'Aztec',
Format.Codabar: 'CodaBar',
Format.Code39: 'Code39',
Format.Code93: 'Code93',
Format.Code128: 'Code128',
Format.DataBar: 'DataBar',
Format.DataBarExpanded: 'DataBarExpanded',
Format.DataMatrix: 'DataMatrix',
Format.EAN8: 'EAN8',
Format.EAN13: 'EAN13',
Format.ITF: 'ITF',
Format.MaxiCode: 'MaxiCode',
Format.PDF417: 'PDF417',
Format.QRCode: 'QR Code',
Format.UPCA: 'UPCA',
Format.UPCE: 'UPCE',
Format.OneDCodes: 'OneD',
Format.TwoDCodes: 'TwoD',
Format.Any: 'Any',
};

4
lib/image_converter.dart → lib/src/utils/image_converter.dart

@ -44,9 +44,7 @@ imglib.Image convertYUV420(CameraImage image) {
for (int planeOffset = 0;
planeOffset < image.height * image.width;
planeOffset += image.width) {
// TODO: fix below warning from lint
// ignore: always_specify_types
final pixelColor = plane.bytes[planeOffset + x];
final int pixelColor = plane.bytes[planeOffset + x];
// color: 0x FF FF FF FF
// A B G R
// Calculate pixel color

13
lib/isolate_utils.dart → lib/src/utils/isolate_utils.dart

@ -4,7 +4,9 @@ import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'flutter_zxing.dart';
import '../../generated_bindings.dart';
import '../logic/zxing.dart';
import 'image_converter.dart';
// Inspired from https://github.com/am15h/object_detection_flutter
@ -62,13 +64,8 @@ class IsolateUtils {
final int cropSize =
(min(image.width, image.height) * cropPercent).round();
final CodeResult result = FlutterZxing.readBarcode(
bytes,
isolateData.format,
image.width,
image.height,
cropSize,
cropSize);
final CodeResult result = readBarcode(bytes, isolateData.format,
image.width, image.height, cropSize, cropSize);
isolateData.responsePort?.send(result);
} catch (e) {

6
test/flutter_zxing_test.dart

@ -1,6 +1,6 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
// import 'package:flutter_zxing/flutter_zxing.dart';
void main() {
const MethodChannel channel = MethodChannel('flutter_zxing');
@ -16,8 +16,4 @@ void main() {
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await FlutterZxing.platformVersion, '42');
});
}

2
zxscanner/lib/main.dart

@ -18,7 +18,7 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializePrefs();
await DbService.instance.initializeApp();
FlutterZxing.setLogEnabled(kDebugMode);
setZxingLogEnabled(kDebugMode);
runApp(const MyApp());
}

4
zxscanner/lib/models/code.dart

@ -1,7 +1,7 @@
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:hive_flutter/hive_flutter.dart';
part "code.g.dart";
part 'code.g.dart';
@HiveType(typeId: 0)
class Code extends HiveObject {
@ -22,5 +22,5 @@ class Code extends HiveObject {
text = result.textString;
}
String get formatName => FlutterZxing.formatName(format ?? 0);
String get formatName => barcodeFormatName(format ?? 0);
}

4
zxscanner/lib/models/encode.dart

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:hive_flutter/hive_flutter.dart';
part "encode.g.dart";
part 'encode.g.dart';
@HiveType(typeId: 1)
class Encode extends HiveObject {
@ -32,5 +32,5 @@ class Encode extends HiveObject {
length = result.length;
}
String get formatName => FlutterZxing.formatName(format ?? 0);
String get formatName => barcodeFormatName(format ?? 0);
}

2
zxscanner/lib/pages/scanner_page.dart

@ -49,7 +49,7 @@ class _ScannerPageState extends State<ScannerPage> {
}
readCodeFromImage(XFile file) async {
final CodeResult? result = await FlutterZxing.readImagePath(file);
final CodeResult? result = await readImagePath(file);
if (result != null && result.isValidBool) {
addCode(result);
} else {

16
zxscanner/pubspec.lock

@ -35,7 +35,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.9.0"
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
@ -105,6 +105,20 @@ packages:
name: camera
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.8"
camera_android:
dependency: transitive
description:
name: camera_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+1"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+1"
camera_platform_interface:
dependency: transitive

Loading…
Cancel
Save