crossplatform_flutter_vidvdockit 1.0.0
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,
);
}
}