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

Plugin Flutter para leitura de QR Code no Android usando ML Kit e CameraX. Oferece leitura única em tela cheia e leitura contínua com preview embutido na árvore de widgets (PlatformView), com debounce [...]

example/lib/main.dart

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

import 'embedded_preview_page.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;

  final _plugin = PluginScannerQr();

  @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 (preview embutido) ──────────────────────────────────────

  /// Abre a tela com o preview embutido — a PlatformView.
  ///
  /// Substitui o antigo modo contínuo em Activity de tela cheia, que cobria toda
  /// a UI Flutter.
  Future<void> _abrirPreviewEmbutido({bool hybrid = false}) async {
    // Via key, e não Navigator.of(context): o context deste State é o do
    // MaterialApp, ou seja, está ACIMA do Navigator.
    await _navigatorKey.currentState?.push(
      MaterialPageRoute<void>(
        builder: (_) => EmbeddedPreviewPage(hybridComposition: hybrid),
      ),
    );
  }

  void _showSnackBar(String message) {
    _messengerKey.currentState?.showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

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

  final _navigatorKey = GlobalKey<NavigatorState>();
  final _messengerKey = GlobalKey<ScaffoldMessengerState>();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: _navigatorKey,
      scaffoldMessengerKey: _messengerKey,
      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),

              // ── Botões ───────────────────────────────────────────────────────
              Row(
                children: [
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: _isLoading ? 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 : _abrirPreviewEmbutido,
                      icon: const Icon(Icons.camera_alt),
                      label: const Text('Preview embutido'),
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                        foregroundColor: Colors.white,
                        padding: const EdgeInsets.symmetric(vertical: 12),
                      ),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: 8),

              // Mesma tela, forçando Hybrid Composition — para comparar num
              // aparelho que apresente artefatos com TLHC.
              OutlinedButton.icon(
                onPressed: _isLoading
                    ? null
                    : () => _abrirPreviewEmbutido(hybrid: true),
                icon: const Icon(Icons.layers),
                label: const Text('Preview embutido (Hybrid Composition)'),
              ),

              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
0
points
12
downloads

Publisher

unverified uploader

Weekly Downloads

Plugin Flutter para leitura de QR Code no Android usando ML Kit e CameraX. Oferece leitura única em tela cheia e leitura contínua com preview embutido na árvore de widgets (PlatformView), com debounce e cooldown nativos.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on plugin_scanner_qr

Packages that implement plugin_scanner_qr