import 'package:flutter/material.dart';
import 'package:crossmint_flutter/crossmint_flutter_ui.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return CrossmintWalletGate(
unauthenticatedBuilder: (context, data) {
return const LoginScreen();
},
initializingBuilder: (context, data) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
},
readyBuilder: (context, data) {
// The gate fires readyBuilder once authenticated, but
// the wallet may still be loading. `hasWallet` is the
// convenience predicate — equivalent to `currentWallet != null`.
if (!data.state.hasWallet) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return WalletScreen(wallet: data.state.currentWallet!);
},
);
}
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();
final _otpController = TextEditingController();
String? _emailId;
bool _otpSent = false;
bool _isPending = false;
Future<void> _sendOtp() async {
final email = _emailController.text.trim();
if (email.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Enter a valid email')),
);
return;
}
setState(() => _isPending = true);
try {
final walletContext = CrossmintWalletContext.of(context);
final challenge = await walletContext.requireAuth.sendEmailOtp(email);
setState(() {
_emailId = challenge.id;
_otpSent = true;
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to send OTP: $e')),
);
} finally {
setState(() => _isPending = false);
}
}
Future<void> _verifyOtp() async {
final otp = _otpController.text.trim();
if (otp.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Enter the OTP code')),
);
return;
}
setState(() => _isPending = true);
try {
final walletContext = CrossmintWalletContext.of(context);
await walletContext.requireAuth.confirmEmailOtp(
email: _emailController.text.trim(),
emailId: _emailId!,
token: otp,
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Invalid OTP: $e')),
);
} finally {
setState(() => _isPending = false);
}
}
@override
Widget build(BuildContext context) {
final walletContext = CrossmintWalletContext.of(context);
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Login', style: TextStyle(fontSize: 24)),
const SizedBox(height: 20),
TextField(
controller: _emailController,
decoration: const InputDecoration(
hintText: 'Enter your email',
border: OutlineInputBorder(),
),
enabled: !_otpSent,
),
const SizedBox(height: 10),
if (!_otpSent) ...[
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isPending ? null : _sendOtp,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF05B959),
),
child: _isPending
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Text('Send OTP',
style: TextStyle(color: Colors.white)),
),
),
] else ...[
TextField(
controller: _otpController,
decoration: const InputDecoration(
hintText: 'Enter OTP code',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isPending ? null : _verifyOtp,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF05B959),
),
child: _isPending
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Text('Verify OTP',
style: TextStyle(color: Colors.white)),
),
),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => setState(() => _otpSent = false),
child: const Text('Back'),
),
),
],
const SizedBox(height: 20),
const Text('OR'),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => walletContext.requireAuth
.loginWithOAuth(CrossmintOAuthProvider.google),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4285F4),
),
child: const Text('Sign in with Google',
style: TextStyle(color: Colors.white)),
),
),
],
),
),
);
}
@override
void dispose() {
_emailController.dispose();
_otpController.dispose();
super.dispose();
}
}
class WalletScreen extends StatelessWidget {
final CrossmintRuntimeWalletBase wallet;
const WalletScreen({super.key, required this.wallet});
@override
Widget build(BuildContext context) {
final walletContext = CrossmintWalletContext.of(context);
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Welcome!',
style: TextStyle(fontSize: 20)),
TextButton(
onPressed: () => walletContext.requireAuth.logout(),
child: const Text('Logout',
style: TextStyle(color: Colors.red)),
),
],
),
const SizedBox(height: 20),
const Text('Wallet Info:',
style: TextStyle(fontSize: 16)),
Text('Address: ${wallet.address}'),
Text('Chain: ${wallet.chain}'),
const SizedBox(height: 20),
const Text('You are now logged in and can access your wallet!'),
],
),
),
);
}
}