sotaid_pluging 1.1.1+1 copy "sotaid_pluging: ^1.1.1+1" to clipboard
sotaid_pluging: ^1.1.1+1 copied to clipboard

A Flutter plugin for integrating SotaBoost's identity verification service into Flutter applications

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:sotaid_pluging/sotaid_plugin.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:async';
import 'dart:io';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SotaID',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
              home: const MyHomePage(title: 'SotaID'),
    );
  }
}

// Verification Status Page
class VerificationStatusPage extends StatelessWidget {
  final String verificationType;
  final String status;
  final String? message;
  final String? sessionId; // Add session ID parameter
  final bool isSuccess;

  const VerificationStatusPage({
    Key? key,
    required this.verificationType,
    required this.status,
    this.message,
    this.sessionId, // Add session ID parameter
    required this.isSuccess,
  });

  @override
  Widget build(BuildContext context) {
    final bool isPending = status == 'requires_manual_verification';
    final Color statusColor = isSuccess
        ? Colors.green
        : (isPending
            ? Colors.orange
            : Colors.red);
    final IconData overlayIcon = isSuccess
        ? Icons.check_circle
        : (isPending ? Icons.schedule : Icons.cancel);
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            children: [
              // Header with back button
              Row(
                children: [
                  IconButton(
                    onPressed: () => Navigator.of(context).pop(),
                    icon: Icon(Icons.arrow_back, color: Colors.black87),
                  ),
                  SizedBox(width: 16),
                  Text(
                    'Status de Vérification',
                    style: TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                      color: Colors.black87,
                    ),
                  ),
                ],
              ),
              SizedBox(height: 40),
              
              // Status Icon
              Container(
                width: 200,
                height: 200,
                child: Stack(
                  alignment: Alignment.center,
                  children: [
                    Image.network(
                      'https://www.sotaid.com/img/verify/reconnaissance-de-visage.png',
                      width: 200,
                      height: 200,
                      errorBuilder: (context, error, stackTrace) => Icon(Icons.image, size: 80, color: Colors.grey.shade400),
                    ),
                    Positioned(
                      bottom: 1,
                      left: 0,
                      right: 0,
                      child: Container(
                        padding: EdgeInsets.all(2),
                        decoration: BoxDecoration(
                          color: Colors.white,
                          shape: BoxShape.circle,
                          boxShadow: [
                            BoxShadow(
                              color: Colors.black.withOpacity(0.15),
                              blurRadius: 4,
                              offset: Offset(0, 2),
                            ),
                          ],
                        ),
                        child: Icon(
                          overlayIcon,
                          size: 22,
                          color: statusColor,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              SizedBox(height: 32),
              
              // Status Title
              Text(
                "${isSuccess ? 'Identité vérifiée avec succès' : (status == 'requires_manual_verification' ? 'Vérification en cours' : 'Échec de la vérification')}",
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: statusColor,
                ),
                textAlign: TextAlign.center,
              ),

              SizedBox(height: 16),
              
              // Verification Type
              Container(
                padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(20),
                  border: Border.all(color: Colors.blue.shade200),
                ),
                child: Text(
                  verificationType,
                  style: TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.w600,
                    color: Colors.blue.shade700,
                  ),
                ),
              ),
              SizedBox(height: 24),
              
              
              // Action Buttons
              Expanded(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    SizedBox(
                      width: double.infinity,
                      child: ElevatedButton(
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.blue,
                          foregroundColor: Colors.white,
                          padding: EdgeInsets.symmetric(vertical: 16),
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(12),
                          ),
                        ),
                        onPressed: () {
                          Navigator.of(context).popUntil((route) => route.isFirst);
                        },
                        child: Text('Terminer'),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildDetailRow(String label, String value) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(
          width: 80,
          child: Text(
            label,
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w500,
              color: Colors.grey.shade700,
            ),
          ),
        ),
        Expanded(
          child: Text(
            value,
            style: TextStyle(
              fontSize: 14,
              color: Colors.black87,
            ),
          ),
        ),
      ],
    );
  }
}



class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final SotaIdPlugin _sotaIdPlugin = SotaIdPlugin();
  String _bearerToken = 'sotaboost'; // Test token for sandbox
  bool _hasPendingVerification = false; // Track pending verification state

