biometry 1.0.3 copy "biometry: ^1.0.3" to clipboard
biometry: ^1.0.3 copied to clipboard

A Flutter package for integrating with the Biometry API to provide biometric authentication and verification services.

example/lib/main.dart

import 'dart:developer' as dev;
import 'dart:io';

import 'package:biometry/biometry.dart';
import 'package:biometry/biometry_scanner_widget.dart';
import 'package:flutter/material.dart';

// theme constants
class AppTheme {
  static const primaryColor = Color(0xFF1A2132);
  static const accentColor = Color.fromARGB(255, 105, 124, 164);
  static const successColor = Color(0xFF4CAF50);
  static const errorColor = Color(0xFFEF5350);
  static const textColorSecondary = Color(0xFF6B7280);
  static const textColorDisabled = Color(0xFFB0BEC5);

  static final containerDecoration = BoxDecoration(
    color: primaryColor,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.2),
        blurRadius: 4,
        offset: Offset(0, 2),
      ),
    ],
  );
}

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

class BiometryApp extends StatelessWidget {
  const BiometryApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const BiometryHomePage(),
    );
  }
}

class BiometryHomePage extends StatefulWidget {
  const BiometryHomePage({Key? key}) : super(key: key);

  @override
  BiometryHomePageState createState() => BiometryHomePageState();
}

