Stat Card
Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress
View component →A polished payment-code display widget inspired by modern fintech apps. Switches between QR Code and Barcode tabs, renders the code inside a shadowed card with a center brand badge, and includes a Renew code action plus a secure-transactions footer. Wire it to your backend to display dynamic payment codes for cashiers and point-of-sale flows.
Display one of the codes below to the cashier to complete your payment. Tap to learn how
All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.
QR Code
Square QR code with center diamond badge
Display one of the codes below to the cashier to complete your payment. Tap to learn how
All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.
Barcode
Horizontal barcode lines with center badge
Display one of the codes below to the cashier to complete your payment. Tap to learn how
All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.
With Brand Logo
Solid brand-colored badge overlay
Display one of the codes below to the cashier to complete your payment. Tap to learn how
Compact
Without the security footer
What's Included
Use Cases
PRO TIP
Regenerate the QR code on a Timer.periodic every 30 seconds and animate the swap with an AnimatedSwitcher — short-lived codes are the standard for payment security. Pair this with the qr_flutter package for production QR rendering and store the active code id in a stream so multiple devices can refresh in lockstep.
Copy this into your Flutter project. No external packages required.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.white,
body: Center(
child: CodeScanCard(),
),
),
);
}
}
/// Flutter Code Scan Widget — All Variants
/// Payment QR / Barcode card inspired by modern fintech apps.
/// rohansurve.in/flutterkit/code-scan
enum CodeScanType { qr, barcode }
enum CodeScanStyle { standard, withLogo, compact }
class CodeScanCard extends StatefulWidget {
final CodeScanType initialType;
final CodeScanStyle style;
final String title;
final String description;
final String renewLabel;
final String footerText;
final VoidCallback? onRenew;
final VoidCallback? onClose;
final VoidCallback? onLearnMore;
const CodeScanCard({
super.key,
this.initialType = CodeScanType.qr,
this.style = CodeScanStyle.standard,
this.title = 'Code Scan',
this.description =
'Display one of the codes below to the cashier to complete your payment.',
this.renewLabel = 'Renew code',
this.footerText =
'All your transactions are fast and secure. By proceeding, you agree to the Terms & Conditions.',
this.onRenew,
this.onClose,
this.onLearnMore,
});
@override
State<CodeScanCard> createState() => _CodeScanCardState();
}
class _CodeScanCardState extends State<CodeScanCard> {
late CodeScanType _activeType;
static const _accent = Color(0xFF2563EB);
static const _gray950 = Color(0xFF030712);
static const _gray900 = Color(0xFF111827);
static const _gray700 = Color(0xFF374151);
static const _gray500 = Color(0xFF6B7280);
static const _gray400 = Color(0xFF9CA3AF);
static const _gray200 = Color(0xFFE5E7EB);
static const _gray50 = Color(0xFFF9FAFB);
@override
void initState() {
super.initState();
_activeType = widget.initialType;
}
@override
Widget build(BuildContext context) {
return Container(
width: 340,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _gray200),
boxShadow: const [
BoxShadow(
color: Color(0x0A000000),
blurRadius: 40,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeader(),
_buildTabs(),
_buildBody(),
],
),
);
}
Widget _buildHeader() {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.qr_code_scanner_rounded,
size: 20, color: _gray700),
const SizedBox(width: 10),
Expanded(
child: Text(
widget.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: _gray900,
),
),
),
InkWell(
onTap: widget.onClose,
borderRadius: BorderRadius.circular(80),
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: _gray200,
borderRadius: BorderRadius.circular(80),
),
child: const Icon(Icons.close_rounded,
size: 14, color: _gray500),
),
),
],
),
const SizedBox(height: 16),
RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 14,
color: _gray500,
height: 1.6,
),
children: [
TextSpan(text: widget.description + ' '),
TextSpan(
text: 'Tap to learn how',
style: const TextStyle(
color: _accent,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
);
}
Widget _buildTabs() {
return Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: _gray200)),
),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
_tab('QR Code', CodeScanType.qr),
_tab('Barcode', CodeScanType.barcode),
],
),
);
}
Widget _tab(String label, CodeScanType type) {
final isActive = _activeType == type;
return Expanded(
child: InkWell(
onTap: () => setState(() => _activeType = type),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: isActive ? _accent : Colors.transparent,
width: 2,
),
),
),
alignment: Alignment.center,
child: Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: isActive ? _gray700 : _gray400,
),
),
),
),
);
}
Widget _buildBody() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 28),
child: Column(
children: [
_buildCodeCard(),
if (widget.style != CodeScanStyle.compact) ...[
const SizedBox(height: 24),
_buildFooter(),
],
],
),
);
}
Widget _buildCodeCard() {
return Container(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _gray200),
boxShadow: const [
BoxShadow(
color: Color(0x0A000000),
blurRadius: 12,
),
],
),
child: Column(
children: [
Container(
width: double.infinity,
height: 220,
decoration: BoxDecoration(
color: _gray50,
borderRadius: BorderRadius.circular(12),
),
child: Stack(
alignment: Alignment.center,
children: [
_activeType == CodeScanType.qr
? _qrPattern()
: _barcodePattern(),
_centerBadge(),
],
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 40,
child: OutlinedButton(
onPressed: widget.onRenew,
style: OutlinedButton.styleFrom(
foregroundColor: _gray950,
side: const BorderSide(color: _gray200),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(80),
),
),
child: Text(
widget.renewLabel,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
);
}
Widget _qrPattern() {
return CustomPaint(
size: const Size(175, 175),
painter: _QrPainter(),
);
}
Widget _barcodePattern() {
return CustomPaint(
size: const Size(240, 90),
painter: _BarcodePainter(),
);
}
Widget _centerBadge() {
if (widget.style == CodeScanStyle.withLogo) {
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: _accent,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white, width: 3),
),
child: const Icon(Icons.bolt_rounded,
color: Colors.white, size: 22),
);
}
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Icon(Icons.diamond_outlined,
color: _accent, size: 22),
);
}
Widget _buildFooter() {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(top: 2),
child: Icon(Icons.shield_outlined, size: 16, color: _gray400),
),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.footerText,
style: const TextStyle(
fontSize: 12,
color: _gray500,
height: 1.6,
),
),
),
],
);
}
}
class _QrPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final cell = size.width / 21;
final paint = Paint()..color = const Color(0xFF111827);
final rng = List<int>.generate(
21 * 21, (i) => (i * 1103515245 + 12345) & 1);
for (int y = 0; y < 21; y++) {
for (int x = 0; x < 21; x++) {
if (rng[y * 21 + x] == 1) {
canvas.drawRect(
Rect.fromLTWH(x * cell, y * cell, cell, cell), paint);
}
}
}
void corner(double cx, double cy) {
final r = Paint()..color = const Color(0xFF111827);
canvas.drawRect(Rect.fromLTWH(cx, cy, cell * 7, cell * 7), r);
canvas.drawRect(
Rect.fromLTWH(cx + cell, cy + cell, cell * 5, cell * 5),
Paint()..color = const Color(0xFFF9FAFB));
canvas.drawRect(
Rect.fromLTWH(cx + cell * 2, cy + cell * 2, cell * 3, cell * 3),
r);
}
corner(0, 0);
corner(size.width - cell * 7, 0);
corner(0, size.height - cell * 7);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class _BarcodePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final widths = <double>[2, 1, 3, 1, 2, 4, 1, 2, 1, 3, 2, 1, 4, 1, 2, 3, 1,
2, 1, 3, 2, 4, 1, 2, 1, 3, 2, 1];
final paint = Paint()..color = const Color(0xFF111827);
double x = 0;
final unit = size.width / widths.fold<double>(0, (a, b) => a + b * 1.6);
for (int i = 0; i < widths.length; i++) {
final w = widths[i] * unit;
if (i.isEven) {
canvas.drawRect(Rect.fromLTWH(x, 0, w, size.height), paint);
}
x += w * 1.6;
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// ─── USAGE ───────────────────────────────────────────────────────
// Standard QR Code
// CodeScanCard(
// initialType: CodeScanType.qr,
// )
// Barcode tab
// CodeScanCard(
// initialType: CodeScanType.barcode,
// )
// With brand logo overlay
// CodeScanCard(
// style: CodeScanStyle.withLogo,
// )
// Compact (no security footer)
// CodeScanCard(
// style: CodeScanStyle.compact,
// )Dashboard metric cards in 4 styles — Simple, Icon, Trend, and Progress
View component →Dismissible alert banners in 5 styles — Info, Success, Warning, and Danger
View component →Clean email + password login with form validation and loading state
View component →