crossplatform_flutter_vidvdockit 1.0.0 copy "crossplatform_flutter_vidvdockit: ^1.0.0" to clipboard
crossplatform_flutter_vidvdockit: ^1.0.0 copied to clipboard

VIDVDockitSdk Flutter plugin

example/lib/main.dart

import 'dart:convert';
import 'package:crossplatform_flutter_vidvdockit/crossplatform_flutter_vidvdockit.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'DocKit Plugin Test',
    theme: ThemeData(
      primarySwatch: Colors.blue,
      useMaterial3: true,
    ),
    home: const HomePage(),
  );
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _dockitResult = 'No result yet';
  bool _isLoading = false;

  // ─────────────────────────────────────────────────────────────────────────────
  // Server Configuration
  // ─────────────────────────────────────────────────────────────────────────────
  final String baseURL = 'https://www.valifystage.com';
  final String bundleKey = 'ad44eb94ca6747beaf99eef02407221f';
  final String userName = 'mobileusername';
  final String password = '3(kbjFLq%Pa3ruLS';
  final String clientID = 'geQx0U7NXMWMeMSMbGPp8fEfwZLgBP5AMIQd8MPX';
  final String clientSecret =
      'eIUsIZGRdoRJprRmOzfX995ioJi94L5zlMbnUWbf5AzLtxjodvdkJI06QgFJ2qKZRY2dGla28CQKkVZOywPhVa4Ormc6B16Ku1UP718sgJrTVjNffSzn2oIfjTBs7oym';

  // ─────────────────────────────────────────────────────────────────────────────
  // Document Type
  // ─────────────────────────────────────────────────────────────────────────────
  String _selectedDocument = 'passport';

  // ─────────────────────────────────────────────────────────────────────────────
  // Language
  // ─────────────────────────────────────────────────────────────────────────────
  String _selectedLanguage = 'en';

  // ─────────────────────────────────────────────────────────────────────────────
  // Capture Mode
  // ─────────────────────────────────────────────────────────────────────────────
  String _captureMode = 'automatic';
  final TextEditingController _autoAfterSecondsController =
  TextEditingController(text: '6');

  // ─────────────────────────────────────────────────────────────────────────────
  // Basic Options
  // ─────────────────────────────────────────────────────────────────────────────
  bool _reviewData = true;
  bool _collectUserInfo = false;
  bool _previewCapturedImage = false;

  // ─────────────────────────────────────────────────────────────────────────────
  // Capture Only Mode
  // ─────────────────────────────────────────────────────────────────────────────
  bool _captureOnlyMode = false;

  // ─────────────────────────────────────────────────────────────────────────────
  // Advanced Options (Extras)
  // ─────────────────────────────────────────────────────────────────────────────
  bool _advancedConfidence = false;
  bool _professionAnalysis = false;
  bool _documentVerificationPlus = false;
  bool _documentLiveness = false;

  // ─────────────────────────────────────────────────────────────────────────────
  // UI Customization
  // ─────────────────────────────────────────────────────────────────────────────
  bool _includePrimaryColor = false;
  final TextEditingController _primaryColorController =
  TextEditingController(text: '#FFA500');
  bool _disableLogo = false;

  // ─────────────────────────────────────────────────────────────────────────────
  // Headers
  // ─────────────────────────────────────────────────────────────────────────────
  bool _includeReferenceUserId = false;
  final TextEditingController _referenceUserIdController =
  TextEditingController(text: '');

  @override
  void dispose() {
    _autoAfterSecondsController.dispose();
    _primaryColorController.dispose();
    _referenceUserIdController.dispose();
    super.dispose();
  }

  Future<String?> _getToken() async {
    final url = '$baseURL/api/o/token/';
    final resp = await http.post(
      Uri.parse(url),
      headers: {'Content-Type': 'application/x-www-form-urlencoded'},
      body:
      'username=$userName&password=$password&client_id=$clientID&client_secret=$clientSecret&grant_type=password',
    );
    if (resp.statusCode == 200) {
      return json.decode(resp.body)['access_token'] as String;
    }
    return null;
  }

  Map<String, dynamic> _buildArgs(String token) {
    final args = <String, dynamic>{
      // ─── Required ───
      "base_url": baseURL,
      "access_token": token,
      "bundle_key": bundleKey,

      // ─── Language ───
      "language": _selectedLanguage,

      // ─── Document Type ───
      "document_type": _selectedDocument,

      // ─── Capture Mode ───
      "capture_mode": _captureMode,
      if (_captureMode == 'auto_after')
        "auto_after_seconds":
        int.tryParse(_autoAfterSecondsController.text) ?? 6,

      // ─── Basic Options ───
      "review_data": _reviewData,
      "collect_user_info": _collectUserInfo,
      "preview_captured_image": _previewCapturedImage,

      // ─── Capture Only Mode ───
      "capture_only_mode": _captureOnlyMode,

      // ─── Advanced Options (Extras) ───
      "advanced_confidence": _advancedConfidence,
      "profession_analysis": _professionAnalysis,
      "document_verification_plus": _documentVerificationPlus,
      "document_liveness": _documentLiveness,

      // ─── UI Customization ───
      "disable_logo": _disableLogo,
      if (_includePrimaryColor) "primary_color": _primaryColorController.text,

      // ─── Headers ───
      if (_includeReferenceUserId &&
          _referenceUserIdController.text.isNotEmpty)
        "headers": {
          "X-Valify-reference-userid": _referenceUserIdController.text,
        },
    };

    return args;
  }

  Future<void> _startDocKit() async {
    setState(() {
      _dockitResult = 'Loading…';
      _isLoading = true;
    });

    try {
      final token = await _getToken();
      if (token == null) {
        setState(() {
          _dockitResult = 'Failed to get token';
          _isLoading = false;
        });
        return;
      }

      final args = _buildArgs(token);

      // Log the args for debugging
      debugPrint('DocKit Args: ${const JsonEncoder.withIndent('  ').convert(args)}');

      final sdkresult = await VIDVDocKit().start(args);
      if (sdkresult == null) {
        setState(() {
          _dockitResult = 'No result';
          _isLoading = false;
        });
        return;
      }

      // Parse defensively
      final Map<String, dynamic> data =
      json.decode(sdkresult) as Map<String, dynamic>;
      final result =
          (data['result'] as Map?)?.cast<String, dynamic>() ?? const {};
      final dataSection =
          (result['data'] as Map?)?.cast<String, dynamic>() ?? const {};
      final captures =
          (dataSection['captures'] as Map?)?.cast<String, dynamic>() ?? const {};

      // Truncate base64 values only if String
      final truncated = <String, dynamic>{};
      captures.forEach((k, v) {
        if (v is String && v.length > 10) {
          truncated[k] =
          '${v.substring(0, 10)}.... [Base64 truncated for display]';
        } else {
          truncated[k] = v;
        }
      });

      // Put back
      final mutable = Map<String, dynamic>.from(data);
      final mutableResult = Map<String, dynamic>.from(result);
      final mutableData = Map<String, dynamic>.from(dataSection);
      mutableData['captures'] = truncated;
      mutableResult['data'] = mutableData;
      mutable['result'] = mutableResult;

      setState(() {
        _dockitResult = const JsonEncoder.withIndent('  ').convert(mutable);
        _isLoading = false;
      });
    } on PlatformException catch (e) {
      setState(() {
        _dockitResult = 'Platform error: ${e.code} ${e.message}';
        _isLoading = false;
      });
    } catch (e, st) {
      debugPrint('Parse error: $e\n$st');
      setState(() {
        _dockitResult = 'Parse error: $e';
        _isLoading = false;
      });
    }
  }

  void _showSampleConfig() {
    const sampleConfig = '''
// ═══════════════════════════════════════════════════════════════════════════════
// VIDV DocKit Flutter Plugin - Complete Configuration Reference
// ═══════════════════════════════════════════════════════════════════════════════

final args = <String, dynamic>{
  // ─────────────────────────────────────────────────────────────────────────────
  // REQUIRED PARAMETERS
  // ─────────────────────────────────────────────────────────────────────────────
  
  "base_url": "https://api.example.com",      // API base URL
  "access_token": "your_oauth_token",         // OAuth access token
  "bundle_key": "your_bundle_key",            // Bundle key from dashboard
  
  // ─────────────────────────────────────────────────────────────────────────────
  // LANGUAGE
  // ─────────────────────────────────────────────────────────────────────────────
  
  "language": "en",                           // "en" | "ar" | "fr"
  
  // ─────────────────────────────────────────────────────────────────────────────
  // DOCUMENT TYPE
  // ─────────────────────────────────────────────────────────────────────────────
  
  "document_type": "passport",                // "passport" | "egyNID" | "tunNID" | "dzaNID"
  
  // ─────────────────────────────────────────────────────────────────────────────
  // CAPTURE MODE
  // ─────────────────────────────────────────────────────────────────────────────
  
  "capture_mode": "automatic",                // "manual" | "automatic" | "auto_after"
  "auto_after_seconds": 6,                    // Required when capture_mode is "auto_after"
                                              // Minimum value: 1
  
  // ─────────────────────────────────────────────────────────────────────────────
  // BASIC OPTIONS
  // ─────────────────────────────────────────────────────────────────────────────
  
  "review_data": true,                        // Show review screen after capture
  "collect_user_info": false,                 // Collect additional user information
  "preview_captured_image": false,            // Show preview of captured image
  
  // ─────────────────────────────────────────────────────────────────────────────
  // CAPTURE ONLY MODE
  // ─────────────────────────────────────────────────────────────────────────────
  
  "capture_only_mode": false,                 // Only capture, skip OCR processing
  
  // ─────────────────────────────────────────────────────────────────────────────
  // ADVANCED OPTIONS (EXTRAS)
  // ─────────────────────────────────────────────────────────────────────────────
  
  "advanced_confidence": false,               // Enable advanced confidence scoring
  "profession_analysis": false,               // Enable profession analysis
  "document_verification_plus": false,        // Enable enhanced document verification
  "document_liveness": false,                 // Enable document liveness detection
  
  // Alternative: Pass extras as a map
  "extras": {
    "advancedConfidence": true,
    "professionAnalysis": true,
    "documentVerificationPlus": true,
    "documentLiveness": true,
    "customKey": "customValue",               // Any additional custom extras
  },
  
  // ─────────────────────────────────────────────────────────────────────────────
  // UI CUSTOMIZATION
  // ─────────────────────────────────────────────────────────────────────────────
  
  "primary_color": "#FFA500",                 // Hex color string (e.g., "#FF5722")
  "disable_logo": false,                      // Hide the logo
  "custom_logo": "base64_encoded_image",      // Custom logo as base64 string
  
  // ─────────────────────────────────────────────────────────────────────────────
  // HEADERS
  // ─────────────────────────────────────────────────────────────────────────────
  
  "headers": {
    "X-Valify-reference-userid": "user123",   // Reference user ID
    "Custom-Header": "value",                 // Any custom headers
  },
  
  // ─────────────────────────────────────────────────────────────────────────────
  // SSL CERTIFICATE (Optional)
  // ─────────────────────────────────────────────────────────────────────────────
  
  "ssl_certificate": "base64_encoded_cert",   // SSL certificate as base64 PEM/DER
};

// ─────────────────────────────────────────────────────────────────────────────
// USAGE
// ─────────────────────────────────────────────────────────────────────────────

final result = await VIDVDocKit().start(args);

// ─────────────────────────────────────────────────────────────────────────────
// RESPONSE STRUCTURE
// ─────────────────────────────────────────────────────────────────────────────

// Success Response:
{
  "state": "SUCCESS",
  "result": {
    "data": {
      "captures": {
        "front": "base64_image...",
        "back": "base64_image..."
      },
      "extractedData": {
        "first_name": "John",
        "last_name": "Doe",
        "birth_date": "1990-01-01",
        // ... other fields
      }
    }
  }
}

// Exit Response (user cancelled):
{
  "state": "EXIT",
  "result": { ... }
}

// Error Response:
{
  "state": "ERROR",
  "result": {
    "message": "Error description"
  }
}

// Service Failure Response:
{
  "state": "FAILURE",
  "result": {
    "message": "Service failure description"
  }
}
''';

    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Configuration Reference'),
        content: SizedBox(
          width: double.maxFinite,
          child: SingleChildScrollView(
            child: SelectableText(
              sampleConfig,
              style: const TextStyle(
                fontFamily: 'monospace',
                fontSize: 11,
              ),
            ),
          ),
        ),
        actions: [
          TextButton(
            onPressed: () {
              Clipboard.setData(ClipboardData(text: sampleConfig));
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('Copied to clipboard!')),
              );
            },
            child: const Text('Copy'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('Close'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext ctx) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('DocKit Plugin Test'),
        actions: [
          IconButton(
            icon: const Icon(Icons.code),
            tooltip: 'Show Sample Config',
            onPressed: _showSampleConfig,
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // ═══════════════════════════════════════════════════════════════════
            // DOCUMENT TYPE
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('Document Type'),
            _buildRadioOption('Passport', 'passport', _selectedDocument,
                    (val) => setState(() => _selectedDocument = val!)),
            _buildRadioOption('Egyptian NID', 'egyNID', _selectedDocument,
                    (val) => setState(() => _selectedDocument = val!)),
            _buildRadioOption('Tunisian NID', 'tunNID', _selectedDocument,
                    (val) => setState(() => _selectedDocument = val!)),
            _buildRadioOption('Algerian NID', 'dzaNID', _selectedDocument,
                    (val) => setState(() => _selectedDocument = val!)),

            const Divider(height: 32),

            // ═══════════════════════════════════════════════════════════════════
            // LANGUAGE
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('Language'),
            _buildRadioOption('English', 'en', _selectedLanguage,
                    (val) => setState(() => _selectedLanguage = val!)),
            _buildRadioOption('Arabic', 'ar', _selectedLanguage,
                    (val) => setState(() => _selectedLanguage = val!)),
            _buildRadioOption('French', 'fr', _selectedLanguage,
                    (val) => setState(() => _selectedLanguage = val!)),

            const Divider(height: 32),

            // ═══════════════════════════════════════════════════════════════════
            // CAPTURE MODE
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('Capture Mode'),
            _buildRadioOption('Manual', 'manual', _captureMode,
                    (val) => setState(() => _captureMode = val!)),
            _buildRadioOption('Automatic', 'automatic', _captureMode,
                    (val) => setState(() => _captureMode = val!)),
            _buildRadioOption('Auto After (seconds)', 'auto_after', _captureMode,
                    (val) => setState(() => _captureMode = val!)),
            if (_captureMode == 'auto_after')
              Padding(
                padding: const EdgeInsets.only(left: 48, right: 16, bottom: 8),
                child: TextField(
                  controller: _autoAfterSecondsController,
                  keyboardType: TextInputType.number,
                  decoration: const InputDecoration(
                    labelText: 'Seconds',
                    hintText: 'e.g., 6',
                    border: OutlineInputBorder(),
                    isDense: true,
                  ),
                ),
              ),

            const Divider(height: 32),

            // ═══════════════════════════════════════════════════════════════════
            // BASIC OPTIONS
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('Basic Options'),
            _buildCheckbox('Review Data', _reviewData,
                    (val) => setState(() => _reviewData = val!)),
            _buildCheckbox('Collect User Info', _collectUserInfo,
                    (val) => setState(() => _collectUserInfo = val!)),
            _buildCheckbox('Preview Captured Image', _previewCapturedImage,
                    (val) => setState(() => _previewCapturedImage = val!)),
            _buildCheckbox('Capture Only Mode', _captureOnlyMode,
                    (val) => setState(() => _captureOnlyMode = val!)),

            const Divider(height: 32),

            // ═══════════════════════════════════════════════════════════════════
            // ADVANCED OPTIONS (EXTRAS)
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('Advanced Options (Extras)'),
            _buildCheckbox('Advanced Confidence', _advancedConfidence,
                    (val) => setState(() => _advancedConfidence = val!)),
            _buildCheckbox('Profession Analysis', _professionAnalysis,
                    (val) => setState(() => _professionAnalysis = val!)),
            _buildCheckbox('Document Verification Plus', _documentVerificationPlus,
                    (val) => setState(() => _documentVerificationPlus = val!)),
            _buildCheckbox('Document Liveness', _documentLiveness,
                    (val) => setState(() => _documentLiveness = val!)),

            const Divider(height: 32),

            // ═══════════════════════════════════════════════════════════════════
            // UI CUSTOMIZATION
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('UI Customization'),
            _buildCheckbox('Disable Logo', _disableLogo,
                    (val) => setState(() => _disableLogo = val!)),
            _buildCheckbox('Custom Primary Color', _includePrimaryColor,
                    (val) => setState(() => _includePrimaryColor = val!)),
            if (_includePrimaryColor)
              Padding(
                padding: const EdgeInsets.only(left: 48, right: 16, bottom: 8),
                child: TextField(
                  controller: _primaryColorController,
                  decoration: const InputDecoration(
                    labelText: 'Color Hex',
                    hintText: '#FFA500',
                    border: OutlineInputBorder(),
                    isDense: true,
                  ),
                ),
              ),

            const Divider(height: 32),

            // ═══════════════════════════════════════════════════════════════════
            // HEADERS
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('Headers'),
            _buildCheckbox('Include Reference User ID', _includeReferenceUserId,
                    (val) => setState(() => _includeReferenceUserId = val!)),
            if (_includeReferenceUserId)
              Padding(
                padding: const EdgeInsets.only(left: 48, right: 16, bottom: 8),
                child: TextField(
                  controller: _referenceUserIdController,
                  decoration: const InputDecoration(
                    labelText: 'Reference User ID',
                    hintText: 'user123',
                    border: OutlineInputBorder(),
                    isDense: true,
                  ),
                ),
              ),

            const SizedBox(height: 24),

            // ═══════════════════════════════════════════════════════════════════
            // ACTION BUTTONS
            // ═══════════════════════════════════════════════════════════════════
            Row(
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _isLoading ? null : _startDocKit,
                    icon: _isLoading
                        ? const SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                        : const Icon(Icons.play_arrow),
                    label: Text(_isLoading ? 'Loading...' : 'Start DocKit'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(vertical: 16),
                    ),
                  ),
                ),
                const SizedBox(width: 12),
                ElevatedButton.icon(
                  onPressed: _showSampleConfig,
                  icon: const Icon(Icons.description),
                  label: const Text('Docs'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
                  ),
                ),
              ],
            ),

            const SizedBox(height: 24),

            // ═══════════════════════════════════════════════════════════════════
            // RESULT
            // ═══════════════════════════════════════════════════════════════════
            _buildSectionHeader('Result'),
            Container(
              width: double.infinity,
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.grey[100],
                borderRadius: BorderRadius.circular(8),
                border: Border.all(color: Colors.grey[300]!),
              ),
              child: SelectableText(
                _dockitResult,
                style: const TextStyle(
                  fontFamily: 'monospace',
                  fontSize: 12,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSectionHeader(String title) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.bold,
          color: Colors.blue,
        ),
      ),
    );
  }

  Widget _buildRadioOption<T>(
      String title,
      T value,
      T groupValue,
      ValueChanged<T?> onChanged,
      ) {
    return RadioListTile<T>(
      title: Text(title),
      value: value,
      groupValue: groupValue,
      onChanged: onChanged,
      dense: true,
      contentPadding: EdgeInsets.zero,
    );
  }

  Widget _buildCheckbox(
      String title,
      bool value,
      ValueChanged<bool?> onChanged,
      ) {
    return CheckboxListTile(
      title: Text(title),
      value: value,
      onChanged: onChanged,
      dense: true,
      contentPadding: EdgeInsets.zero,
      controlAffinity: ListTileControlAffinity.leading,
    );
  }
}