quickcapture 1.0.18 copy "quickcapture: ^1.0.18" to clipboard
quickcapture: ^1.0.18 copied to clipboard

QuickCapture AI Based Mobile Document Scanning plugin for Flutter From Extrieve.

example/lib/main.dart

/// Sample Flutter application demonstrating the use of the QuickCapture Flutter plugin.
///
/// This application allows users to:
/// - Attach an image from the gallery.
/// - Capture document with SDK camera.
/// - Compress and optimize image with proper DPI, Layout control.
/// - Convert images to single TIFF and PDF files.
///
/// The application showcases how to integrate and use various features of the QuickCapture plugin.
///
/// Author: Extrieve Technologies
/// Website: https://www.extrieve.com
/// Extrieve Technologies - Your Expert in Document Management & AI Solutions
library;

import 'dart:convert';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:image_picker/image_picker.dart';
import 'package:open_file/open_file.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image/image.dart' as img;

// Import Quickcapture plugin
import 'package:quickcapture/quickcapture.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  // If you want to call showDialog without context, you can use this:
  static final GlobalKey<NavigatorState> navigatorKey =
      GlobalKey<NavigatorState>();

  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

typedef MyCallback = void Function(String result);

class _MyAppState extends State<MyApp> {
  final _quickcapturePlugin = Quickcapture();

  // Store captured images from startCapture()
  List<String> _capturedImage = [];