class BiometryHomePageState extends State<BiometryHomePage>
    with SingleTickerProviderStateMixin {
  final _formKey = GlobalKey<FormState>();
  final TextEditingController _tokenController = TextEditingController();
  final TextEditingController _fullNameController = TextEditingController();

  File? _capturedVideo;
  String _result = '';
  bool _isProcessing = false;
  Biometry? _biometry;
  bool _isBiometryInitialized = false;
  late AnimationController _animationController;
  bool _showResultsPanel = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
  }

  @override
  void dispose() {
    _tokenController.dispose();
    _fullNameController.dispose();
    _animationController.dispose();
    super.dispose();
  }

  Future<void> _initializeBiometry() async {
    final token = _tokenController.text.trim();
    if (token.isEmpty) {
      _showSnackBar('Please enter a valid token.');
      return;
    }
    final fullName = _fullNameController.text.trim();
    if (fullName.isEmpty) {
      _showSnackBar('Please enter your full name.');
      return;
    }
    try {
      _biometry = await Biometry.initialize(token: token, fullName: fullName);
      setState(() {
        _isBiometryInitialized = true;
      });
      _showSnackBar('Biometry initialized successfully!');
    } catch (e) {
      _showSnackBar('Failed to initialize Biometry: $e');
    }
  }

  // method to handle biometric operations
  Future<void> _executeBiometricOperation({
    required Future<dynamic> Function() operationCallback,
    required String successMessage,
    required String errorMessage,
    bool requireVideo = false,
  }) async {
    if (!_formKey.currentState!.validate()) {
      _showSnackBar('Please provide all required information.');
      return;
    }
    if (_biometry == null || !_isBiometryInitialized) {
      _showSnackBar('Biometry is not initialized.');
      return;
    }
    if (requireVideo && _capturedVideo == null) {
      _showSnackBar('Please scan a person first.');
      return;
    }
    setState(() {
      _isProcessing = true;
      _result = '';
      _animationController.forward();
    });
    try {
      final response = await operationCallback();
      setState(() {
        if (response.statusCode == 200 || response.statusCode == 201) {
          _result = '$successMessage\n${response.body}';
        } else {
          _result = '$errorMessage: ${response.statusCode}\n${response.body}';
        }
        _showResultsPanel = true;
      });
    } on HttpException catch (e) {
      setState(() {
        _result = 'Network error: $e';
        _showResultsPanel = true;
      });
    } on FormatException catch (e) {
      setState(() {
        _result = 'Invalid response format: $e';
        _showResultsPanel = true;
      });
    } catch (e) {
      setState(() {
        _result = 'Unexpected error: $e';
        _showResultsPanel = true;
      });
    } finally {
      setState(() {
        _isProcessing = false;
        _animationController.reverse();
      });
    }
  }

  Future<void> _allowConsent() async {
    await _executeBiometricOperation(
      operationCallback: () => _biometry!.allowConsent(consent: true),
      successMessage: 'Consent allowed successfully!',
      errorMessage: 'Failed to allow consent',
    );
  }

  Future<void> _allowStorageConsent() async {
    await _executeBiometricOperation(
      operationCallback: () => _biometry!.allowStorageConsent(consent: true),
      successMessage: 'Storage Consent allowed successfully!',
      errorMessage: 'Failed to allow storage consent',
    );
  }

  Future<void> _endSession() async {
    await _executeBiometricOperation(
      operationCallback: () => _biometry!.endSession(),
      successMessage: 'Session ended successfully!',
      errorMessage: 'Failed to end session',
    );
    // Clear captured video for security
    if (_capturedVideo != null) {
      await _capturedVideo!.delete();
      setState(() {
        _capturedVideo = null;
      });
    }
  }

  Future<void> _processVideo() async {
    await _executeBiometricOperation(
      operationCallback: () =>
          _biometry!.processVideo(videoFile: _capturedVideo!),
      successMessage: 'Video processed successfully!',
      errorMessage: 'Failed to process video',
      requireVideo: true,
    );
  }

  Future<void> _processDocAuth() async {
    await _executeBiometricOperation(
      operationCallback: () => _biometry!.docAuth(),
      successMessage: 'Document authenticated successfully!',
      errorMessage: 'Failed to authenticate document',
    );
  }

  Future<void> _faceMatch() async {
    await _executeBiometricOperation(
      operationCallback: () => _biometry!.faceMatch(),
      successMessage: 'Face match processed successfully!',
      errorMessage: 'Failed to match face',
    );
  }

  Future<void> _enrollVoice() async {
    await _executeBiometricOperation(
      operationCallback: () =>
          _biometry!.enrolVoice(videoFile: _capturedVideo!),
      successMessage: 'Voice enrolled successfully!',
      errorMessage: 'Failed to enroll voice',
      requireVideo: true,
    );
  }

  Future<void> _enrollFace() async {
    await _executeBiometricOperation(
      operationCallback: () => _biometry!.enrolFace(),
      successMessage: 'Face enrolled successfully!',
      errorMessage: 'Failed to enroll face',
    );
  }

  Future<void> _scanPerson() async {
    if (!_formKey.currentState!.validate()) {
      _showSnackBar('Please provide all required information.');
      return;
    }
    if (_biometry == null || !_isBiometryInitialized) {
      _showSnackBar('Biometry is not initialized.');
      return;
    }
    final phrase = _biometry!.phraseAsIntList;
    final captured = await showModalBottomSheet<File?>(
      backgroundColor: AppTheme.primaryColor,
      context: context,
      isScrollControlled: true,
      builder: (context) {
        return Column(
          children: [
            const Spacer(),
            Text(
              "Please Speak Only The Following Numbers",
              style: TextStyle(
                color: AppTheme.textColorSecondary,
                fontWeight: FontWeight.normal,
              ),
              semanticsLabel: 'Instruction to speak the displayed numbers',
            ),
            const SizedBox(height: 10),
            Row(
              children: [
                const Spacer(),
                Icon(Icons.spatial_audio_off_rounded,
                    color: AppTheme.accentColor),
                const SizedBox(width: 10),
                Text(
                  phrase,
                  style: TextStyle(
                    color: AppTheme.accentColor,
                    fontWeight: FontWeight.bold,
                  ),
                  semanticsLabel: 'Numbers to speak: ${phrase}',
                ),
                const Spacer(),
              ],
            ),
            const SizedBox(height: 20),
            SizedBox(
              child: BiometryScannerWidget(
                phrase: phrase,
                onCapture: (capturedVideo) {
                  dev.log('Captured video: ${capturedVideo.path}');
                  Navigator.pop(context, capturedVideo);
                },
              ),
            ),
            const SizedBox(height: 20),
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: Text(
                'Cancel',
                style: TextStyle(color: AppTheme.errorColor, fontSize: 16),
                semanticsLabel: 'Cancel scanning',
              ),
            ),
            const Spacer(),
          ],
        );
      },
    );
    if (captured != null) {
      setState(() {
        _capturedVideo = captured;
        _showResultsPanel = true;
      });
      _showSnackBar('Person scanned successfully!');
    } else {
      _showSnackBar('Person scanning cancelled or failed.');
    }
  }

  void _showSnackBar(String message) {
    final isSuccess = message.contains('successfully');

    ScaffoldMessenger.of(context)
      ..removeCurrentSnackBar()
      ..showSnackBar(
        SnackBar(
          content: Text(
            message,
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.w600,
            ),
            semanticsLabel: message,
          ),
          duration: const Duration(seconds: 3),
          behavior: SnackBarBehavior.floating,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(12),
          ),
          backgroundColor:
              isSuccess ? AppTheme.successColor : AppTheme.errorColor,
        ),
      );
  }

  Widget _buildActionButton({
    required String label,
    required VoidCallback? onPressed,
    IconData? icon,
    String? tooltip,
  }) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Tooltip(
        message: tooltip ?? '',
        child: ElevatedButton.icon(
          onPressed: onPressed,
          icon: icon != null
              ? Icon(icon,
                  size: 20,
                  color: onPressed != null
                      ? Colors.white
                      : AppTheme.textColorDisabled)
              : const SizedBox.shrink(),
          label: Text(
            label,
            style: TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w600,
              color:
                  onPressed != null ? Colors.white : AppTheme.textColorDisabled,
            ),
          ),
          style: ElevatedButton.styleFrom(
            minimumSize: const Size(double.infinity, 50),
            backgroundColor: onPressed != null
                ? AppTheme.accentColor
                : AppTheme.textColorSecondary,
            padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
            shape:
                RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
            elevation: 2,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Biometric Authentication'),
        backgroundColor: AppTheme.primaryColor,
        foregroundColor: Colors.white,
        actions: [
          IconButton(
            icon: Icon(
              Icons.info,
              color: _showResultsPanel ? AppTheme.successColor : Colors.white,
            ),
            tooltip: _showResultsPanel ? 'Hide Results' : 'Show Results',
            onPressed: () {
              setState(() {
                _showResultsPanel = !_showResultsPanel;
              });
            },
          ),
        ],
      ),
      backgroundColor: AppTheme.primaryColor,
      body: Stack(
        children: [
          SafeArea(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(20),
              child: Form(
                key: _formKey,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Container(
                      decoration: AppTheme.containerDecoration,
                      padding: const EdgeInsets.all(16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Initialize Session',
                            style: TextStyle(
                              color: Colors.white,
                              fontWeight: FontWeight.bold,
                              fontSize: 20,
                            ),
                            semanticsLabel: 'Initialize Session Section',
                          ),
                          const SizedBox(height: 16),
                          TextFormField(
                            controller: _tokenController,
                            decoration: InputDecoration(
                              labelText: 'Token',
                              labelStyle:
                                  TextStyle(color: AppTheme.textColorSecondary),
                              prefixIcon: Icon(Icons.vpn_key,
                                  color: AppTheme.accentColor),
                              filled: true,
                              fillColor: const Color(0xFF1F2937),
                              border: OutlineInputBorder(
                                borderRadius: BorderRadius.circular(12),
                                borderSide: BorderSide.none,
                              ),
                              contentPadding: const EdgeInsets.symmetric(
                                  horizontal: 16, vertical: 12),
                            ),
                            style: const TextStyle(color: Colors.white),
                            validator: (value) {
                              if (value == null || value.trim().isEmpty) {
                                return 'Please enter your token';
                              }
                              return null;
                            },
                            textInputAction: TextInputAction.next,
                          ),
                          const SizedBox(height: 12),
                          TextFormField(
                            controller: _fullNameController,
                            decoration: InputDecoration(
                              labelText: 'Full Name',
                              labelStyle:
                                  TextStyle(color: AppTheme.textColorSecondary),
                              prefixIcon: Icon(Icons.person,
                                  color: AppTheme.accentColor),
                              filled: true,
                              fillColor: const Color(0xFF1F2937),
                              border: OutlineInputBorder(
                                borderRadius: BorderRadius.circular(12),
                                borderSide: BorderSide.none,
                              ),
                              contentPadding: const EdgeInsets.symmetric(
                                  horizontal: 16, vertical: 12),
                            ),
                            style: const TextStyle(color: Colors.white),
                            validator: (value) {
                              if (value == null || value.trim().isEmpty) {
                                return 'Please enter your full name';
                              }
                              return null;
                            },
                            textInputAction: TextInputAction.done,
                          ),
                          const SizedBox(height: 16),
                          _buildActionButton(
                            label: 'Initialize Biometry',
                            onPressed: _initializeBiometry,
                            icon: Icons.start,
                            tooltip: 'Start the biometric session',
                          ),
                        ],
                      ),
                    ),
                    const SizedBox(height: 20),
                    if (_isBiometryInitialized) ...[
                      Container(
                        decoration: AppTheme.containerDecoration,
                        padding: const EdgeInsets.all(16),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Consent Management',
                              style: TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                                fontSize: 20,
                              ),
                              semanticsLabel: 'Consent Management Section',
                            ),
                            const SizedBox(height: 8),
                            const Text(
                              'Consents are required for enrollment.',
                              style: TextStyle(
                                color: Color(0xFF9CA3AF),
                                fontSize: 14,
                              ),
                            ),
                            const SizedBox(height: 16),
                            _buildActionButton(
                              label: 'Consent to Use Biometrics',
                              icon: Icons.check_circle,
                              onPressed: _allowConsent,
                              tooltip: 'Allow use of biometric data',
                            ),
                            _buildActionButton(
                              label: 'Consent to Store Biometrics',
                              icon: Icons.storage,
                              onPressed: _allowStorageConsent,
                              tooltip: 'Allow storage of biometric data',
                            ),
                          ],
                        ),
                      ),
                      const SizedBox(height: 20),
                      Container(
                        decoration: AppTheme.containerDecoration,
                        padding: const EdgeInsets.all(16),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Biometric Actions',
                              style: TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                                fontSize: 20,
                              ),
                              semanticsLabel: 'Biometric Actions Section',
                            ),
                            const SizedBox(height: 16),
                            _buildActionButton(
                              label: 'Scan Person',
                              onPressed: _scanPerson,
                              icon: Icons.camera_alt,
                              tooltip: 'Capture biometric video',
                            ),
                            if (_biometry?.phraseAsIntList != null) ...[
                              const SizedBox(height: 12),
                              Row(
                                children: [
                                  Icon(Icons.format_quote,
                                      color: AppTheme.accentColor),
                                  const SizedBox(width: 8),
                                  Expanded(
                                    child: Text(
                                      'Phrase: ${_biometry!.phraseAsIntList}',
                                      style: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.bold,
                                        color:
                                            Color.fromARGB(255, 119, 144, 199),
                                      ),
                                      semanticsLabel:
                                          'Phrase: ${_biometry!.phraseAsIntList}',
                                    ),
                                  ),
                                ],
                              ),
                            ],
                            const SizedBox(height: 16),
                            _buildActionButton(
                              label: 'Process Video',
                              onPressed:
                                  _capturedVideo != null ? _processVideo : null,
                              icon: Icons.videocam,
                              tooltip:
                                  'Process the captured video (auto-enrolls if consents given)',
                            ),
                            _buildActionButton(
                              label: 'Document Auth',
                              onPressed: _processDocAuth,
                              icon: Icons.document_scanner,
                              tooltip: 'Authenticate a document',
                            ),
                            _buildActionButton(
                              label: 'Enroll Face',
                              onPressed: _enrollFace,
                              icon: Icons.face,
                              tooltip: 'Enroll facial biometrics',
                            ),
                            _buildActionButton(
                              label: 'Enroll Voice',
                              onPressed:
                                  _capturedVideo != null ? _enrollVoice : null,
                              icon: Icons.mic,
                              tooltip: 'Enroll voice biometrics',
                            ),
                            _buildActionButton(
                              label: 'Face Match',
                              onPressed: _faceMatch,
                              icon: Icons.compare,
                              tooltip: 'Match face against document',
                            ),
                          ],
                        ),
                      ),
                      const SizedBox(height: 20),
                      Container(
                        decoration: AppTheme.containerDecoration,
                        padding: const EdgeInsets.all(16),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Session Management',
                              style: TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                                fontSize: 20,
                              ),
                              semanticsLabel: 'Session Management Section',
                            ),
                            const SizedBox(height: 16),
                            _buildActionButton(
                              label: 'End Session',
                              icon: Icons.stop,
                              onPressed: _endSession,
                              tooltip: 'End the biometric session',
                            ),
                          ],
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            ),
          ),
          if (_showResultsPanel &&
              (_capturedVideo != null ||
                  _biometry?.faceImagePath != null ||
                  _result.isNotEmpty))
            Positioned(
              bottom: 20,
              left: 20,
              right: 20,
              child: Container(
                constraints: BoxConstraints(
                  maxHeight: MediaQuery.of(context).size.height * 0.6,
                  minHeight: 100,
                ),
                decoration: BoxDecoration(
                  color: const Color(0xFF2A3447),
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.2),
                      blurRadius: 4,
                      offset: const Offset(0, 2),
                    ),
                  ],
                ),
                child: SingleChildScrollView(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          const Text(
                            'Results',
                            style: TextStyle(
                              color: Colors.white,
                              fontWeight: FontWeight.bold,
                              fontSize: 20,
                            ),
                            semanticsLabel: 'Results Panel',
                          ),
                          IconButton(
                            icon: const Icon(Icons.close, color: Colors.white),
                            onPressed: () {
                              setState(() {
                                _showResultsPanel = false;
                              });
                            },
                          ),
                        ],
                      ),
                      const SizedBox(height: 16),
                      if (_capturedVideo != null)
                        ListTile(
                          leading: Icon(Icons.video_file,
                              color: AppTheme.accentColor),
                          title: Text(
                            'Video: ${_capturedVideo!.path.split('/').last}',
                            style: const TextStyle(
                                fontSize: 16, color: Colors.white),
                            semanticsLabel:
                                'Captured video: ${_capturedVideo!.path.split('/').last}',
                          ),
                        ),
                      if (_biometry?.faceImagePath?.isNotEmpty ?? false)
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Captured Face:',
                              style: TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.w600,
                                color: Colors.white,
                              ),
                              semanticsLabel: 'Captured Face Image',
                            ),
                            const SizedBox(height: 8),
                            ClipRRect(
                              borderRadius: BorderRadius.circular(12),
                              child: Image.file(
                                File(_biometry!.faceImagePath!),
                                height: 150,
                                width: double.infinity,
                                fit: BoxFit.cover,
                                semanticLabel: 'Captured face image',
                              ),
                            ),
                          ],
                        ),
                      if (_result.isNotEmpty)
                        ListTile(
                          leading: Icon(
                            _result.contains('successfully')
                                ? Icons.check_circle
                                : Icons.error,
                            color: _result.contains('successfully')
                                ? AppTheme.successColor
                                : AppTheme.errorColor,
                          ),
                          title: Text(
                            _result,
                            style: TextStyle(
                              fontSize: 16,
                              color: _result.contains('successfully')
                                  ? AppTheme.successColor
                                  : AppTheme.errorColor,
                            ),
                            semanticsLabel: _result,
                          ),
                        ),
                    ],
                  ),
                ),
              ),
            ),
          if (_isProcessing)
            FadeTransition(
              opacity: _animationController,
              child: Center(
                child: CircularProgressIndicator(
                  valueColor: AlwaysStoppedAnimation<Color>(
                      const Color.fromARGB(255, 255, 255, 255)),
                  semanticsLabel: 'Processing',
                ),
              ),
            ),
        ],
      ),
    );
  }
}
1
likes
150
points
193
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for integrating with the Biometry API to provide biometric authentication and verification services.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

audio_session, camera, camera_platform_interface, device_info_plus, flutter, flutter_doc_scanner, http, http_parser, path_provider, recase, speech_to_text, uuid, video_compress

More

Packages that depend on biometry