itc_scanner 0.0.1 copy "itc_scanner: ^0.0.1" to clipboard
itc_scanner: ^0.0.1 copied to clipboard

Flutter plugin for extracting data from Ghana vehicle licensing documents using ML Kit text recognition. Simple integration for cross-platform document scanning.

example/lib/main.dart

import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:itc_scanner/itc_scanner.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});

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

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  bool _isLoading = false;
  File? _selectedImage;
  Map<String, dynamic>? _extractionResult;
  final _itcScannerPlugin = ItcScanner();

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  Future<void> initPlatformState() async {
    String platformVersion;
    try {
      platformVersion =
          await _itcScannerPlugin.getPlatformVersion() ??
          'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  Future<void> _pickImageFromSource(ImageSource source) async {
    try {
      final ImagePicker picker = ImagePicker();
      final XFile? image = await picker.pickImage(
        source: source,
        maxWidth: 1024,
        maxHeight: 1024,
        imageQuality: 90,
      );

      if (image != null) {
        setState(() {
          _selectedImage = File(image.path);
          _extractionResult = null; 
        });
      }
    } catch (e) {
      _showErrorSnackBar(
        'Error ${source == ImageSource.camera ? 'capturing' : 'selecting'} image: $e',
      );
    }
  }

  Future<void> _extractDocument() async {
    if (_selectedImage == null) {
      _showErrorSnackBar('Please capture or select an image first');
      return;
    }

    setState(() {
      _isLoading = true;
    });

    try {
      final Uint8List imageBytes = await _selectedImage!.readAsBytes();
      final result = await _itcScannerPlugin.extractDocument(imageBytes);

      setState(() {
        _extractionResult = result;
      });
    } catch (e) {
      _showErrorSnackBar('Error extracting document: $e');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  void _clearImage() {
    setState(() {
      _selectedImage = null;
      _extractionResult = null;
    });
  }

  void _showErrorSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message), backgroundColor: Colors.red[600]),
    );
  }

  Widget _buildFieldCard(
    String label,
    String value,
    double confidence,
    String fieldType,
  ) {
    final confidencePercent = (confidence * 100).toInt();
    Color confidenceColor = Colors.green;
    if (confidence < 0.5) {
      confidenceColor = Colors.red;
    } else if (confidence < 0.8) {
      confidenceColor = Colors.orange;
    }

    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  label,
                  style: const TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                    color: Colors.black87,
                  ),
                ),
                Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 8,
                    vertical: 4,
                  ),
                  decoration: BoxDecoration(
                    color: confidenceColor.withValues(alpha: .1),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: confidenceColor.withValues(alpha: .3),
                    ),
                  ),
                  child: Text(
                    '$confidencePercent%',
                    style: TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                      color: confidenceColor,
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 8),
            Text(
              value,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.w600,
                color: Colors.blue,
              ),
            ),
            const SizedBox(height: 4),
            Text(
              'Type: $fieldType',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSummaryCard() {
    if (_extractionResult == null) return const SizedBox.shrink();

    final fields = _extractionResult!['fields'] as List<dynamic>? ?? [];
    final processingTime = _extractionResult!['processingTime'] ?? 0;
    final documentType = _extractionResult!['documentType'] ?? 'Unknown';

    final highConfidenceFields = fields
        .where((f) => (f['confidence'] ?? 0) > 0.8)
        .length;
    final avgConfidence = fields.isNotEmpty
        ? fields
                  .map((f) => f['confidence'] as double? ?? 0.0)
                  .reduce((a, b) => a + b) /
              fields.length
        : 0.0;

    return Card(
      color: Colors.green[50],
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.analytics, color: Colors.green[700], size: 24),
                const SizedBox(width: 8),
                const Text(
                  'Extraction Summary',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                    color: Colors.green,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            _buildSummaryRow('Document Type', documentType),
            _buildSummaryRow('Processing Time', '${processingTime}ms'),
            _buildSummaryRow('Total Fields', '${fields.length}'),
            _buildSummaryRow(
              'High Confidence Fields',
              '$highConfidenceFields/${fields.length}',
            ),
            _buildSummaryRow(
              'Average Confidence',
              '${(avgConfidence * 100).toInt()}%',
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSummaryRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            label,
            style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
          ),
          Text(
            value,
            style: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.bold,
              color: Colors.green,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildApiKeysCard() {
    return Card(
      color: Colors.blue[50],
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.code, color: Colors.blue[700], size: 20),
                const SizedBox(width: 8),
                const Text(
                  'Keys for Third-Party Integration',
                  style: TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.bold,
                    color: Colors.blue,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            const Text(
              'Field Labels (keys):',
              style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 4),
            Wrap(
              spacing: 8,
              runSpacing: 4,
              children: [
                _buildKeyChip('Registration Number'),
                _buildKeyChip('Document ID'),
                _buildKeyChip('Make'),
                _buildKeyChip('Model'),
                _buildKeyChip('Color'),
                _buildKeyChip('Use'),
                _buildKeyChip('Expiry Date'),
              ],
            ),
            const SizedBox(height: 8),
            const Text(
              'Field Properties: label, value, confidence, fieldType',
              style: TextStyle(
                fontSize: 11,
                fontFamily: 'monospace',
                color: Colors.blue,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildKeyChip(String key) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: Colors.blue[100],
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(
        key,
        style: const TextStyle(
          fontSize: 10,
          fontFamily: 'monospace',
          color: Colors.blue,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('ITC Scanner Test'),
          backgroundColor: Colors.blue[700],
          foregroundColor: Colors.white,
        ),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Platform version
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Text(
                    'Running on: $_platformVersion',
                    style: const TextStyle(fontSize: 16),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),

              const SizedBox(height: 20),

              // Instructions
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.blue[50],
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.blue[200]!),
                ),
                child: const Text(
                  '📸 Capture or select a Ghana vehicle registration document (road worthy certificate) to test the extraction.',
                  style: TextStyle(fontSize: 14, color: Colors.blue),
                  textAlign: TextAlign.center,
                ),
              ),

              const SizedBox(height: 20),

              // Image preview
              if (_selectedImage != null) ...[
                Container(
                  height: 200,
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.grey),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(8),
                    child: Image.file(_selectedImage!, fit: BoxFit.contain),
                  ),
                ),
                const SizedBox(height: 8),
                TextButton.icon(
                  onPressed: _clearImage,
                  icon: const Icon(Icons.clear, size: 18),
                  label: const Text('Clear Image'),
                  style: TextButton.styleFrom(foregroundColor: Colors.red[600]),
                ),
                const SizedBox(height: 16),
              ],

              // Action buttons
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: () => _pickImageFromSource(ImageSource.camera),
                      icon: const Icon(Icons.camera_alt),
                      label: const Text('Camera'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue[600],
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(vertical: 12),
                      ),
                    ),
                  ),
                  const SizedBox(width: 12),
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: () =>
                          _pickImageFromSource(ImageSource.gallery),
                      icon: const Icon(Icons.photo_library),
                      label: const Text('Gallery'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.orange[600],
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(vertical: 12),
                      ),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 16),

              // Extract button
              SizedBox(
                width: double.infinity,
                child: ElevatedButton.icon(
                  onPressed: _isLoading ? null : _extractDocument,
                  icon: _isLoading
                      ? const SizedBox(
                          width: 20,
                          height: 20,
                          child: CircularProgressIndicator(
                            strokeWidth: 2,
                            valueColor: AlwaysStoppedAnimation<Color>(
                              Colors.white,
                            ),
                          ),
                        )
                      : const Icon(Icons.document_scanner),
                  label: Text(
                    _isLoading ? 'Processing...' : 'Extract Document',
                  ),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.green[600],
                    foregroundColor: Colors.white,
                    padding: const EdgeInsets.symmetric(vertical: 16),
                  ),
                ),
              ),

              const SizedBox(height: 20),

              // Results section
              if (_extractionResult != null &&
                  _extractionResult!['success'] == true) ...[
                const Text(
                  'Extracted Document Fields:',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 16),

                // Show each field individually
                ...(_extractionResult!['fields'] as List<dynamic>).map((field) {
                  return _buildFieldCard(
                    field['label']?.toString() ?? 'Unknown',
                    field['value']?.toString() ?? 'No value',
                    (field['confidence'] as double?) ?? 0.0,
                    field['fieldType']?.toString() ?? 'Unknown',
                  );
                }).toList(),

                const SizedBox(height: 20),

                // Summary card
                _buildSummaryCard(),

                const SizedBox(height: 20),

                // API keys reference
                _buildApiKeysCard(),
              ] else if (_extractionResult != null) ...[
                Card(
                  color: Colors.red[50],
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      children: [
                        Icon(Icons.error, color: Colors.red[600], size: 48),
                        const SizedBox(height: 8),
                        const Text(
                          'Extraction Failed',
                          style: TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                            color: Colors.red,
                          ),
                        ),
                        const SizedBox(height: 4),
                        const Text(
                          'Could not extract document data. Please try with a clearer image.',
                          textAlign: TextAlign.center,
                        ),
                      ],
                    ),
                  ),
                ),
              ] else ...[
                Card(
                  child: Container(
                    padding: const EdgeInsets.all(32),
                    child: Column(
                      children: [
                        Icon(
                          Icons.document_scanner,
                          size: 48,
                          color: Colors.grey[400],
                        ),
                        const SizedBox(height: 16),
                        Text(
                          'No document processed yet',
                          style: TextStyle(
                            fontSize: 16,
                            color: Colors.grey[600],
                          ),
                        ),
                        const SizedBox(height: 8),
                        Text(
                          'Capture or select an image to see extraction results',
                          style: TextStyle(
                            fontSize: 14,
                            color: Colors.grey[500],
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ],
                    ),
                  ),
                ),
              ],

              const SizedBox(height: 50),
            ],
          ),
        ),
      ),
    );
  }
}
3
likes
0
points
30
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for extracting data from Ghana vehicle licensing documents using ML Kit text recognition. Simple integration for cross-platform document scanning.

Homepage

Topics

#ocr #document-scanning #ghana #vehicle-documents #ml-kit

Funding

Consider supporting this project:

www.itconsortiumgh.com

License

unknown (license)

Dependencies

flutter, google_mlkit_commons, plugin_platform_interface

More

Packages that depend on itc_scanner

Packages that implement itc_scanner