Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "example",
"cwd": "example",
"request": "launch",
"type": "dart"
}
]
}
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## 2.0.0
### Breaking Changes
* Reorganized library structure: all implementation files moved to `lib/src/` directory.
* Renamed `ios_options.dart` to `ios_scanner_options.dart` for better clarity.
* Separated `IosImageFormat` enum into its own file (`ios_image_format.dart`).

### Improvements
* Added custom exception `CunningDocumentScannerException` with specific error codes.
* Replaced generic `Exception` with `CunningDocumentScannerException.permissionDenied()` for better error handling.
* Improved code organization with barrel exports - users only need a single import.
* Added comprehensive unit tests for custom exceptions.
* Enhanced equality operators for `CunningDocumentScannerException`.

### Migration Guide
* No changes required for users - the public API remains the same with `import 'package:cunning_document_scanner/cunning_document_scanner.dart';`
* If catching exceptions, update catch blocks to use `CunningDocumentScannerException` instead of generic `Exception`.

## 1.4.0
### General
* Bumped `permission_handler` to `12.0.1`.
Expand Down
12 changes: 7 additions & 5 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';

import 'package:cunning_document_scanner/cunning_document_scanner.dart';
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
Expand Down Expand Up @@ -49,10 +50,11 @@ class _MyAppState extends State<MyApp> {
List<String> pictures;
try {
pictures = await CunningDocumentScanner.getPictures(
isGalleryImportAllowed: true,
iosScannerOptions: IosScannerOptions(
imageFormat: IosImageFormat.jpg,
jpgCompressionQuality: 0.5,
)) ??
imageFormat: IosImageFormat.jpg,
jpgCompressionQuality: 0.5,
)) ??
[];
if (!mounted) return;
setState(() {
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.3.1"
version: "2.0.0"
cupertino_icons:
dependency: "direct main"
description:
Expand Down
53 changes: 5 additions & 48 deletions lib/cunning_document_scanner.dart
Original file line number Diff line number Diff line change
@@ -1,49 +1,6 @@
import 'dart:async';
// Cunning Document Scanner - A simple document scanning library for Flutter.

import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';

import 'ios_options.dart';

export 'ios_options.dart';

/// A class that provides a simple way to scan documents.
class CunningDocumentScanner {
/// The method channel used to interact with the native platform.
static const MethodChannel _channel =
MethodChannel('cunning_document_scanner');

/// Starts the document scanning process.
///
/// This method will open the camera and allow the user to scan documents.
///
/// [noOfPages] is the maximum number of pages that can be scanned.
/// [isGalleryImportAllowed] is a flag that allows the user to import images from the gallery.
/// [iosScannerOptions] is a set of options for the iOS scanner.
///
/// Returns a list of paths to the scanned images, or null if the user cancels the operation.
static Future<List<String>?> getPictures({
int noOfPages = 100,
bool isGalleryImportAllowed = false,
IosScannerOptions? iosScannerOptions,
}) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
].request();
if (statuses.containsValue(PermissionStatus.denied) ||
statuses.containsValue(PermissionStatus.permanentlyDenied)) {
throw Exception("Permission not granted");
}

final List<dynamic>? pictures = await _channel.invokeMethod('getPictures', {
'noOfPages': noOfPages,
'isGalleryImportAllowed': isGalleryImportAllowed,
if (iosScannerOptions != null)
'iosScannerOptions': {
'imageFormat': iosScannerOptions.imageFormat.name,
'jpgCompressionQuality': iosScannerOptions.jpgCompressionQuality,
}
});
return pictures?.map((e) => e as String).toList();
}
}
export 'src/cunning_document_scanner.dart';
export 'src/exceptions.dart';
export 'src/ios_image_format.dart';
export 'src/ios_scanner_options.dart';
49 changes: 49 additions & 0 deletions lib/src/cunning_document_scanner.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';

import 'exceptions.dart';
import 'ios_scanner_options.dart';

