vlens 0.1.10
vlens: ^0.1.10 copied to clipboard
VLens Flutter SDK provides seamless integration for biometric verification.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import 'login_api.dart' show loginApi, sdkBaseUrl, sdkApiKey, sdkTenancyName;
import 'id_generator.dart';
import 'package:vlens/vlens.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'VLens Flutter SDK Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const VLensDemoScreen(),
);
}
}
class VLensDemoScreen extends StatefulWidget {
const VLensDemoScreen({super.key});
@override
State<VLensDemoScreen> createState() => _VLensDemoScreenState();
}
class _VLensDemoScreenState extends State<VLensDemoScreen> {
final TextEditingController _transactionIdController =
TextEditingController();
final TextEditingController _accessTokenController = TextEditingController();
bool _isLoading = false;
bool _isDataComplete = false;
String _locale = 'en';
static const Color _primary = Color(0xFF397374);
static const Color _passportColor = Color(0xFF4E5A78);
// ── Shared SDK colours / errors reused by all flows ───────────────────────
ColorsConfig get _colors => ColorsConfig(
light: ColorConfig(
accent: "#4E5A78",
primary: "#397374",
secondary: "#FF4081",
background: "#FEFEFE",
dark: "#000000",
light: "#FFFFFF",
),
dark: ColorConfig(
accent: "#FFC107",
primary: "#2196F3",
secondary: "#FF4081",
background: "#000000",
dark: "#FFFFFF",
light: "#000000",
),
);
List<ApiError> get _errorMessages => [
ApiError(
errorCode: 101,
errorMessageEn: "Network error",
errorMessageAr: "خطأ في الشبكة",
),
];
EnvironmentConfig get _env => EnvironmentConfig(
apiBaseUrl: sdkBaseUrl,
accessToken: _accessTokenController.text,
refreshToken: "your-refresh-token",
apiKey: sdkApiKey,
tenancyName: sdkTenancyName,
);
// ── Build ─────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
title: const Padding(
padding: EdgeInsets.only(top: 14),
child: Text(
'VLens Flutter SDK Demo',
style: TextStyle(
color: _primary,
fontWeight: FontWeight.bold,
),
),
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child:
Image.asset("packages/vlens/assets/images/vlens_logo.png"),
),
const SizedBox(height: 30),
// ── Transaction ID ───────────────────────────────────────────
const Text(
'Transaction ID',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Color(0xff204061),
),
),
const SizedBox(height: 8),
TextField(
controller: _transactionIdController,
enabled: false,
style: const TextStyle(color: Colors.black),
decoration: const InputDecoration(
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
borderSide:
BorderSide(color: Color(0xff9B9999), width: 0.5),
),
hintText: 'Transaction ID',
hintStyle: TextStyle(color: Colors.grey),
),
),
const SizedBox(height: 16),
// ── Access Token ─────────────────────────────────────────────
const Text(
'Access Token',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Color(0xff204061),
),
),
const SizedBox(height: 8),
SizedBox(
height: 200,
child: TextField(
controller: _accessTokenController,
maxLines: 7,
enabled: false,
style: const TextStyle(color: Colors.black),
decoration: const InputDecoration(
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
borderSide:
BorderSide(color: Color(0xff9B9999), width: 0.5),
),
hintText: 'Access Token',
hintStyle: TextStyle(color: Colors.grey),
),
),
),
const SizedBox(height: 24),
// ── Language toggle ──────────────────────────────────────────
_buildLocaleToggle(),
const SizedBox(height: 16),
// ── Set Default Data ─────────────────────────────────────────
_isLoading
? const Center(
child: CircularProgressIndicator(color: _primary))
: _buildButton(
label: 'Set Default Data',
onPressed: _setDefaultData,
enabled: true,
),
const SizedBox(height: 12),
// ── ID card flow ─────────────────────────────────────────────
_buildButton(
label: 'Get Started (ID Card)',
onPressed: () => _getStarted(context),
enabled: _isDataComplete,
),
const SizedBox(height: 12),
// ── Passport / ePassport flow ────────────────────────────────
_buildButton(
label: 'Get Started (ePassport)',
onPressed: () => _getStartedPassport(context),
enabled: _isDataComplete,
color: _passportColor,
),
const SizedBox(height: 12),
// ── Liveness-only flow ───────────────────────────────────────
_buildButton(
label: 'Get Started (Liveness Only)',
onPressed: () => _getStartedWithLivenessOnly(context),
enabled: _isDataComplete,
),
],
),
),
);
}
// ── Locale toggle ─────────────────────────────────────────────────────────
Widget _buildLocaleToggle() {
return Row(
children: [
const Text(
'Language',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xff204061),
),
),
const Spacer(),
Container(
decoration: BoxDecoration(
color: const Color(0xFFF0F0F0),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: ['en', 'ar'].map((locale) {
final selected = _locale == locale;
return GestureDetector(
onTap: () => setState(() => _locale = locale),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 8),
decoration: BoxDecoration(
color: selected ? _primary : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Text(
locale == 'en' ? 'EN' : 'AR',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: selected ? Colors.white : Colors.grey,
),
),
),
);
}).toList(),
),
),
],
);
}
// ── Button widget ─────────────────────────────────────────────────────────
Widget _buildButton({
required String label,
required VoidCallback onPressed,
required bool enabled,
Color color = _primary,
}) {
return SizedBox(
width: double.infinity,
height: 44,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: enabled ? onPressed : null,
child: Text(
label,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
);
}
// ── Set Default Data ──────────────────────────────────────────────────────
void _setDefaultData() {
setState(() {
_isLoading = true;
_isDataComplete = false;
});
loginApi().then((accessToken) {
if (accessToken != null) {
setState(() {
_isLoading = false;
_isDataComplete = true;
_transactionIdController.text = generateUUID();
_accessTokenController.text = accessToken;
});
} else {
setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login failed — check credentials or network')),
);
}
}).catchError((e) {
setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Network error: $e')),
);
});
}
// ── Flow launchers ────────────────────────────────────────────────────────
void _getStarted(BuildContext context) {
VLensManager().init(
context,
SdkConfig(
transactionId: _transactionIdController.text,
isLivenessOnly: false,
isNationalIdOnly: false,
isPassport: false,
logoPath: "assets/images/test_logo.png",
enableSounds: true,
env: _env,
defaultLocale: _locale,
colors: _colors,
errorMessages: _errorMessages,
onSuccess: (extractedData) {
Logger().d(
"ID success ${extractedData?.idFrontData?.name} *** ${extractedData?.idFrontData?.idKey}");
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Success Verified!")),
);
},
onFailure: (errorCode, errorMsg) {
Logger().d("ID failed: $errorMsg, Code: $errorCode");
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(errorMsg)));
},
getExtractedData: true,
disableAutoCapture: false,
),
);
}
void _getStartedPassport(BuildContext context) {
VLensManager().init(
context,
SdkConfig(
transactionId: _transactionIdController.text,
isLivenessOnly: false,
isNationalIdOnly: false,
isPassport: true,
logoPath: "assets/images/test_logo.png",
enableSounds: true,
env: _env,
defaultLocale: _locale,
colors: _colors,
errorMessages: _errorMessages,
onSuccess: (extractedData) {
Logger().d(
"Passport success ${extractedData?.passportData?.name}");
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Passport Verified!")),
);
},
onFailure: (errorCode, errorMsg) {
Logger().d("Passport failed: $errorMsg, Code: $errorCode");
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(errorMsg)));
},
getExtractedData: true,
disableAutoCapture: false,
),
);
}
void _getStartedWithLivenessOnly(BuildContext context) {
VLensManager().init(
context,
SdkConfig(
transactionId: _transactionIdController.text,
isLivenessOnly: true,
isNationalIdOnly: true,
env: _env,
defaultLocale: _locale,
colors: _colors,
errorMessages: _errorMessages,
onSuccess: (extractedData) {
Logger().d(
"Liveness success ${extractedData?.idFrontData?.name}");
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Success Verified!")),
);
},
onFailure: (errorCode, errorMsg) {
Logger().d("Liveness failed: $errorMsg, Code: $errorCode");
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(errorMsg)));
},
detectionRetryCounter: 5,
getExtractedData: true,
),
);
}
@override
void dispose() {
_transactionIdController.dispose();
_accessTokenController.dispose();
super.dispose();
}
}