vlens 0.1.10 copy "vlens: ^0.1.10" to clipboard
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();
  }
}