quickcapture 1.0.15 copy "quickcapture: ^1.0.15" to clipboard
quickcapture: ^1.0.15 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,
        );
      },
    );
  }
}
16
likes
0
points
235
downloads

Publisher

verified publisherextrieve.com

Weekly Downloads

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

Homepage

License

unknown (license)

Dependencies

collection, flutter, plugin_platform_interface

More

Packages that depend on quickcapture

Packages that implement quickcapture