Skip to main content

sprintocr 0.1.0 copy "sprintocr: ^0.1.0" to clipboard
sprintocr: ^0.1.0 copied to clipboard

Native OCR text recognition and ID card parser for Android (ML Kit) and iOS (Apple Vision). Standalone package with zero location or mapping dependencies.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:sprintocr/sprintocr.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SprintOCR Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0066FF)),
        useMaterial3: true,
      ),
      home: const OcrExampleHomePage(),
    );
  }
}

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

  @override
  State<OcrExampleHomePage> createState() => _OcrExampleHomePageState();
}

class _OcrExampleHomePageState extends State<OcrExampleHomePage> {
  final SprintOcr _ocr = SprintOcr();
  final ImagePicker _picker = ImagePicker();

  List<XFile> _selectedImages = [];
  bool _isLoading = false;
  String _extractedText = '';
  RecognizedTextResult? _recognizedResult;
  int _selectedTab = 0; // 0: Clean Text, 1: Structured Blocks

  Future<void> _pickImage(ImageSource source) async {
    try {
      final XFile? photo = await _picker.pickImage(source: source);
      if (photo == null) return;

      setState(() {
        _selectedImages = [photo];
        _isLoading = true;
        _extractedText = '';
        _recognizedResult = null;
      });

      await _processImage(photo.path);
    } catch (e) {
      _showSnackBar('Error picking image: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _pickMultipleImages() async {
    try {
      final List<XFile> images = await _picker.pickMultiImage();
      if (images.isEmpty) return;

      setState(() {
        _selectedImages = images;
        _isLoading = true;
        _extractedText = '';
        _recognizedResult = null;
      });

      final paths = images.map((e) => e.path).toList();
      final text = await _ocr.extractTextFromMultipleImages(paths);

      setState(() {
        _extractedText = text;
      });
    } catch (e) {
      _showSnackBar('Error picking images: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _processImage(String imagePath) async {
    final result = await _ocr.recognizeText(imagePath);

    setState(() {
      _recognizedResult = result;
      _extractedText = result.text;
    });
  }

  void _copyToClipboard(String text) {
    if (text.isEmpty) return;
    Clipboard.setData(ClipboardData(text: text));
    _showSnackBar('Text copied to clipboard!');
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Native OCR Text Recognition'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Action Buttons Card
            Card(
              elevation: 2,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Select Image for Text Recognition',
                      style: Theme.of(context).textTheme.titleMedium?.copyWith(
                            fontWeight: FontWeight.bold,
                          ),
                    ),
                    const SizedBox(height: 12),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: () => _pickImage(ImageSource.camera),
                            icon: const Icon(Icons.camera_alt),
                            label: const Text('Camera'),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: ElevatedButton.icon(
                            onPressed: () => _pickImage(ImageSource.gallery),
                            icon: const Icon(Icons.photo_library),
                            label: const Text('Gallery'),
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    SizedBox(
                      width: double.infinity,
                      child: OutlinedButton.icon(
                        onPressed: _pickMultipleImages,
                        icon: const Icon(Icons.collections),
                        label: const Text('Pick Multiple Images (Multi-Page)'),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),

            // Image Preview Area
            if (_selectedImages.isNotEmpty) ...[
              Text(
                'Selected Image(s) (${_selectedImages.length})',
                style: Theme.of(context).textTheme.titleSmall?.copyWith(
                      fontWeight: FontWeight.bold,
                    ),
              ),
              const SizedBox(height: 8),
              SizedBox(
                height: 140,
                child: ListView.separated(
                  scrollDirection: Axis.horizontal,
                  itemCount: _selectedImages.length,
                  separatorBuilder: (_, _) => const SizedBox(width: 8),
                  itemBuilder: (context, index) {
                    return ClipRRect(
                      borderRadius: BorderRadius.circular(8),
                      child: Image.file(
                        File(_selectedImages[index].path),
                        width: 140,
                        height: 140,
                        fit: BoxFit.cover,
                      ),
                    );
                  },
                ),
              ),
              const SizedBox(height: 16),
            ],

            // Loading Indicator or Results
            if (_isLoading)
              const Padding(
                padding: EdgeInsets.symmetric(vertical: 32.0),
                child: Center(
                  child: Column(
                    children: [
                      CircularProgressIndicator(),
                      SizedBox(height: 12),
                      Text('Extracting text using native OCR engine...'),
                    ],
                  ),
                ),
              )
            else if (_extractedText.isNotEmpty || _recognizedResult != null) ...[
              // Tab Selector
              SegmentedButton<int>(
                segments: const [
                  ButtonSegment(
                    value: 0,
                    label: Text('Extracted Text'),
                    icon: Icon(Icons.text_snippet),
                  ),
                  ButtonSegment(
                    value: 1,
                    label: Text('Blocks & Lines'),
                    icon: Icon(Icons.account_tree),
                  ),
                ],
                selected: {_selectedTab},
                onSelectionChanged: (set) {
                  setState(() => _selectedTab = set.first);
                },
              ),
              const SizedBox(height: 12),

              if (_selectedTab == 0) ...[
                // Clean Text Output
                Card(
                  elevation: 1,
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            Text(
                              'Clean OCR Text Result',
                              style: Theme.of(context)
                                  .textTheme
                                  .titleMedium
                                  ?.copyWith(fontWeight: FontWeight.bold),
                            ),
                            IconButton(
                              icon: const Icon(Icons.copy),
                              tooltip: 'Copy Text',
                              onPressed: () => _copyToClipboard(_extractedText),
                            ),
                          ],
                        ),
                        const Divider(),
                        SelectableText(
                          _extractedText.isEmpty
                              ? 'No text detected in the selected image.'
                              : _extractedText,
                          style: const TextStyle(height: 1.5, fontSize: 15),
                        ),
                      ],
                    ),
                  ),
                ),
              ] else ...[
                // Structured Blocks View
                if (_recognizedResult == null ||
                    _recognizedResult!.blocks.isEmpty)
                  const Card(
                    child: Padding(
                      padding: EdgeInsets.all(16.0),
                      child: Text('No structured text blocks recognized.'),
                    ),
                  )
                else
                  ListView.builder(
                    shrinkWrap: true,
                    physics: const NeverScrollableScrollPhysics(),
                    itemCount: _recognizedResult!.blocks.length,
                    itemBuilder: (context, blockIndex) {
                      final block = _recognizedResult!.blocks[blockIndex];
                      return Card(
                        margin: const EdgeInsets.only(bottom: 8),
                        child: ExpansionTile(
                          title: Text(
                            'Block #${blockIndex + 1}: ${block.text}',
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: const TextStyle(fontWeight: FontWeight.w600),
                          ),
                          subtitle: Text(
                            'Lines: ${block.lines.length}'
                            '${block.boundingBox != null ? " | Bounds: ${block.boundingBox}" : ""}',
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.grey.shade700,
                            ),
                          ),
                          children: block.lines.map((line) {
                            return ListTile(
                              dense: true,
                              title: Text(line.text),
                              subtitle: Text(
                                'Confidence: ${line.confidence != null ? (line.confidence! * 100).toStringAsFixed(1) : "N/A"}%'
                                '${line.boundingBox != null ? " | ${line.boundingBox}" : ""}',
                                style: const TextStyle(fontSize: 11),
                              ),
                            );
                          }).toList(),
                        ),
                      );
                    },
                  ),
              ],
            ] else
              Card(
                color: Colors.grey.shade100,
                child: Padding(
                  padding: const EdgeInsets.all(24.0),
                  child: Column(
                    children: [
                      Icon(Icons.document_scanner,
                          size: 48, color: Colors.grey.shade500),
                      const SizedBox(height: 12),
                      Text(
                        'No image selected yet.',
                        style: TextStyle(color: Colors.grey.shade700),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        'Take a photo or pick an image from gallery to start text extraction.',
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey.shade600,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}
0
likes
150
points
56
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Native OCR text recognition and ID card parser for Android (ML Kit) and iOS (Apple Vision). Standalone package with zero location or mapping dependencies.

Homepage

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on sprintocr

Packages that implement sprintocr