simple_native 0.0.18 copy "simple_native: ^0.0.18" to clipboard
simple_native: ^0.0.18 copied to clipboard

A comprehensive Flutter plugin for accessing native device features including Camera (Photos, QR & Barcode), Location, Device Information, and Biometric Authentication.

example/lib/main.dart

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:simple_native/simple_native.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';
  String _locationInfo = 'Unknown';
  String _deviceInfo = 'Unknown';
  String _biometricInfo = 'Unknown';
  final _simpleNativePlugin = SimpleNative();

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

  Future<void> _getLocation() async {
    String locationInfo;
    try {
      final location = await _simpleNativePlugin.getCurrentLocation();
      locationInfo = location?.toString() ?? 'Location not available';
    } on PlatformException {
      locationInfo = 'Failed to get location.';
    }

    if (!mounted) return;

    setState(() {
      _locationInfo = locationInfo;
    });
  }

  Future<void> _getDeviceInfo() async {
    String deviceInfo;
    try {
      final info = await _simpleNativePlugin.getDeviceInfo();
      deviceInfo = info?.toString() ?? 'Device info not available';
    } on PlatformException {
      deviceInfo = 'Failed to get device info.';
    }

    if (!mounted) return;

    setState(() {
      _deviceInfo = deviceInfo;
    });
  }

  Future<void> _testBiometrics() async {
    String info = '';
    try {
      final type = await _simpleNativePlugin.getAvailableBiometric();
      info += 'Type: $type\n';

      if (type != SimpleBiometricType.notSupported) {
        final result = await _simpleNativePlugin.authenticate(
          localizedReason: "Please authenticate to test",
        );
        info += 'Result: $result';
      }
    } on PlatformException catch (e) {
      info = 'Failed to test biometrics: ${e.message}';
    }

    if (!mounted) return;

    setState(() {
      _biometricInfo = info;
    });
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion =
          await _simpleNativePlugin.getPlatformVersion() ??
          'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Plugin example app')),
        body: Builder(
          builder: (context) {
            return Center(
              child: SingleChildScrollView(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text('Running on: $_platformVersion\n'),
                    Text('Location: $_locationInfo\n'),
                    Text('Device info: $_deviceInfo\n'),
                    Text(
                      'Biometric Info:\n$_biometricInfo\n',
                      textAlign: TextAlign.center,
                    ),
                    ElevatedButton(
                      onPressed: _getLocation,
                      child: const Text('Get Location'),
                    ),
                    ElevatedButton(
                      onPressed: _getDeviceInfo,
                      child: const Text('Get Device Info'),
                    ),
                    ElevatedButton(
                      onPressed: _testBiometrics,
                      child: const Text('Test Biometrics'),
                    ),
                    ElevatedButton(
                      onPressed: () =>
                          _simpleNativePlugin.openBiometricSettings(),
                      child: const Text('Open Biometric Settings'),
                    ),
                    const SizedBox(height: 20),
                    ElevatedButton(
                      onPressed: () {
                        Navigator.of(context).push(
                          MaterialPageRoute(
                            builder: (context) => const CameraPage(),
                          ),
                        );
                      },
                      child: const Text('Open Camera'),
                    ),
                  ],
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

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

  @override
  State<CameraPage> createState() => _CameraPageState();
}

class _CameraPageState extends State<CameraPage> {
  SimpleCameraController? _controller;
  final ValueNotifier<Uint8List?> _vnPreviewImage = ValueNotifier(null);
  List<SimpleBarcode> _detectedBarcodes = [];

  bool _freezeOnResult = false;
  bool _isFrozen = false;
  String? _frozenImagePath;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Camera View')),
      body: LayoutBuilder(
        builder: (context, constraints) {
          final width = constraints.maxWidth;
          final height = constraints.maxHeight;

          final scanWindowSize = 250.0;
          final scanWindow = Rect.fromLTWH(
            (width - scanWindowSize) / 2,
            (height - scanWindowSize) / 2,
            scanWindowSize,
            scanWindowSize,
          );

          return Stack(
            children: [
              SimpleCameraView(
                key: ValueKey('camera_qr_$_freezeOnResult'),
                cameraType: SimpleCameraType.qr,
                cameraPosition: SimpleCameraPosition.back,
                freezeOnResult: _freezeOnResult,
                scanWindow: scanWindow,
                onCameraCreated: (controller) {
                  _controller = controller;
                },
                onResult: (result) {
                  if (_isFrozen) return;
                  setState(() {
                    _detectedBarcodes = result.barcodes;
                    if (_freezeOnResult && result.barcodes.isNotEmpty) {
                      _isFrozen = true;
                      _frozenImagePath = result.imagePath;
                    }
                  });
                },
              ),
              if (_isFrozen && _frozenImagePath != null)
                Positioned.fill(
                  child: Image.file(
                    File(_frozenImagePath!),
                    fit: BoxFit.cover,
                  ),
                ),
              // Visual scan window overlay
              Positioned(
                left: scanWindow.left,
                top: scanWindow.top,
                width: scanWindow.width,
                height: scanWindow.height,
                child: Container(
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.yellowAccent, width: 3),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: const Center(
                    child: Text(
                      'Quét trong khung này',
                      style: TextStyle(
                        color: Colors.yellowAccent,
                        fontSize: 12,
                        fontWeight: FontWeight.bold,
                        backgroundColor: Colors.black45,
                      ),
                    ),
                  ),
                ),
              ),
          // Bounding Box Overlays
          for (final barcode in _detectedBarcodes)
            if (barcode.boundingBox != null)
              Positioned(
                left: barcode.boundingBox!.x,
                top: barcode.boundingBox!.y,
                width: barcode.boundingBox!.width,
                height: barcode.boundingBox!.height,
                child: GestureDetector(
                  onTap: () async {
                    if (_isFrozen) {
                      await _controller?.resumeScanning();
                      if (_frozenImagePath != null) {
                        try {
                          final file = File(_frozenImagePath!);
                          if (await file.exists()) {
                            await file.delete();
                          }
                        } catch (e) {
                          // ignore
                        }
                      }
                      setState(() {
                        _isFrozen = false;
                        _detectedBarcodes = [];
                        _frozenImagePath = null;
                      });
                    }
                  },
                  child: Container(
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.greenAccent, width: 3),
                      borderRadius: BorderRadius.circular(8),
                      boxShadow: const [
                        BoxShadow(
                          color: Color(0x4D69F0AE),
                          blurRadius: 6,
                          spreadRadius: 2,
                        ),
                      ],
                    ),
                    child: Stack(
                      clipBehavior: Clip.none,
                      children: [
                        Positioned(
                          top: -24,
                          left: -3,
                          child: Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 6,
                              vertical: 2,
                            ),
                            decoration: BoxDecoration(
                              color: Colors.greenAccent,
                              borderRadius: BorderRadius.circular(4),
                            ),
                            child: Text(
                              barcode.value ?? '',
                              style: const TextStyle(
                                color: Colors.black,
                                fontSize: 10,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),

          Positioned(
            bottom: 30 + MediaQuery.viewPaddingOf(context).bottom,
            left: 0,
            right: 0,
            child: Center(
              child: FloatingActionButton(
                onPressed: () async {
                  final path = await _controller?.takePicture();
                  if (path != null && context.mounted) {
                    showDialog(
                      context: context,
                      builder: (context) =>
                          Dialog.fullscreen(child: Image.file(File(path))),
                    );
                  } else {
                    debugPrint('Không chụp được ảnh');
                  }
                },
                child: const Icon(Icons.camera),
              ),
            ),
          ),
          Positioned(
            top: 20,
            right: 20,
            child: Column(
              children: [
                FloatingActionButton(
                  heroTag: "switch_camera",
                  mini: true,
                  onPressed: () {
                    _controller?.switchCamera();
                  },
                  child: const Icon(Icons.switch_camera),
                ),
                const SizedBox(height: 10),
                ValueListenableBuilder(
                  valueListenable: _vnPreviewImage,
                  builder: (context, value, child) => value == null
                      ? const SizedBox.shrink()
                      : Container(
                          decoration: BoxDecoration(
                            border: Border.all(color: Colors.white, width: 2),
                          ),
                          child: Image.memory(value, width: 100),
                        ),
                ),
              ],
            ),
          ),
          // Top settings panel
          Positioned(
            top: 20,
            left: 20,
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
              decoration: BoxDecoration(
                color: Colors.black.withAlpha(153),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text(
                    'Dừng hình:',
                    style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(width: 4),
                  Switch(
                    value: _freezeOnResult,
                    activeThumbColor: Colors.greenAccent,
                    activeTrackColor: Colors.greenAccent.withAlpha(128),
                    onChanged: (val) {
                      setState(() {
                        _freezeOnResult = val;
                        if (!val) {
                          _isFrozen = false;
                          _controller?.resumeScanning();
                          if (_frozenImagePath != null) {
                            try {
                              final file = File(_frozenImagePath!);
                              if (file.existsSync()) {
                                file.deleteSync();
                              }
                            } catch (e) {
                              // ignore
                            }
                          }
                          _frozenImagePath = null;
                        }
                      });
                    },
                  ),
                ],
              ),
            ),
          ),
          // Status banner when frozen
          if (_isFrozen)
            Positioned(
              bottom: 110 + MediaQuery.viewPaddingOf(context).bottom,
              left: 20,
              right: 20,
              child: Center(
                child: Container(
                  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
                  decoration: BoxDecoration(
                    color: Colors.black.withAlpha(204),
                    borderRadius: BorderRadius.circular(30),
                    border: Border.all(color: Colors.greenAccent.withAlpha(128), width: 1.5),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withAlpha(102),
                        blurRadius: 10,
                        spreadRadius: 2,
                      ),
                    ],
                  ),
                  child: const Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(Icons.info_outline, color: Colors.greenAccent, size: 20),
                      SizedBox(width: 8),
                      Text(
                        'Đã dừng hình. Chạm vào QR để tiếp tục.',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 13,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
        ],
      );
    },
  ),
);
  }
}
3
likes
140
points
178
downloads

Documentation

API reference

Publisher

verified publishertranduc1710.com

Weekly Downloads

A comprehensive Flutter plugin for accessing native device features including Camera (Photos, QR & Barcode), Location, Device Information, and Biometric Authentication.

Homepage

Topics

#camera #location #device-info #native #plugin

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_web_plugins, plugin_platform_interface

More

Packages that depend on simple_native

Packages that implement simple_native