fadsync_email_validator 1.0.0
fadsync_email_validator: ^1.0.0 copied to clipboard
Superfast email validation, disposable email detection, and anti-fraud auth guard SDK for Flutter. Secure login and signup flows with 1 click.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:fadsync_email_validator/fadsync_email_validator.dart';
void main() {
runApp(const FadSyncDemoApp());
}
class FadSyncDemoApp extends StatelessWidget {
const FadSyncDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FadSync Email Validator Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF00C49F),
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: const MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedTab = 0;
String _apiKey = 'demo_free_key';
late FadSyncEmailValidator _validator;
@override
void initState() {
super.initState();
_initValidator();
}
void _initValidator() {
_validator = FadSyncEmailValidator(
apiKey: _apiKey,
baseUrl: 'https://mailcheck.fadsync.com',
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFF00C49F).withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.shield_rounded, color: Color(0xFF00C49F)),
),
const SizedBox(width: 10),
const Text(
'FadSync Security',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
],
),
actions: [
IconButton(
icon: const Icon(Icons.key_rounded),
tooltip: 'Configure API Key',
onPressed: _showApiKeyDialog,
),
],
),
body: IndexedStack(
index: _selectedTab,
children: [
SignupProtectionView(validator: _validator),
LiveInspectorView(validator: _validator),
],
),
bottomNavigationBar: NavigationBar(
selectedIndex: _selectedTab,
onDestinationSelected: (index) => setState(() => _selectedTab = index),
destinations: const [
NavigationDestination(
icon: Icon(Icons.app_registration_rounded),
label: '1-Click Auth Guard',
),
NavigationDestination(
icon: Icon(Icons.analytics_rounded),
label: 'Email Inspector',
),
],
),
);
}
void _showApiKeyDialog() {
final controller = TextEditingController(text: _apiKey);
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('FadSync API Key'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Get your free API Key from https://mailcheck.fadsync.com/',
style: TextStyle(fontSize: 13, color: Colors.grey),
),
const SizedBox(height: 12),
TextField(
controller: controller,
decoration: const InputDecoration(
labelText: 'API Key',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.vpn_key_rounded),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
setState(() {
_apiKey = controller.text.trim();
_initValidator();
});
Navigator.pop(ctx);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('API Key updated!')),
);
},
child: const Text('Save Key'),
),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// TAB 1: 1-Click Secure Signup Flow
// ─────────────────────────────────────────────────────────────────────────────
class SignupProtectionView extends StatefulWidget {
final FadSyncEmailValidator validator;
const SignupProtectionView({super.key, required this.validator});
@override
State<SignupProtectionView> createState() => _SignupProtectionViewState();
}
class _SignupProtectionViewState extends State<SignupProtectionView> {
final _emailController = TextEditingController();
final _nameController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
final List<String> _registeredUsersDb = [];
Future<void> _handleSignup() async {
final email = _emailController.text.trim();
final name = _nameController.text.trim();
if (name.isEmpty || email.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please fill all required fields.')),
);
return;
}
setState(() => _isLoading = true);
// 🛡️ 1-CLICK AUTH GUARD INTEGRATION
await FadSyncAuthGuard.protect(
client: widget.validator,
email: email,
// 1. SAFE & ALLOWED -> Save user to database
onAllowed: (result) async {
setState(() {
_isLoading = false;
_registeredUsersDb.add('${name.trim()} (${result.email})');
_emailController.clear();
_nameController.clear();
_passwordController.clear();
});
_showSuccessDialog(result);
},
// 2. BLOCKED OR DISPOSABLE -> Show friendly error alert
onBlocked: (errorMessage, result) {
setState(() => _isLoading = false);
_showBlockedDialog(errorMessage, result);
},
// 3. TYPO FOUND -> Offer 1-tap autocorrect
onTypoFound: (suggestedEmail, result) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Did you mean $suggestedEmail?'),
action: SnackBarAction(
label: 'Apply',
onPressed: () {
setState(() {
_emailController.text = suggestedEmail;
});
},
),
),
);
},
);
}
void _showSuccessDialog(ValidationResult result) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.check_circle_rounded, color: Color(0xFF00C49F), size: 48),
title: const Text('Account Created! 🎉'),
content: Text(
'Email "${result.email}" was verified by FadSync and saved to the database.\n\nRisk Score: ${result.riskScore}/100',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Great!'),
),
],
),
);
}
void _showBlockedDialog(String errorMessage, ValidationResult result) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.block_rounded, color: Colors.redAccent, size: 48),
title: const Text('Registration Blocked'),
content: Text(
'$errorMessage\n\n(Disposable: ${result.isDisposable}, Risk: ${result.riskScore})',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('OK'),
),
],
),
);
}
void _fillPreset(String email) {
setState(() {
_emailController.text = email;
_nameController.text = 'Demo User';
_passwordController.text = 'Password123!';
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
color: const Color(0xFF1E293B),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.shield_outlined, color: Color(0xFF00C49F)),
SizedBox(width: 8),
Text(
'1-Click Signup Security Guard',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 6),
const Text(
'Try creating an account with a legitimate email, a disposable email, or a domain with a typo to see the SDK in action.',
style: TextStyle(color: Colors.white70, fontSize: 13),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 6,
children: [
ActionChip(
label: const Text('✅ Valid: alex@fadsync.com'),
onPressed: () => _fillPreset('alex@fadsync.com'),
),
ActionChip(
label: const Text('🚫 Disposable: test@10minutemail.com'),
onPressed: () => _fillPreset('test@10minutemail.com'),
),
ActionChip(
label: const Text('💡 Typo: user@gamil.com'),
onPressed: () => _fillPreset('user@gamil.com'),
),
],
),
],
),
),
),
const SizedBox(height: 20),
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person_outline),
),
),
const SizedBox(height: 14),
EmailValidatorFormField(
client: widget.validator,
controller: _emailController,
triggerMode: ValidationTriggerMode.onChangedDebounced,
debounceDuration: const Duration(milliseconds: 500),
showTypoSuggestion: true,
decoration: const InputDecoration(
labelText: 'Email Address',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email_outlined),
),
),
const SizedBox(height: 14),
TextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.lock_outline),
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _handleSignup,
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black),
)
: const Icon(Icons.lock_outline),
label: Text(
_isLoading ? 'Validating Email...' : 'Create Secure Account',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00C49F),
foregroundColor: Colors.black,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
),
),
const SizedBox(height: 30),
const Text(
'Simulated Database (Users Saved):',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
),
const SizedBox(height: 8),
if (_registeredUsersDb.isEmpty)
const Text(
'No users saved yet. Sign up above to persist clean accounts.',
style: TextStyle(color: Colors.grey, fontSize: 13),
)
else
..._registeredUsersDb.map(
(u) => Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(6),
),
child: Row(
children: [
const Icon(Icons.check_circle, size: 16, color: Color(0xFF00C49F)),
const SizedBox(width: 8),
Text(u, style: const TextStyle(fontSize: 13)),
],
),
),
),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// TAB 2: Live Email Inspector
// ─────────────────────────────────────────────────────────────────────────────
class LiveInspectorView extends StatefulWidget {
final FadSyncEmailValidator validator;
const LiveInspectorView({super.key, required this.validator});
@override
State<LiveInspectorView> createState() => _LiveInspectorViewState();
}
class _LiveInspectorViewState extends State<LiveInspectorView> {
final _controller = TextEditingController(text: 'demo@mailinator.com');
ValidationResult? _result;
bool _isLoading = false;
String? _errorMessage;
Future<void> _analyzeEmail() async {
final email = _controller.text.trim();
if (email.isEmpty) return;
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final res = await widget.validator.verify(email);
setState(() {
_result = res;
_isLoading = false;
});
} catch (e) {
setState(() {
_errorMessage = e.toString();
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _controller,
decoration: InputDecoration(
labelText: 'Email to Inspect',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.search_rounded),
suffixIcon: IconButton(
icon: const Icon(Icons.arrow_forward_rounded),
onPressed: _isLoading ? null : _analyzeEmail,
),
),
onSubmitted: (_) => _analyzeEmail(),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _analyzeEmail,
icon: const Icon(Icons.radar_rounded),
label: const Text('Inspect Email Health & Fraud Risk'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00C49F),
foregroundColor: Colors.black,
),
),
),
const SizedBox(height: 24),
if (_isLoading)
const Center(child: CircularProgressIndicator())
else if (_errorMessage != null)
Card(
color: Colors.red.shade900.withValues(alpha: 0.4),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Error: $_errorMessage'),
),
)
else if (_result != null)
_buildResultCard(_result!),
],
),
);
}
Widget _buildResultCard(ValidationResult r) {
final isBlocked = r.isBlocked;
final statusColor = isBlocked ? Colors.redAccent : const Color(0xFF00C49F);
return Card(
color: const Color(0xFF1E293B),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
r.email,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
Chip(
label: Text(
r.recommendation.name.toUpperCase(),
style: TextStyle(fontWeight: FontWeight.bold, color: statusColor),
),
backgroundColor: statusColor.withValues(alpha: 0.15),
),
],
),
const Divider(height: 24),
_infoRow('Safe to Register', r.isSafeToRegister ? 'YES ✅' : 'NO ❌',
r.isSafeToRegister ? Colors.green : Colors.redAccent),
_infoRow('Disposable / Burner', r.isDisposable ? 'YES (High Risk)' : 'NO (Clean)',
r.isDisposable ? Colors.redAccent : Colors.green),
_infoRow('Syntax Format', r.isValidFormat ? 'Valid RFC' : 'Invalid Syntax',
r.isValidFormat ? Colors.green : Colors.redAccent),
_infoRow('MX Mail Servers', r.domainDetails.hasValidMx ? 'Active' : 'Missing / Dead',
r.domainDetails.hasValidMx ? Colors.green : Colors.redAccent),
_infoRow('Free Provider', r.isFreeProvider ? 'Yes (Public)' : 'No (Custom Domain)',
Colors.white70),
_infoRow('Role / Department Account', r.isRoleAccount ? 'Yes (support/admin)' : 'No (Personal)',
Colors.white70),
_infoRow('Risk Score', '${r.riskScore} / 100',
r.riskScore > 60 ? Colors.redAccent : Colors.green),
if (r.hasTypoSuggestion)
_infoRow('Typo Suggestion', r.typoFix!, Colors.amberAccent),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'End-User Friendly UI Message:\n"${r.userFriendlyMessage}"',
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
),
),
],
),
),
);
}
Widget _infoRow(String label, String value, Color valueColor) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(color: Colors.white70, fontSize: 13)),
Text(
value,
style: TextStyle(fontWeight: FontWeight.bold, color: valueColor, fontSize: 13),
),
],
),
);
}
}