  @override
  void initState() {
    super.initState();
    // Initialize plugin with bearer token (defaults to sandbox mode)
    _sotaIdPlugin.initialize(bearerToken: _bearerToken);
    _checkPendingVerificationOnStartup();
  }
  
  void _startCompleteVerification() async {
    // Check for pending verification first
    await _handlePendingVerificationCheck();
    
    await _sotaIdPlugin.startCompleteVerification(
      context: context,
      onVerificationComplete: (response) async {
        print('=== VERIFICATION RESPONSE (Complete Verification) ===');
        print('Status: ${response.status}');
        print('Message: ${response.message}');
        print('Session ID: ${response.sessionId}');
        print('Success: ${response.status == 'success'}');
        print('Full Response: $response');
        print('=====================================');
        
        // Handle different response types
        if (response.status == 'check_pending_status') {
          // Handle pending status check - this should be handled by the main app
          print('Checking pending verification status for session: ${response.sessionId}');
          if (response.sessionId != null) {
            await _checkVerificationStatus(response.sessionId!);
          }
          return;
        }
        
        // Handle requires_manual_verification status
        if (response.status == 'requires_manual_verification') {
          setState(() {
            _hasPendingVerification = true;
          });
        }
        
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => VerificationStatusPage(
              verificationType: 'Complete Verification',
              status: response.status,
              message: response.message,
              sessionId: response.sessionId, // Pass session ID to status page
              isSuccess: response.status == 'success',
            ),
          ),
        );
      },
      onError: (error) {
        
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => VerificationStatusPage(
              verificationType: 'Complete Verification',
              status: 'error',
              message: error,
              isSuccess: false,
            ),
          ),
        );
      },
    );
  }

    void _startFaceVerification() async {
    // Check for pending verification first
    await _handlePendingVerificationCheck();
    
    // Navigate to image upload page first
    final result = await Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => ImageUploadPage(
          bearerToken: _bearerToken,
        ),
      ),
    );
    
    // Handle result from ImageUploadPage (for pending status check)
    if (result != null && result is Map<String, dynamic>) {
      if (result['action'] == 'check_pending_status') {
        final sessionId = result['session_id'] as String?;
        if (sessionId != null) {
          await _checkVerificationStatus(sessionId);
        }
      } else if (result['action'] == 'requires_manual_verification') {
        // Set pending verification state
        setState(() {
          _hasPendingVerification = true;
        });
      }
    }
  }

  void _startLivenessVerification() async {
    // Check for pending verification first
    await _handlePendingVerificationCheck();
    
    await _sotaIdPlugin.startVerificationFlow(
      context: context,
      verificationType: VerificationType.liveness,
      onVerificationComplete: (response) async {
        
        // Handle different response types
        if (response.status == 'check_pending_status') {
          // Handle pending status check - this should be handled by the main app
          if (response.sessionId != null) {
            await _checkVerificationStatus(response.sessionId!);
          }
          return;
        }
        
        // Handle requires_manual_verification status
        if (response.status == 'requires_manual_verification') {
          setState(() {
            _hasPendingVerification = true;
          });
        }
        
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => VerificationStatusPage(
              verificationType: 'Liveness',
              status: response.status,
              message: response.message,
              sessionId: response.sessionId, // Pass session ID to status page
              isSuccess: response.status == 'success',
            ),
          ),
        );
      },
      onError: (error) {
        
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => VerificationStatusPage(
              verificationType: 'Liveness',
              status: 'error',
              message: error,
              isSuccess: false,
            ),
          ),
        );
      },
    );
  }



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

  /// Check verification status for a pending session
  Future<void> _checkVerificationStatus(String sessionId) async {
    try {
      setState(() {
      });

      final status = await _sotaIdPlugin.getVerificationStatus(sessionId);
      
      setState(() {
      });

      // Show status result
      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (context) => VerificationStatusPage(
            verificationType: 'Status Check',
            status: status.status,
            message: 'Status checked for session: $sessionId',
            sessionId: sessionId,
            isSuccess: status.status == 'verified',
          ),
        ),
      );
      
      // Clear pending state if verification is completed
      if (status.status == 'verified' || status.status == 'failed' || status.status == 'success') {
        await _sotaIdPlugin.clearPendingSession();
        setState(() {
          _hasPendingVerification = false;
        });
      }
    } catch (e) {
      setState(() {
      });
      
      _showSnackBar('Error checking status: $e', backgroundColor: Colors.red);
    }
  }

  /// Check for pending verification on app startup
  Future<void> _checkPendingVerificationOnStartup() async {
    try {
      final pendingSession = await _sotaIdPlugin.getPendingSession();
      if (pendingSession != null) {
        setState(() {
          _hasPendingVerification = true;
        });
        // Show a subtle notification about pending verification
        _showSnackBar(
          'Vous avez une vérification en cours. Cliquez sur l\'icône horloge ou la notification pour vérifier le statut.',
          backgroundColor: Colors.orange,
        );
      }
    } catch (e) {
      print('Error checking pending verification on startup: $e');
    }
  }

  /// Show dialog when user tries to start verification with pending manual verification
  Future<bool> _showPendingVerificationDialog() async {
    return await showDialog<bool>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return AlertDialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          title: Row(
            children: [
              Icon(
                Icons.schedule,
                color: Colors.orange,
                size: 24,
              ),
              SizedBox(width: 12),
              Text(
                'Vérification en cours',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'Vous avez une vérification en attente de traitement manuel.',
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.black87,
                ),
              ),
              SizedBox(height: 16),
              Text(
                'Que souhaitez-vous faire ?',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w600,
                  color: Colors.grey.shade700,
                ),
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(true); // Continue with new verification
              },
              child: Text(
                'Nouvelle vérification',
                style: TextStyle(
                  color: Colors.grey.shade600,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
            ElevatedButton(
              onPressed: () async {
                Navigator.of(context).pop(); // Close dialog first
                // Get the pending session and check its status directly
                final pendingSession = await _sotaIdPlugin.getPendingSession();
                if (pendingSession != null && pendingSession['session_id'] != null) {
                  await _checkVerificationStatus(pendingSession['session_id']);
                }
              },
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.orange,
                foregroundColor: Colors.white,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
              ),
              child: Text(
                'Vérifier le statut',
                style: TextStyle(
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ],
        );
      },
    ) ?? false;
  }

  /// Check if there's a pending verification and handle accordingly
  Future<void> _handlePendingVerificationCheck() async {
    if (_hasPendingVerification) {
      final result = await _showPendingVerificationDialog();
      if (result == true) {
        // User chose to continue with new verification, clear the pending state
        await _sotaIdPlugin.clearPendingSession();
        setState(() {
          _hasPendingVerification = false;
        });
      }
      // If result is false or null, the user either cancelled or chose to check status
      // The status check is now handled directly in the button onPressed callback
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
        actions: [
          if (_hasPendingVerification)
            IconButton(
              onPressed: () async {
                final pendingSession = await _sotaIdPlugin.getPendingSession();
                if (pendingSession != null && pendingSession['session_id'] != null) {
                  await _checkVerificationStatus(pendingSession['session_id']);
                }
              },
              icon: Icon(
                Icons.schedule,
                color: Colors.orange,
              ),
              tooltip: 'Vérifier le statut en cours',
            ),
        ],
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: Column(
              children: [
                // Logo with better styling
                Container(
                  padding: EdgeInsets.all(16),
                  child: Image.asset(
                    'assets/sotaid_logo.png',
                    height: 40,
                    fit: BoxFit.contain,
                  ),
                ),
                SizedBox(height: 5),
                // Instructional text
                Text(
                  'Veuillez sélectionner l\'un des trois processus pour commencer',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 14,
                    color: Colors.black87,
                    height: 1.4,
                    fontWeight: FontWeight.w500,
                  ),
                ),
                SizedBox(height: 20),
                
                // Middle section with verification options
                // Full KYC Option
                _buildVerificationOption(
                  title: 'Full KYC',
                  description: 'Vous permet de réaliser notre processus entier de KYC. Acquisition et validation d\'authenticité de document, facematching avec le titulaire de la pièce avec la réalisation de la détection du vivant.',
                  onTap: _startCompleteVerification,
                  customIconPath: 'assets/full_kyc_icon.svg',
                ),
                SizedBox(height: 10),
                
                // Face Matching Option
                _buildVerificationOption(
                  title: 'Face Matching',
                  description: 'Réaliser le facematch entre un document de référence et une nouvelle vidéo acquise. La détection du vivant y est intégrée également.',
                  onTap: _startFaceVerification,
                  customIconPath: 'assets/face_macthin_icon.svg',
                ),
                SizedBox(height: 10),
                
                // Liveness Option
                _buildVerificationOption(
                  title: 'Liveness',
                  description: 'Preuve de vie, détection de vivant sur une vidéo reçue.',
                  onTap: _startLivenessVerification,
                  customIconPath: 'assets/liveness_icon.svg',
                ),
                SizedBox(height: 40),
                
                // Bottom section with powered by text
                Text(
                  'Powered by SotaBoost',
                  style: TextStyle(
                    fontSize: 14,
                    color: Colors.grey.shade500,
                  ),
                ),
                SizedBox(height: 40),
              ],
            ),
          ),
        ),
      ),
    );
  }

    Widget _buildVerificationOption({
    IconData? icon,
    required String title,
    required String description,
    required VoidCallback onTap,
    String? customIconPath,
  }) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        width: double.infinity,
        padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(12),
          border: Border.all(
            color: Colors.grey.shade200,
            width: 1,
          ),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.05),
              blurRadius: 8,
              offset: Offset(0, 2),
            ),
          ],
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                // Icon with title badge
                Container(
                  width: 100,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      // Icon container
                      Container(
                        width: 40,
                        height: 40,
                        decoration: BoxDecoration(
                          color: Colors.grey.shade50,
                          borderRadius: BorderRadius.circular(12),
                          border: Border.all(color: Colors.grey.shade200),
                        ),
                        child: Stack(
                          children: [
                            Center(
                              child: customIconPath != null
                                  ? SvgPicture.asset(
                                      customIconPath,
                                      width: 28,
                                      height: 28,
                                      fit: BoxFit.contain,
                                    )
                                  : Icon(
                                      icon ?? Icons.person,
                                      size: 28,
                                      color: Colors.black87,
                                    ),
                            ),
                            // Pending verification indicator
                            if (_hasPendingVerification)
                              Positioned(
                                top: -2,
                                right: -2,
                                child: Container(
                                  width: 12,
                                  height: 12,
                                  decoration: BoxDecoration(
                                    color: Colors.orange,
                                    shape: BoxShape.circle,
                                    border: Border.all(color: Colors.white, width: 2),
                                  ),
                                  child: Icon(
                                    Icons.schedule,
                                    size: 6,
                                    color: Colors.white,
                                  ),
                                ),
                              ),
                          ],
                        ),
                      ),
                      SizedBox(height: 8),
                      // Title badge
                      Container(
                        padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                        decoration: BoxDecoration(
                          color: Colors.blue.shade400,
                          borderRadius: BorderRadius.circular(8),
                          border: Border.all(color: Colors.blue.shade300, width: 1),
                        ),
                        child: Text(
                          title,
                          style: TextStyle(
                            fontSize: 8,
                            fontWeight: FontWeight.w700,
                            color: Colors.white,
                          ),
                          textAlign: TextAlign.center,
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                    ],
                  ),
                ),
                SizedBox(width: 14),
              ],
            ),
            SizedBox(height: 4),
            // Description with info icon
            Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Icon(
                  Icons.info_outline,
                  size: 16,
                  color: Colors.grey.shade600,
                ),
                SizedBox(width: 8),
                Expanded(
                  child: Text(
                    description,
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.grey.shade700,
                      height: 1.4,
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// Image Upload Page for Face Matching
class ImageUploadPage extends StatefulWidget {
  final String bearerToken;

  const ImageUploadPage({
    super.key,
    required this.bearerToken,
  });

  @override
  State<ImageUploadPage> createState() => _ImageUploadPageState();
}

class _ImageUploadPageState extends State<ImageUploadPage> {
  File? _selectedImage;
  bool _isLoading = false;
  final ImagePicker _picker = ImagePicker();
  final SotaIdPlugin _sotaIdPlugin = SotaIdPlugin();

  Future<void> _pickImage() async {
    try {
      setState(() {
        _isLoading = true;
      });

      // Show option to pick from gallery or camera
      final XFile? image = await showModalBottomSheet<XFile?>(
        context: context,
        builder: (BuildContext context) {
          return SafeArea(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                ListTile(
                  leading: Icon(Icons.photo_library),
                  title: Text('Galerie'),
                  onTap: () async {
                    Navigator.pop(context, await _picker.pickImage(
                      source: ImageSource.gallery,
                      imageQuality: 80,
                    ));
                  },
                ),
                ListTile(
                  leading: Icon(Icons.camera_alt),
                  title: Text('Caméra'),
                  onTap: () async {
                    Navigator.pop(context, await _picker.pickImage(
                      source: ImageSource.camera,
                      imageQuality: 80,
                    ));
                  },
                ),
                ListTile(
                  leading: Icon(Icons.close),
                  title: Text('Annuler'),
                  onTap: () => Navigator.pop(context),
                ),
              ],
            ),
          );
        },
      );

      if (image != null) {
        setState(() {
          _selectedImage = File(image.path);
          _isLoading = false;
        });
        _showSnackBar('Image sélectionnée avec succès', backgroundColor: Colors.green);
      } else {
        setState(() {
          _isLoading = false;
        });
      }
    } catch (e) {
      setState(() {
        _isLoading = false;
      });
      _showSnackBar('Erreur lors de la sélection de l\'image: $e', backgroundColor: Colors.red);
    }
  }

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

  void _startFaceVerification() async {
    if (_selectedImage == null) {
      _showSnackBar('Veuillez d\'abord sélectionner une image de référence', backgroundColor: Colors.orange);
      return;
    }

    await _sotaIdPlugin.startFaceVerification(
      context: context,
      documentFile: _selectedImage!,
      onVerificationComplete: (response) async {
        
        // Handle different response types
        if (response.status == 'check_pending_status') {
          // Handle pending status check - this should be handled by the main app
          if (response.sessionId != null) {
            // Navigate back to main page to handle status check
            Navigator.of(context).pop({
              'action': 'check_pending_status',
              'session_id': response.sessionId,
            });
            return;
          }
          return;
        }
        
        // Handle requires_manual_verification status
        if (response.status == 'requires_manual_verification') {
          // Navigate back to main page with pending verification info
          Navigator.of(context).pop({
            'action': 'requires_manual_verification',
            'session_id': response.sessionId,
          });
          return;
        }
        
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => VerificationStatusPage(
              verificationType: 'Face Matching',
              status: response.status,
              message: response.message,
              sessionId: response.sessionId, // Pass session ID to status page
              isSuccess: response.status == 'success',
            ),
          ),
        );
      },
      onError: (error) {
        
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => VerificationStatusPage(
              verificationType: 'Face Matching',
              status: 'error',
              message: error,
              isSuccess: false,
            ),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            children: [
              // Header
              Row(
                children: [
                  IconButton(
                    onPressed: () => Navigator.of(context).pop(),
                    icon: Icon(Icons.arrow_back, color: Colors.black87),
                  ),
                  SizedBox(width: 16),
                  Text(
                    'Upload Image de Référence',
                    style: TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                      color: Colors.black87,
                    ),
                  ),
                ],
              ),
              SizedBox(height: 32),

              // Instructions
              Container(
                width: double.infinity,
                padding: EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: Colors.blue.shade200),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(Icons.info_outline, color: Colors.blue.shade700, size: 20),
                        SizedBox(width: 8),
                        Text(
                          'Instructions',
                          style: TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.w600,
                            color: Colors.blue.shade700,
                          ),
                        ),
                      ],
                    ),
                    SizedBox(height: 12),
                    Text(
                      'Pour le Face Matching, vous devez d\'abord fournir une image de référence (document d\'identité) qui sera comparée avec votre visage lors de la vérification.',
                      style: TextStyle(
                        fontSize: 14,
                        color: Colors.blue.shade700,
                        height: 1.4,
                      ),
                    ),
                  ],
                ),
              ),
              SizedBox(height: 32),

              // Image Upload Area
              Expanded(
                child: Container(
                  width: double.infinity,
                  decoration: BoxDecoration(
                    color: Colors.grey.shade50,
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: _selectedImage != null ? Colors.green.shade300 : Colors.grey.shade300,
                      width: 2,
                    ),
                  ),
                  child: _isLoading
                      ? Center(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              CircularProgressIndicator(),
                              SizedBox(height: 16),
                              Text(
                                'Chargement...',
                                style: TextStyle(
                                  fontSize: 16,
                                  color: Colors.grey.shade600,
                                ),
                              ),
                            ],
                          ),
                        )
                      : _selectedImage != null
                          ? Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Icon(
                                  Icons.check_circle,
                                  color: Colors.green,
                                  size: 60,
                                ),
                                SizedBox(height: 16),
                                Text(
                                  'Image sélectionnée',
                                  style: TextStyle(
                                    fontSize: 18,
                                    fontWeight: FontWeight.w600,
                                    color: Colors.green,
                                  ),
                                ),
                                SizedBox(height: 8),
                                Text(
                                  'Document de référence prêt',
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.grey.shade600,
                                  ),
                                ),
                                SizedBox(height: 24),
                                TextButton(
                                  onPressed: _pickImage,
                                  child: Text(
                                    'Changer l\'image',
                                    style: TextStyle(
                                      color: Colors.blue,
                                      fontWeight: FontWeight.w600,
                                    ),
                                  ),
                                ),
                              ],
                            )
                          : Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Icon(
                                  Icons.cloud_upload_outlined,
                                  size: 80,
                                  color: Colors.grey.shade400,
                                ),
                                SizedBox(height: 16),
                                Text(
                                  'Aucune image sélectionnée',
                                  style: TextStyle(
                                    fontSize: 18,
                                    fontWeight: FontWeight.w600,
                                    color: Colors.grey.shade600,
                                  ),
                                ),
                                SizedBox(height: 8),
                                Text(
                                  'Tapez pour sélectionner une image',
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.grey.shade500,
                                  ),
                                ),
                                SizedBox(height: 24),
                                ElevatedButton.icon(
                                  onPressed: _pickImage,
                                  icon: Icon(Icons.upload_file),
                                  label: Text('Sélectionner Image'),
                                  style: ElevatedButton.styleFrom(
                                    backgroundColor: Colors.blue,
                                    foregroundColor: Colors.white,
                                    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                                    shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(8),
                                    ),
                                  ),
                                ),
                              ],
                            ),
                ),
              ),
              SizedBox(height: 32),

              // Start Verification Button
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: _selectedImage != null ? _startFaceVerification : null,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.blue,
                    foregroundColor: Colors.white,
                    padding: EdgeInsets.symmetric(vertical: 16),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                    disabledBackgroundColor: Colors.grey.shade300,
                  ),
                  child: Text(
                    'Commencer la Vérification',
                    style: TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
125
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for integrating SotaBoost's identity verification service into Flutter applications

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

camera, dio, dropdown_button2, flutter, http, http_parser, image, image_picker, plugin_platform_interface, shared_preferences, video_player

More

Packages that depend on sotaid_pluging

Packages that implement sotaid_pluging