/// A class that provides a simple way to scan documents.
class CunningDocumentScanner {
/// The method channel used to interact with the native platform.
static const MethodChannel _channel =
MethodChannel('cunning_document_scanner');

/// Starts the document scanning process.
///
/// This method will open the camera and allow the user to scan documents.
///
/// [noOfPages] is the maximum number of pages that can be scanned.
/// [isGalleryImportAllowed] is a flag that allows the user to import images from the gallery.
/// [iosScannerOptions] is a set of options for the iOS scanner.
///
/// Returns a list of paths to the scanned images, or null if the user cancels the operation.
static Future<List<String>?> getPictures({
int noOfPages = 100,
bool isGalleryImportAllowed = false,
IosScannerOptions? iosScannerOptions,
}) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
].request();
if (statuses.containsValue(PermissionStatus.denied) ||
statuses.containsValue(PermissionStatus.permanentlyDenied)) {
throw const CunningDocumentScannerException.permissionDenied(
'Camera permission not granted');
}

final List<dynamic>? pictures = await _channel.invokeMethod('getPictures', {
'noOfPages': noOfPages,
'isGalleryImportAllowed': isGalleryImportAllowed,
if (iosScannerOptions != null)
'iosScannerOptions': {
'imageFormat': iosScannerOptions.imageFormat.name,
'jpgCompressionQuality': iosScannerOptions.jpgCompressionQuality,
}
});
return pictures?.map((e) => e as String).toList();
}
}
30 changes: 30 additions & 0 deletions lib/src/exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// Custom exceptions thrown by `CunningDocumentScanner`.
class CunningDocumentScannerException implements Exception {
/// A short message describing the error.
final String message;

/// Optional code to categorize errors (e.g. 'permission_denied').
final String? code;

const CunningDocumentScannerException(this.message, {this.code});

/// Named constructor for permission errors.
const CunningDocumentScannerException.permissionDenied([
String message = 'Permission not granted',
]) : this(message, code: 'permission_denied');

@override
String toString() =>
'CunningDocumentScannerException(${code ?? 'error'}): $message';

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CunningDocumentScannerException &&
runtimeType == other.runtimeType &&
message == other.message &&
code == other.code;

@override
int get hashCode => message.hashCode ^ code.hashCode;
}
8 changes: 8 additions & 0 deletions lib/src/ios_image_format.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// Enumerates the different output image formats are supported.
enum IosImageFormat {
/// Indicates the output image should be formatted as JPEG image.
jpg,

/// Indicates the output image should be formatted as PNG image.
png,
}
9 changes: 1 addition & 8 deletions lib/ios_options.dart → lib/src/ios_scanner_options.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
/// Enumerates the different output image formats are supported.
enum IosImageFormat {
/// Indicates the output image should be formatted as JPEG image.
jpg,

/// Indicates the output image should be formatted as PNG image.
png,
}
import 'ios_image_format.dart';

/// Different options that modify the behavior of the document scanner on iOS.
///
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: cunning_document_scanner
description: A document scanner plugin for flutter. Scan and crop automatically on iOS and Android.
version: 1.4.0
version: 2.0.0
homepage: https://cunning.biz
repository: https://github.com/jachzen/cunning_document_scanner

Expand All @@ -19,7 +19,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ">=3.0.1 <7.0.0"
mockito: ^5.4.5
mockito: ^5.6.1

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
Expand Down
18 changes: 14 additions & 4 deletions test/cunning_document_scanner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ void main() {
});

test('getPictures with denied permission', () async {
expect(() async => await CunningDocumentScanner.getPictures(),
throwsA(isA<Exception>()));
expect(
() async => await CunningDocumentScanner.getPictures(),
throwsA(isA<CunningDocumentScannerException>()
.having((e) => e.code, 'code', 'permission_denied')
.having(
(e) => e.message, 'message', 'Camera permission not granted')),
);
});
});

Expand All @@ -25,8 +30,13 @@ void main() {
});

test('getPictures with permanently denied permission', () async {
expect(() async => await CunningDocumentScanner.getPictures(),
throwsA(isA<Exception>()));
expect(
() async => await CunningDocumentScanner.getPictures(),
throwsA(isA<CunningDocumentScannerException>()
.having((e) => e.code, 'code', 'permission_denied')
.having(
(e) => e.message, 'message', 'Camera permission not granted')),
);
});
});

Expand Down
Loading