kids_auth 1.0.1
kids_auth: ^1.0.1 copied to clipboard
A delightful, child-friendly Flutter authentication package featuring login, registration, forgot password, and password reset screens, plus managed auth flows, backend adapters, and large accessible [...]
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:kids_auth/kids_auth.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'kids_auth Example',
debugShowCheckedModeBanner: false,
home: ThemePickerScreen(),
);
}
}
class ThemePickerScreen extends StatelessWidget {
const ThemePickerScreen({super.key});
@override
Widget build(BuildContext context) {
final themes = {
'Default 🌈': const KidsAuthTheme(),
'Candy 🍬': KidsAuthTheme.candy(),
'Ocean 🌊': KidsAuthTheme.ocean(),
'Jungle 🌿': KidsAuthTheme.jungle(),
'Space 🚀': KidsAuthTheme.space(),
};
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFEDE7F6), Color(0xFFFCE4EC)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 40),
const Text(
'kids_auth',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w900,
color: Color(0xFF6C63FF),
letterSpacing: 1.5,
),
),
const SizedBox(height: 8),
const Text(
'Pick a theme to preview the connected flow',
style: TextStyle(color: Colors.black54, fontSize: 15),
),
const SizedBox(height: 40),
Expanded(
child: ListView(
children: themes.entries
.map(
(entry) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _ThemeCard(
label: entry.key,
theme: entry.value,
),
),
)
.toList(),
),
),
],
),
),
),
),
);
}
}
class _ThemeCard extends StatelessWidget {
final String label;
final KidsAuthTheme theme;
const _ThemeCard({required this.label, required this.theme});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => _DemoShell(theme: theme, name: label),
),
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: theme.primaryColor.withValues(alpha: 0.2),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [theme.primaryColor, theme.secondaryColor],
),
shape: BoxShape.circle,
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
label,
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
color: theme.primaryColor,
),
),
),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: theme.primaryColor,
),
],
),
),
);
}
}
class _DemoShell extends StatefulWidget {
final KidsAuthTheme theme;
final String name;
const _DemoShell({required this.theme, required this.name});
@override
State<_DemoShell> createState() => _DemoShellState();
}
class _DemoShellState extends State<_DemoShell> {
late final _FakeAuthAdapter _adapter;
late final _MemoryPersistenceStore _store;
bool _loggedIn = false;
KidsAuthFlowStep _step = KidsAuthFlowStep.login;
String _email = '';
String _status = 'Ready to test connected auth flow';
@override
void initState() {
super.initState();
_adapter = _FakeAuthAdapter(
onStatusChanged: (status) {
if (!mounted) return;
setState(() => _status = status);
},
onLoggedIn: (email) {
if (!mounted) return;
setState(() {
_email = email;
_loggedIn = true;
});
},
);
_store = _MemoryPersistenceStore(
const KidsAuthSavedLoginState(
email: 'saved@kid.example',
rememberMe: true,
),
);
}
@override
Widget build(BuildContext context) {
if (_loggedIn) {
return _HomeScreen(
theme: widget.theme,
email: _email,
status: _status,
onLogout: () {
setState(() {
_loggedIn = false;
_step = KidsAuthFlowStep.login;
_status = 'Logged out';
});
},
onPreviewReset: () {
setState(() {
_loggedIn = false;
_step = KidsAuthFlowStep.resetPassword;
_status = 'Previewing reset password flow';
});
},
onPreviewRegister: () {
setState(() {
_loggedIn = false;
_step = KidsAuthFlowStep.register;
_status = 'Previewing sign-up flow';
});
},
);
}
return KidsAuthConnectedFlow(
theme: widget.theme,
config: KidsAuthConfig(
appName: widget.name,
appTagline: 'Testing a connected auth flow',
),
adapter: _adapter,
persistenceStore: _store,
initialStep: _step,
resetToken: 'demo-reset-token',
onStepChanged: (step) => setState(() => _step = step),
onLoginSuccess: () {
setState(() {
_loggedIn = true;
_step = KidsAuthFlowStep.login;
});
},
onResetPasswordSuccess: () {
setState(() => _status = 'Password reset successful');
},
onTermsOfServiceTap: () => _showLegalDialog(
context,
title: 'Terms & Conditions',
body:
'This demo uses fake sign-in logic for preview purposes only. No real account is created, and no personal data is sent to a backend.',
),
onPrivacyPolicyTap: () => _showLegalDialog(
context,
title: 'Privacy Policy',
body:
'This demo stores the remembered email only in local in-memory state while the app is running. Closing the app resets the sample data.',
),
);
}
void _showLegalDialog(
BuildContext context, {
required String title,
required String body,
}) {
showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(body),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
}
}
class _HomeScreen extends StatelessWidget {
final KidsAuthTheme theme;
final String email;
final String status;
final VoidCallback onLogout;
final VoidCallback onPreviewReset;
final VoidCallback onPreviewRegister;
const _HomeScreen({
required this.theme,
required this.email,
required this.status,
required this.onLogout,
required this.onPreviewReset,
required this.onPreviewRegister,
});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: theme.backgroundGradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
KidsCharacterAvatar(
theme: theme,
size: 120,
mood: KidsCharacterMood.success,
),
const SizedBox(height: 24),
Text(
'Welcome! 🎉',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w900,
color: theme.primaryColor,
),
),
const SizedBox(height: 8),
Text(
email,
style: TextStyle(
fontSize: 15,
color: theme.textColor.withValues(alpha: 0.6),
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Text(
status,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: theme.textColor,
),
),
),
const SizedBox(height: 32),
KidsAuthButton(
label: 'Preview Sign Up',
theme: theme,
onPressed: onPreviewRegister,
leading: const KidsAuthIcon(
type: KidsAuthIconType.star,
size: 22,
color: Colors.white,
),
),
const SizedBox(height: 16),
KidsAuthButton(
label: 'Preview Reset Flow',
theme: theme,
onPressed: onPreviewReset,
leading: const KidsAuthIcon(
type: KidsAuthIconType.key,
size: 22,
color: Colors.white,
),
),
const SizedBox(height: 16),
KidsAuthButton(
label: 'Log Out',
theme: theme,
isSecondary: true,
onPressed: onLogout,
leading: KidsAuthIcon(
type: KidsAuthIconType.logout,
size: 22,
color: theme.primaryColor,
),
),
],
),
),
),
),
),
);
}
}
class _FakeAuthAdapter implements KidsAuthAdapter {
final ValueChanged<String> onStatusChanged;
final ValueChanged<String> onLoggedIn;
_FakeAuthAdapter({
required this.onStatusChanged,
required this.onLoggedIn,
});
@override
Future<void> login(String email, String password) async {
onStatusChanged('Signing in as $email...');
await Future.delayed(const Duration(milliseconds: 900));
if (password.length < 6) {
throw Exception('Wrong password, try again! 🙈');
}
onLoggedIn(email);
onStatusChanged('Signed in successfully');
}
@override
Future<void> register(String email, String password) async {
onStatusChanged('Creating account for $email...');
await Future.delayed(const Duration(milliseconds: 900));
if (password.length < 6) {
throw Exception('That password is too short 🙈');
}
onLoggedIn(email);
onStatusChanged('Account created successfully');
}
@override
Future<void> resetPassword(String newPassword, String? resetToken) async {
onStatusChanged('Resetting password with token: ${resetToken ?? 'none'}');
await Future.delayed(const Duration(milliseconds: 900));
if (newPassword.length < 6) {
throw Exception('That password is too short 🙈');
}
onStatusChanged('Password reset complete');
}
@override
Future<void> sendResetLink(String email) async {
onStatusChanged('Sending reset link to $email');
await Future.delayed(const Duration(milliseconds: 900));
}
}
class _MemoryPersistenceStore implements KidsAuthPersistenceStore {
KidsAuthSavedLoginState? _state;
_MemoryPersistenceStore(this._state);
@override
Future<void> clearLoginState() async {
_state = null;
}
@override
Future<KidsAuthSavedLoginState?> readLoginState() async => _state;
@override
Future<void> writeLoginState(KidsAuthSavedLoginState state) async {
_state = state;
}
}