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

PlatformAndroid

Plugin Flutter para leitura de QR Code no Android com suporte a modo único e modo contínuo, usando ML Kit e CameraX. Inclui debounce nativo, cooldown configurável e helper Dart de alto nível (Continuo [...]

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:plugin_scanner_qr/plugin_scanner_qr.dart';

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

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

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

class _MyAppState extends State<MyApp> {
  // ── Estado geral ────────────────────────────────────────────────────────────
  String _platformVersion = 'Carregando…';
  String _qrCodeResult = 'Nenhum QR code escaneado ainda';
  bool _isLoading = false;

  // ── Opções de câmera (modo único e contínuo) ─────────────────────────────────
  bool _useFrontCamera = false;
  bool _enableTorch = false;
  String _orientation = 'portrait';
  bool _enableAutoFocus = true;
  double _zoomLevel = 0.0;
  bool _enableZoom = true;
  double _brightness = 0.5;
  double _contrast = 0.5;
  double _exposureCompensation = 0.0;

  // ── Opções exclusivas do modo contínuo ──────────────────────────────────────
  int _cooldownSeconds = 2;

  // ── Leitura contínua ────────────────────────────────────────────────────────
  final _plugin = PluginScannerQr();
  ContinuousQrScanner? _scanner;
  bool _continuousRunning = false;

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

  Future<void> _initPlatformState() async {
    String version;
    try {
      version = await _plugin.getPlatformVersion() ?? 'Versão desconhecida';
    } on PlatformException {
      version = 'Falha ao obter versão da plataforma';
    }
    if (!mounted) return;
    setState(() => _platformVersion = version);
  }

  // ── Leitura única ────────────────────────────────────────────────────────────

  Future<void> _scanQRCode() async {
    setState(() => _isLoading = true);
    try {
      if (!await _plugin.isCameraAvailable()) {
        _showSnackBar('Câmera não disponível no dispositivo');
        return;
      }
      if (_useFrontCamera && !await _plugin.isFrontCameraAvailable()) {
        _showSnackBar('Câmera frontal não disponível');
        return;
      }
      if (!await _plugin.requestCameraPermission()) {
        _showSnackBar('Permissão de câmera necessária');
        return;
      }

      final result = await _plugin.scanQRCodeWithOptions(
        useFrontCamera: _useFrontCamera,
        orientation: _orientation,
        enableTorch: _enableTorch,
        enableAutoFocus: _enableAutoFocus,
        zoomLevel: _zoomLevel,
        enableZoom: _enableZoom,
        brightness: _brightness,
        contrast: _contrast,
        exposureCompensation: _exposureCompensation,
      );

      setState(() {
        _qrCodeResult =
            result ?? 'Escaneamento cancelado pelo usuário';
      });
    } on PlatformException catch (e) {
      setState(() => _qrCodeResult = 'Erro: ${e.message}');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // ── Leitura contínua ─────────────────────────────────────────────────────────

  Future<void> _startContinuousScan() async {
    if (_continuousRunning) return;

    setState(() => _isLoading = true);

    final scanner = ContinuousQrScanner(
      options: ContinuousScanOptions(
        useFrontCamera: _useFrontCamera,
        orientation: _orientation,
        enableTorch: _enableTorch,
        cooldown: Duration(seconds: _cooldownSeconds),
      ),
    );

    try {
      await scanner.start();
    } on Exception catch (e) {
      setState(() {
        _isLoading = false;
        _qrCodeResult = 'Erro ao iniciar scanner: $e';
      });
      await scanner.dispose();
      return;
    }

    _scanner = scanner;
    setState(() {
      _isLoading = false;
      _continuousRunning = true;
    });

    scanner.scans.listen(
      (result) {
        if (!mounted) return;
        setState(() => _qrCodeResult = result.value);
        ScaffoldMessenger.maybeOf(context)?.showSnackBar(
          SnackBar(
            content: Text('QR lido: ${result.value}'),
            duration: const Duration(seconds: 2),
          ),
        );
      },
      onDone: () {
        if (mounted) setState(() => _continuousRunning = false);
      },
    );
  }

  Future<void> _stopContinuousScan() async {
    await _scanner?.dispose();
    _scanner = null;
    if (mounted) setState(() => _continuousRunning = false);
  }

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

  @override
  void dispose() {
    _scanner?.dispose();
    super.dispose();
  }

  // ── UI ───────────────────────────────────────────────────────────────────────

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Scanner QR Code'),
          backgroundColor: Colors.blue,
          foregroundColor: Colors.white,
        ),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Center(
                child: Column(
                  children: [
                    const Icon(Icons.qr_code_scanner, size: 80, color: Colors.blue),
                    const SizedBox(height: 8),
                    Text(
                      'Plugin Scanner QR Code',
                      style: Theme.of(context).textTheme.headlineSmall,
                      textAlign: TextAlign.center,
                    ),
                    Text(
                      'Plataforma: $_platformVersion',
                      style: Theme.of(context).textTheme.bodyMedium,
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 16),

              // ── Configurações de câmera ──────────────────────────────────────
              _SectionCard(
                title: 'Configurações da Câmera',
                children: [
                  SwitchListTile(
                    title: const Text('Câmera frontal'),
                    value: _useFrontCamera,
                    onChanged: (v) => setState(() => _useFrontCamera = v),
                    contentPadding: EdgeInsets.zero,
                  ),
                  SwitchListTile(
                    title: const Text('Flash'),
                    value: _enableTorch,
                    onChanged: (v) => setState(() => _enableTorch = v),
                    contentPadding: EdgeInsets.zero,
                  ),
                  Row(
                    children: [
                      const Text('Orientação: '),
                      const SizedBox(width: 8),
                      DropdownButton<String>(
                        value: _orientation,
                        items: const [
                          DropdownMenuItem(
                            value: 'portrait',
                            child: Text('Vertical'),
                          ),
                          DropdownMenuItem(
                            value: 'landscape',
                            child: Text('Horizontal'),
                          ),
                        ],
                        onChanged: (v) =>
                            setState(() => _orientation = v ?? 'portrait'),
                      ),
                    ],
                  ),
                  SwitchListTile(
                    title: const Text('Foco automático'),
                    value: _enableAutoFocus,
                    onChanged: (v) => setState(() => _enableAutoFocus = v),
                    contentPadding: EdgeInsets.zero,
                  ),
                  SwitchListTile(
                    title: const Text('Zoom manual'),
                    value: _enableZoom,
                    onChanged: (v) => setState(() => _enableZoom = v),
                    contentPadding: EdgeInsets.zero,
                  ),
                  if (_enableZoom)
                    _LabeledSlider(
                      label: 'Zoom',
                      value: _zoomLevel,
                      min: 0,
                      max: 1,
                      divisions: 10,
                      display: '${(_zoomLevel * 100).round()}%',
                      onChanged: (v) => setState(() => _zoomLevel = v),
                    ),
                  _LabeledSlider(
                    label: 'Brilho',
                    value: _brightness,
                    min: 0,
                    max: 1,
                    divisions: 10,
                    display: '${(_brightness * 100).round()}%',
                    onChanged: (v) => setState(() => _brightness = v),
                  ),
                  _LabeledSlider(
                    label: 'Contraste',
                    value: _contrast,
                    min: 0,
                    max: 1,
                    divisions: 10,
                    display: '${(_contrast * 100).round()}%',
                    onChanged: (v) => setState(() => _contrast = v),
                  ),
                  _LabeledSlider(
                    label: 'Exposição',
                    value: _exposureCompensation,
                    min: -2,
                    max: 2,
                    divisions: 20,
                    display: _exposureCompensation.toStringAsFixed(1),
                    onChanged: (v) =>
                        setState(() => _exposureCompensation = v),
                  ),
                ],
              ),

              const SizedBox(height: 12),

              // ── Opções do modo contínuo ──────────────────────────────────────
              _SectionCard(
                title: 'Modo Contínuo',
                children: [
                  _LabeledSlider(
                    label: 'Cooldown após leitura',
                    value: _cooldownSeconds.toDouble(),
                    min: 0,
                    max: 10,
                    divisions: 10,
                    display: _cooldownSeconds == 0
                        ? 'sem pausa'
                        : '${_cooldownSeconds}s',
                    onChanged: (v) =>
                        setState(() => _cooldownSeconds = v.round()),
                  ),
                  const Text(
                    'Tempo que o scanner pausa automaticamente após ler um QR.\n'
                    'Use 0 para leitura ininterrupta.',
                    style: TextStyle(fontSize: 12, color: Colors.grey),
                  ),
                ],
              ),

              const SizedBox(height: 16),

              // ── Botões ───────────────────────────────────────────────────────
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed:
                          (_isLoading || _continuousRunning) ? null : _scanQRCode,
                      icon: _isLoading
                          ? const SizedBox(
                              width: 18,
                              height: 18,
                              child: CircularProgressIndicator(
                                strokeWidth: 2,
                                color: Colors.white,
                              ),
                            )
                          : const Icon(Icons.qr_code_scanner),
                      label: const Text('Leitura única'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.blue,
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(vertical: 12),
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _isLoading
                          ? null
                          : (_continuousRunning
                              ? _stopContinuousScan
                              : _startContinuousScan),
                      icon: _isLoading
                          ? const SizedBox(
                              width: 18,
                              height: 18,
                              child: CircularProgressIndicator(
                                strokeWidth: 2,
                                color: Colors.white,
                              ),
                            )
                          : Icon(
                              _continuousRunning
                                  ? Icons.stop
                                  : Icons.camera_alt,
                            ),
                      label: Text(
                        _continuousRunning ? 'Parar' : 'Leitura contínua',
                      ),
                      style: ElevatedButton.styleFrom(
                        backgroundColor:
                            _continuousRunning ? Colors.red : Colors.green,
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(vertical: 12),
                      ),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 16),

              // ── Resultado ────────────────────────────────────────────────────
              _SectionCard(
                title: 'Resultado',
                children: [
                  SelectableText(
                    _qrCodeResult,
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// ── Widgets auxiliares ────────────────────────────────────────────────────────

class _SectionCard extends StatelessWidget {
  final String title;
  final List<Widget> children;

  const _SectionCard({required this.title, required this.children});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.grey[50],
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: Colors.grey[300]!),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: Theme.of(context)
                .textTheme
                .titleMedium
                ?.copyWith(fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          ...children,
        ],
      ),
    );
  }
}

class _LabeledSlider extends StatelessWidget {
  final String label;
  final double value;
  final double min;
  final double max;
  final int divisions;
  final String display;
  final ValueChanged<double> onChanged;

  const _LabeledSlider({
    required this.label,
    required this.value,
    required this.min,
    required this.max,
    required this.divisions,
    required this.display,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        SizedBox(width: 90, child: Text(label)),
        Expanded(
          child: Slider(
            value: value,
            min: min,
            max: max,
            divisions: divisions,
            label: display,
            onChanged: onChanged,
          ),
        ),
        SizedBox(width: 50, child: Text(display, textAlign: TextAlign.end)),
      ],
    );
  }
}
0
likes
140
points
79
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Plugin Flutter para leitura de QR Code no Android com suporte a modo único e modo contínuo, usando ML Kit e CameraX. Inclui debounce nativo, cooldown configurável e helper Dart de alto nível (ContinuousQrScanner).

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on plugin_scanner_qr

Packages that implement plugin_scanner_qr