flutter_auth_screen 0.1.4 copy "flutter_auth_screen: ^0.1.4" to clipboard
flutter_auth_screen: ^0.1.4 copied to clipboard

Customizable PIN authentication plugin with biometric support, lockout protection, and pure UI components.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_auth_screen/flutter_auth_screen.dart';
import 'pin_service.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Auth Screen Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
        brightness: Brightness.light,
      ),
      darkTheme: ThemeData(
        primarySwatch: Colors.purple,
        useMaterial3: true,
        brightness: Brightness.dark,
      ),
      themeMode: ThemeMode.system,
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _isPinSet = false;
  String _statusMessage = '';
  final _pinService = PinService();
  bool _enableBiometric = false;
  
  @override
  void initState() {
    super.initState();
    _checkPinStatus();
  }

  Future<void> _checkPinStatus() async {
    try {
      final isPinSet = await _pinService.isPinSet();
      if (mounted) {
        setState(() {
          _isPinSet = isPinSet;
          _statusMessage = isPinSet ? 'PIN is set ✓' : 'No PIN set';
        });
      }
    } catch (e) {
      if (mounted) {
        setState(() {
          _isPinSet = false;
          _statusMessage = 'Error checking PIN status: $e';
        });
      }
    }
  }

  void _showSetPinScreen() {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => SetPinScreen(
          onPinSet: (pin) async {
            return await _pinService.savePin(pin);
          },
          onCompleted: (success) {
            if (success) {
              setState(() {
                _statusMessage = 'PIN set successfully!';
              });
              _checkPinStatus();
              
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('PIN set successfully!'),
                  backgroundColor: Colors.green,
                ),
              );
            } else {
              setState(() {
                _statusMessage = 'Failed to set PIN';
              });
              
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('Failed to set PIN. Please try again.'),
                  backgroundColor: Colors.red,
                ),
              );
            }
          },
          onCancel: () {
            Navigator.of(context).pop();
          },
          primaryColor: Colors.blue,
          createTitle: 'Secure Your App',
          createSubtitle: 'Create a 4-digit PIN',
          confirmTitle: 'Confirm Your PIN',
          confirmSubtitle: 'Enter your PIN again',
          mismatchMessage: 'PINs don\'t match! Please try again.',
          config: const PinScreenConfig(
            pinLength: 4,
            enableHaptic: true,
            showShakeAnimation: true,
            enableVoiceOver: true,
          ),
        ),
      ),
    );
  }

  void _showChangePinScreen() async {
    final isPinSet = await _pinService.isPinSet();
    
    if (!isPinSet) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Please set a PIN first'),
          backgroundColor: Colors.orange,
        ),
      );
      return;
    }

    if (!mounted) return;
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => ChangePinScreen(
          onVerifyOldPin: (pin) async {
            return await _pinService.verifyPin(pin);
          },
          onPinChanged: (newPin) async {
            return await _pinService.savePin(newPin);
          },
          onCompleted: (success) {
            if (success) {
              setState(() {
                _statusMessage = 'PIN changed successfully!';
              });
              _checkPinStatus();
            } else {
              setState(() {
                _statusMessage = 'Failed to change PIN';
              });
            }
          },
          validateNewPin: (pin) {
            // Optional: Add validation logic here
            return null;
          },
          onCancel: () {
            Navigator.of(context).pop();
          },
          primaryColor: Colors.blue,
          oldPinTitle: 'Enter Current PIN',
          oldPinSubtitle: 'Verify your current PIN',
          newPinTitle: 'Create New PIN',
          newPinSubtitle: 'Enter your new 4-digit PIN',
          confirmPinTitle: 'Confirm New PIN',
          confirmPinSubtitle: 'Re-enter your new PIN',
          incorrectOldPinMessage: 'Incorrect PIN.',
          mismatchMessage: 'PINs don\'t match! Please try again.',
          maxAttempts: 3,
          maxAttemptsMessage: 'Too many failed attempts. Please try again later.',
          config: const PinScreenConfig(
            pinLength: 4,
            enableHaptic: true,
            showShakeAnimation: true,
            enableVoiceOver: true,
          ),
        ),
      ),
    );
  }

  void _showVerifyPinScreen() async {
    final isPinSet = await _pinService.isPinSet();
    
    if (!isPinSet) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Please set a PIN first'),
          backgroundColor: Colors.orange,
        ),
      );
      return;
    }
    
    // Check if locked out
    if (await _pinService.isLockedOut()) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Account is locked. Please try again later.'),
          backgroundColor: Colors.red,
          duration: Duration(seconds: 3),
        ),
      );
      return;
    }

    if (!mounted) return;
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => VerifyPinScreen(
          onVerifyPin: (pin) async {
            return await _pinService.verifyPin(pin);
          },
          onVerificationResult: (success, attemptsRemaining) {
            if (success) {
              setState(() {
                _statusMessage = 'PIN verified successfully!';
              });
              
              if (context.mounted) {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                    content: Row(
                      children: [
                        Icon(Icons.check_circle, color: Colors.white),
                        SizedBox(width: 12),
                        Text('Access Granted!', style: TextStyle(fontSize: 16)),
                      ],
                    ),
                    backgroundColor: Colors.green,
                    duration: Duration(seconds: 2),
                  ),
                );
              }
            } else if (attemptsRemaining == 0) {
              // Lockout user
              _pinService.setLockout();
              setState(() {
                _statusMessage = 'Account locked for 5 minutes!';
              });
              
              if (context.mounted) {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                    content: Text('Maximum attempts reached. Account locked for 5 minutes.'),
                    backgroundColor: Colors.red,
                    duration: Duration(seconds: 5),
                  ),
                );
              }
            }
          },
          // Biometric callback (passed directly to screen, not config)
          onBiometric: _enableBiometric ? () async {
            final result = await _showBiometricDialog();
            if (result && context.mounted) {
              // If biometric succeeds, close screen
              Navigator.pop(context);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('Authenticated with biometric!'),
                  backgroundColor: Colors.green,
                ),
              );
            }
          } : null,
          canCancel: true,
          maxAttempts: 5,
          primaryColor: Colors.blue,
          title: 'Verify Identity',
          subtitle: 'Enter your PIN to access secured content',
          incorrectPinMessage: (remaining) =>
              'Wrong PIN! $remaining ${remaining == 1 ? 'attempt' : 'attempts'} left.',
          maxAttemptsMessage: 'Account locked. Try again later.',
          config: PinScreenConfig(
            showBiometric: _enableBiometric,
            biometricIcon: Icons.fingerprint,
            pinLength: 4,
            enableHaptic: true,
            showShakeAnimation: true,
            enableVoiceOver: true,
            pinDotSemanticLabel: 'PIN digit',
            deleteButtonSemanticLabel: 'Delete last digit',
            biometricButtonSemanticLabel: 'Use fingerprint',
          ),
        ),
      ),
    );
  }
  
  Future<bool> _showBiometricDialog() async {
    // Simulate biometric authentication
    // In real app, use local_auth package
    return await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Biometric Authentication'),
        content: const Text('In a real app, this would use fingerprint/face ID.\n\nSimulating authentication...'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, true),
            child: const Text('Authenticate'),
          ),
        ],
      ),
    ) ?? false;
  }
  
  void _removeSecurity() async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Remove Security'),
        content: const Text('Are you sure you want to remove the PIN?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(false),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () => Navigator.of(context).pop(true),
            style: TextButton.styleFrom(foregroundColor: Colors.red),
            child: const Text('Remove'),
          ),
        ],
      ),
    );

    if (confirmed == true) {
      final success = await _pinService.removePin();
      
      setState(() {
        _statusMessage = success ? 'PIN removed successfully' : 'Failed to remove PIN';
      });
      _checkPinStatus();
      
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(success ? 'PIN removed successfully' : 'Failed to remove PIN'),
            backgroundColor: success ? Colors.green : Colors.red,
          ),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Auth Screen Demo'),
        centerTitle: true,
        elevation: 2,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status Card
            Card(
              elevation: 4,
              child: Padding(
                padding: const EdgeInsets.all(24.0),
                child: Column(
                  children: [
                    Icon(
                      _isPinSet ? Icons.lock : Icons.lock_open,
                      size: 64,
                      color: _isPinSet ? Colors.green : Colors.grey,
                    ),
                    const SizedBox(height: 16),
                    Text(
                      _statusMessage,
                      style: const TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.w500,
                      ),
                      textAlign: TextAlign.center,
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 24),
            
            // Feature Configuration
            Card(
              elevation: 2,
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      '🎨 Feature Configuration',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 16),
                    
                    SwitchListTile(
                      title: const Text('Biometric Auth'),
                      subtitle: const Text('Show fingerprint button (simulated)'),
                      value: _enableBiometric,
                      onChanged: (value) {
                        setState(() {
                          _enableBiometric = value;
                        });
                      },
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 24),
            
            // Action Buttons
            if (!_isPinSet)
              ElevatedButton.icon(
                onPressed: _showSetPinScreen,
                icon: const Icon(Icons.pin),
                label: const Text('Set PIN'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.blue,
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
              ),
            
            if (_isPinSet) ...[
              ElevatedButton.icon(
                onPressed: _showChangePinScreen,
                icon: const Icon(Icons.sync_lock),
                label: const Text('Change PIN'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.blue,
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
              ),
              const SizedBox(height: 12),
            ],
            
            if (!_isPinSet)
              const SizedBox(height: 12),
            
            ElevatedButton.icon(
              onPressed: _isPinSet ? _showVerifyPinScreen : null,
              icon: const Icon(Icons.verified_user),
              label: const Text('Verify PIN'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(vertical: 16),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
            ),
            const SizedBox(height: 12),
            
            OutlinedButton.icon(
              onPressed: _isPinSet ? _removeSecurity : null,
              icon: const Icon(Icons.delete_outline),
              label: const Text('Remove Security'),
              style: OutlinedButton.styleFrom(
                foregroundColor: Colors.red,
                side: const BorderSide(color: Colors.red),
                padding: const EdgeInsets.symmetric(vertical: 16),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
            ),
            const SizedBox(height: 24),
            
            // Feature List
            Card(
              color: Colors.blue.shade50,
              child: const Padding(
                padding: EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '✨ Features Demonstrated:',
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                    SizedBox(height: 12),
                    Text('✅ Set & Create new PIN'),
                    Text('✅ Change existing PIN (3-step flow)'),
                    Text('✅ Verify PIN with lockout protection'),
                    Text('✅ Biometric authentication (simulated)'),
                    Text('✅ Lockout protection (5 attempts, 5 min)'),
                    Text('✅ Haptic feedback & animations'),
                    Text('✅ Accessibility support (VoiceOver)'),
                    Text('✅ Custom messages & colors'),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
2
likes
150
points
208
downloads

Publisher

unverified uploader

Weekly Downloads

Customizable PIN authentication plugin with biometric support, lockout protection, and pure UI components.

Repository (GitHub)
View/report issues

Topics

#security #authentication #pin #biometric #lock-screen

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_auth_screen

Packages that implement flutter_auth_screen