friendly_captcha_flutter 0.1.0
friendly_captcha_flutter: ^0.1.0 copied to clipboard
FriendlyCaptcha v2 widget for Flutter. Privacy-first, accessible bot protection for Android, iOS, and Web (Wasm-compatible).
import 'package:flutter/material.dart';
import 'package:friendly_captcha_flutter/friendly_captcha_flutter.dart';
/// Replace with your own sitekey from https://app.friendlycaptcha.com/, or use
/// one of the documented test sitekeys at
/// https://developer.friendlycaptcha.com/docs/v2/guides/automated-testing.
const String kDemoSitekey = 'FCMGEMUD2KTDSQ5H';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'friendly_captcha_flutter example',
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
brightness: Brightness.light,
),
darkTheme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
brightness: Brightness.dark,
),
home: const SignUpFormPage(),
);
}
}
class SignUpFormPage extends StatefulWidget {
const SignUpFormPage({super.key});
@override
State<SignUpFormPage> createState() => _SignUpFormPageState();
}
class _SignUpFormPageState extends State<SignUpFormPage> {
final _captcha = FriendlyCaptchaController();
final _emailController = TextEditingController();
@override
void initState() {
super.initState();
_captcha.addListener(_onCaptchaChanged);
}
@override
void dispose() {
_captcha
..removeListener(_onCaptchaChanged)
..dispose();
_emailController.dispose();
super.dispose();
}
void _onCaptchaChanged() => setState(() {});
void _submit() {
final solution = _captcha.solution;
if (solution == null) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Would POST to your backend with solution: '
'${solution.substring(0, solution.length.clamp(0, 24))}…',
),
),
);
// After a real submit you'd reset on failure so the user gets a fresh
// challenge. Doing it unconditionally here keeps the demo simple.
_captcha.reset();
}
@override
Widget build(BuildContext context) {
final stateLabel = _captcha.lastResult?.state.name ?? 'idle';
return Scaffold(
appBar: AppBar(title: const Text('FriendlyCaptcha demo')),
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
FriendlyCaptcha(
sitekey: kDemoSitekey,
controller: _captcha,
onResult: (result) {
if (result.isError) {
debugPrint('Captcha error: ${result.error}');
}
},
),
const SizedBox(height: 16),
FilledButton(
onPressed: _captcha.hasSolution ? _submit : null,
child: const Text('Sign up'),
),
const SizedBox(height: 8),
Text(
'Captcha state: $stateLabel',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
),
),
);
}
}