  @override
  void initState() {
    super.initState();
    // 1) Activate license (if you have any license - optional)
    activateLicense();

    // 2) Initialize the Quickcapture plugin
    _quickcapturePlugin.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // If using the global navigatorKey, set it here:
      navigatorKey: MyApp.navigatorKey,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('QuickCapture Sample Demo'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // Show captured images if we have any
              _capturedImage.isNotEmpty
                  ? Expanded(
                      child: ImageGrid(_capturedImage),
                    )
                  : Container(),
              ElevatedButton(
                onPressed: () => startCapture(),
                child: const Text("Start Capture"),
              ),
              ElevatedButton(
                onPressed: () => buildPDFForLastCapture(),
                child: const Text("Build PDF For Last Capture"),
              ),
              ElevatedButton(
                onPressed: () => buildPDF(),
                child: const Text("Build PDF from images"),
              ),
              ElevatedButton(
                onPressed: () => pickImageFromGallery(),
                child: const Text("Attach from Gallery"),
              ),
              ElevatedButton(
                onPressed: () => pickAndDetectHumanFaces(),
                child: const Text("Human Face Detection & Matching"),
              ),
            ],
          ),
        ),
      ),
    );
  }

  /// Activate Quickcapture license
  Future<void> activateLicense() async {
    // TODO: Securely fetch license keys from a secure source.
    String androidLicense =
        "<Pass the license string for Android platform here>";
    String iosLicense = "<Pass the license string for IOS platform here>";

    bool? response = await _quickcapturePlugin.activateLicense(
      android: androidLicense,
      ios: iosLicense,
    );

    String lisMsg = "License activation failed. Invalid license";
    Color bgColor = const Color.fromARGB(255, 216, 90, 58);
    if (response == true) {
      lisMsg = "License for android activated";
      bgColor = const Color.fromARGB(255, 58, 216, 84);
    }

    Fluttertoast.showToast(
      msg: lisMsg,
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.CENTER,
      backgroundColor: bgColor,
      textColor: Colors.white,
      fontSize: 16.0,
    );
  }

  /// Set config for capturing images with QuickCapture
  void setConfigForCapture() {
    // Example: Set DPI and layout type
    _quickcapturePlugin.config.image.setDPI(DPI.dpi200);
    _quickcapturePlugin.config.image.setLayoutType(LayoutType.A4);

    // Enable flash, set max pages, etc.
    _quickcapturePlugin.config.capture.enableFlash = true;
    _quickcapturePlugin.config.capture.maxPage = 2;
  }

  /// Start capturing images using QuickCapture
  Future<void> startCapture() async {
    try {
      setConfigForCapture();
      String? response = await _quickcapturePlugin.startCapture();
      if (response != null) {
        setState(() {
          Map<String, dynamic> jsonResponse = jsonDecode(response);
          _capturedImage = List<String>.from(jsonResponse['fileCollection']);
        });

        // Save to gallery
        for (String imagePath in _capturedImage) {
          final result = await ImageGallerySaver.saveFile(imagePath);
          if (result['isSuccess'] == true) {
            Fluttertoast.showToast(
              msg: "Image saved to gallery: $imagePath",
              backgroundColor: Colors.green,
              textColor: Colors.white,
            );
          } else {
            Fluttertoast.showToast(
              msg: "Failed to save image: $imagePath",
              backgroundColor: Colors.red,
              textColor: Colors.white,
            );
          }
        }
      } else {
        throw Exception("No response received from startCapture");
      }
    } catch (e) {
      Fluttertoast.showToast(
        msg: "Error capturing image: ${e.toString()}",
        backgroundColor: Colors.red,
        textColor: Colors.white,
      );
    }
  }

  /// Build PDF from last capture
  Future<void> buildPDFForLastCapture() async {
    String? response = await _quickcapturePlugin.buildPDFForLastCapture();
    if (response != null) {
      OpenFile.open(response);
    }
  }

  /// Build PDF from selected images
  Future<void> buildPDF() async {
    final ImagePicker picker = ImagePicker();

    try {
      // Allow multiple image selection
      final List<XFile> images = await picker.pickMultiImage();

      if (images.isEmpty) {
        Fluttertoast.showToast(
          msg: "No images selected.",
          backgroundColor: Colors.orange,
          textColor: Colors.white,
        );
        return;
      }

      // Extract paths of selected images
      final List<String> imagePaths =
          images.map((image) => image.path).toList();

      // Pass the image paths to the buildPDF function
      final String? jsonResponse =
          await _quickcapturePlugin.buildPDF(imagePaths);

      if (jsonResponse != null && jsonResponse.isNotEmpty) {
        // Parse JSON response
        final Map<String, dynamic> response = json.decode(jsonResponse);

        if (response['status'] == true) {
          final String? filePath = response['filePath'];
          if (filePath != null && filePath.isNotEmpty) {
            // Success: Open the PDF file
            Fluttertoast.showToast(
              msg: response['description'] ?? "PDF created successfully.",
              backgroundColor: Colors.green,
              textColor: Colors.white,
            );
            OpenFile.open(filePath); // Open the PDF file
          } else {
            // No file path returned
            Fluttertoast.showToast(
              msg: "PDF created but file path is missing.",
              backgroundColor: Colors.orange,
              textColor: Colors.white,
            );
          }
        } else {
          // Failure response
          Fluttertoast.showToast(
            msg: response['description'] ?? "Failed to create PDF.",
            backgroundColor: Colors.red,
            textColor: Colors.white,
          );
        }
      } else {
        // Invalid or empty response
        Fluttertoast.showToast(
          msg: "Failed to create PDF: Empty response.",
          backgroundColor: Colors.red,
          textColor: Colors.white,
        );
      }
    } catch (e) {
      // Handle errors
      Fluttertoast.showToast(
        msg: "Error building PDF: ${e.toString()}",
        backgroundColor: Colors.red,
        textColor: Colors.white,
      );
    }
  }

  /// Pick image from Gallery, apply QuickCapture compression & optimisation based on imaging config
  Future<void> pickImageFromGallery() async {
    final ImagePicker picker = ImagePicker();

    try {
      final XFile? pickedFile =
          await picker.pickImage(source: ImageSource.gallery);

      if (pickedFile != null) {
        String imagePath = pickedFile.path;
        if (kDebugMode) {
          print("imagePath:$imagePath");
        }

        // Example: set resizing mode
        _quickcapturePlugin.config.image
            .setResizeMode(ResizeMode.fitWithAspect);
        _quickcapturePlugin.config.image.setLayoutType(LayoutType.A4);

        String? compressedImagePath =
            await _quickcapturePlugin.compressToJPEG(imagePath);

        if (compressedImagePath != null && compressedImagePath.isNotEmpty) {
          final result = await ImageGallerySaver.saveFile(compressedImagePath);

          if (result['isSuccess'] == true) {
            Fluttertoast.showToast(
              msg:
                  "Image processed and saved successfully: $compressedImagePath",
              backgroundColor: Colors.green,
              textColor: Colors.white,
            );
          } else {
            Fluttertoast.showToast(
              msg: "Failed to save compressed image to gallery.",
              backgroundColor: Colors.red,
              textColor: Colors.white,
            );
          }
        } else {
          Fluttertoast.showToast(
            msg: "Failed to compress the image.",
            backgroundColor: Colors.red,
            textColor: Colors.white,
          );
        }
      } else {
        Fluttertoast.showToast(
          msg: "No image selected.",
          backgroundColor: Colors.orange,
          textColor: Colors.white,
        );
      }
    } catch (e) {
      Fluttertoast.showToast(
        msg: "Error picking or processing image: ${e.toString()}",
        backgroundColor: Colors.red,
        textColor: Colors.white,
      );
    }
  }

  /// Pick image, detect faces, and show a popup for multiple images with face matching
  Future<void> pickAndDetectHumanFaces() async {
    final ImagePicker picker = ImagePicker();
    List<Map<String, dynamic>> attachedImages = [];
    List<Map<String, dynamic>> allFaceThumbnails = [];
    List<Map<String, dynamic>> selectedThumbnails = [];

    void handleFaceThumbnailSelection(Map<String, dynamic> thumbnail) {
      if (selectedThumbnails.contains(thumbnail)) {
        selectedThumbnails.remove(thumbnail);
      } else {
        if (selectedThumbnails.length < 2) {
          selectedThumbnails.add(thumbnail);
        } else {
          Fluttertoast.showToast(
            msg: "Only 2 thumbnails can be selected at a time.",
            backgroundColor: Colors.orange,
            textColor: Colors.white,
          );
        }
      }
    }

    void showPopup() {
      final ctx = MyApp.navigatorKey.currentContext ?? context;

      showDialog(
        context: ctx,
        builder: (context) {
          return StatefulBuilder(
            builder: (context, setState) {
              return AlertDialog(
                title: const Text("Detect and Match Faces"),
                content: SingleChildScrollView(
                  child: Column(
                    children: [
                      // Display attached images
                      attachedImages.isNotEmpty
                          ? Column(
                              children: attachedImages
                                  .map((imageData) => Image.file(
                                        File(imageData['path']),
                                        width: 100,
                                        height: 100,
                                      ))
                                  .toList(),
                            )
                          : const Text("No images attached"),
                      ElevatedButton(
                        onPressed: () async {
                          final XFile? pickedFile = await picker.pickImage(
                              source: ImageSource.gallery);

                          if (pickedFile != null) {
                            setState(() {
                              attachedImages.add({
                                'path': pickedFile.path,
                                'docId': null,
                              });
                            });

                            Fluttertoast.showToast(
                              msg: "Image attached. Detecting faces...",
                              backgroundColor: Colors.blue,
                              textColor: Colors.white,
                            );

                            String? detectionResponse =
                                await _quickcapturePlugin
                                    .detectHumanFaces(pickedFile.path);

                            if (detectionResponse != null &&
                                detectionResponse.isNotEmpty) {
                              Map<String, dynamic> responseJson =
                                  jsonDecode(detectionResponse);

                              if (responseJson["STATUS"] == true &&
                                  responseJson["DATA"] != null) {
                                List<dynamic> faceData = responseJson["DATA"];
                                final originalBytes =
                                    await File(pickedFile.path).readAsBytes();
                                final img.Image? originalImage =
                                    img.decodeImage(originalBytes);

                                if (originalImage != null) {
                                  for (var face in faceData) {
                                    int left = face["LEFT"]
                                        .clamp(0, originalImage.width);
                                    int right = face["RIGHT"]
                                        .clamp(0, originalImage.width);
                                    int top = face["TOP"]
                                        .clamp(0, originalImage.height);
                                    int bottom = face["BOTTOM"]
                                        .clamp(0, originalImage.height);

                                    final croppedFace = img.copyCrop(
                                        originalImage,
                                        x: left,
                                        y: top,
                                        width: right - left,
                                        height: bottom - top);

                                    final croppedBytes =
                                        img.encodeJpg(croppedFace);
                                    setState(() {
                                      allFaceThumbnails.add({
                                        'thumbnail':
                                            Uint8List.fromList(croppedBytes),
                                        'docId': responseJson['IDENTIFIER'],
                                        'faceIndex': face['INDEX'],
                                      });
                                    });
                                  }
                                }
                              }
                            }
                          }
                        },
                        child: const Text("Attach Image"),
                      ),
                      const SizedBox(height: 16),
                      // Show face thumbnails
                      allFaceThumbnails.isNotEmpty
                          ? Wrap(
                              spacing: 8,
                              runSpacing: 8,
                              children: allFaceThumbnails.map((thumbnailData) {
                                return GestureDetector(
                                  onTap: () {
                                    setState(() {
                                      handleFaceThumbnailSelection(
                                          thumbnailData);
                                    });
                                  },
                                  child: Container(
                                    decoration: BoxDecoration(
                                      border: Border.all(
                                        color: selectedThumbnails
                                                .contains(thumbnailData)
                                            ? Colors.blue
                                            : Colors.transparent,
                                        width: 2,
                                      ),
                                    ),
                                    child: Image.memory(
                                      thumbnailData['thumbnail'],
                                      width: 80,
                                      height: 80,
                                      fit: BoxFit.cover,
                                    ),
                                  ),
                                );
                              }).toList(),
                            )
                          : const Text("No faces detected"),
                    ],
                  ),
                ),
                actions: [
                  TextButton(
                    onPressed: () => Navigator.of(context).pop(),
                    child: const Text("Close"),
                  ),
                  if (selectedThumbnails.length == 2)
                    ElevatedButton(
                      onPressed: () async {
                        Fluttertoast.showToast(
                          msg: "Matching faces...",
                          backgroundColor: Colors.blue,
                          textColor: Colors.white,
                        );

                        String? matchResult =
                            await _quickcapturePlugin.matchHumanFaces(
                                firstDocumentID: selectedThumbnails[0]['docId'],
                                firstDocumentFaceIndex: selectedThumbnails[0]
                                    ['faceIndex'],
                                secondDocumentID: selectedThumbnails[1]
                                    ['docId'],
                                secondDocumentFaceIndex: selectedThumbnails[1]
                                    ['faceIndex']);
                        Fluttertoast.showToast(
                          msg: "Match result: $matchResult",
                          backgroundColor: Colors.green,
                          textColor: Colors.white,
                        );
                      },
                      child: const Text("Match Faces"),
                    ),
                ],
              );
            },
          );
        },
      );
    }

    // Call `initHumanFaceHelper` and show the popup
    bool? initialized = await initHumanFaceHelper();
    if (initialized == true) {
      showPopup();
    } else {
      Fluttertoast.showToast(
        msg: "Failed to initialize Human Face Helper.",
        backgroundColor: Colors.red,
        textColor: Colors.white,
      );
    }
  }

  /// Initialize Human Face Helper
  Future<bool?> initHumanFaceHelper() async {
    bool? result = await _quickcapturePlugin.initHumanFaceHelper();
    String message = result == true
        ? "Human Face Helper initialized successfully."
        : "Failed to initialize Human Face Helper.";
    Fluttertoast.showToast(
      msg: message,
      backgroundColor: result == true ? Colors.green : Colors.red,
      textColor: Colors.white,
    );
    return result;
  }
}

/// Basic widget to display a list of image paths in a grid
class ImageGrid extends StatelessWidget {
  final List<String> imagePaths;

  const ImageGrid(this.imagePaths, {super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3, // images per row
        crossAxisSpacing: 8.0,
        mainAxisSpacing: 8.0,
      ),
      itemCount: imagePaths.length,
      itemBuilder: (context, index) {
        return Image.file(
          File(imagePaths[index]),
          width: 200,
          height: 200,
          fit: BoxFit.contain,
        );
      },
    );
  }
}
15
likes
160
points
233
downloads

Publisher

verified publisherextrieve.com

Weekly Downloads

QuickCapture AI Based Mobile Document Scanning plugin for Flutter From Extrieve.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

collection, flutter, plugin_platform_interface

More

Packages that depend on quickcapture