flutter_coupon_card 0.1.0
flutter_coupon_card: ^0.1.0 copied to clipboard
A highly customizable coupon, voucher, ticket, promotion and discount card widget for Flutter, inspired by modern food delivery and e-commerce apps.
import 'package:flutter/material.dart';
import 'package:flutter_coupon_card/flutter_coupon_card.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatefulWidget {
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
ThemeMode _mode = ThemeMode.light;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter_coupon_card',
debugShowCheckedModeBanner: false,
themeMode: _mode,
theme: ThemeData(colorSchemeSeed: const Color(0xFFFF5A5F)),
darkTheme: ThemeData.dark(useMaterial3: true),
home: GalleryPage(
isDark: _mode == ThemeMode.dark,
onToggleTheme: () => setState(
() => _mode = _mode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark,
),
),
);
}
}
class GalleryPage extends StatelessWidget {
const GalleryPage({
super.key,
required this.isDark,
required this.onToggleTheme,
});
final bool isDark;
final VoidCallback onToggleTheme;
void _snack(BuildContext context, String message) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(
content: Text(message),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 1),
));
}
@override
Widget build(BuildContext context) {
// Center content and cap width so it reads well on tablet / desktop / web.
return Scaffold(
backgroundColor: isDark ? const Color(0xFF121216) : const Color(0xFFF4F5F7),
appBar: AppBar(
title: const Text('flutter_coupon_card'),
actions: [
IconButton(
tooltip: 'Toggle theme',
onPressed: onToggleTheme,
icon: Icon(isDark ? Icons.light_mode : Icons.dark_mode),
),
],
),
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560),
child: ListView(
padding: const EdgeInsets.symmetric(vertical: 16),
children: [
_section('1 · Food delivery coupon'),
CouponCard(
style: CouponCardStyle.foodDelivery,
discountText: '50% OFF',
title: 'Up to \$10 off',
subtitle: 'On your first food order',
minimumOrder: 'Min. order \$15',
usageLimit: 'One time use per account',
code: 'YUMMY50',
percentageText: '50%',
enableFadeIn: true,
onTap: () => _snack(context, 'Coupon tapped'),
onCodeCopied: (c) => _snack(context, 'Copied $c'),
),
_section('2 · Shopping voucher (scalloped)'),
VoucherCard(
style: CouponCardStyle.shopping,
discountText: '\$25 OFF',
title: 'Mega Sale Voucher',
subtitle: 'Storewide electronics',
minimumOrder: 'Min. spend \$100',
code: 'MEGA25',
trailing: const CouponBarcode(data: 'MEGA25-0099'),
onCodeCopied: (c) => _snack(context, 'Copied $c'),
),
_section('3 · Event ticket (perforated)'),
TicketCard(
style: CouponCardStyle.event,
shape: CouponShape.eventTicket,
title: 'Neon Music Festival',
subtitle: 'Sat 22 Aug · 7:00 PM',
description: 'Gate A · Row 12 · Seat 34',
code: 'NMF-88213',
trailing: const CouponQr(data: 'NMF-88213'),
),
_section('4 · Airline boarding pass'),
_boardingPass(context),
_section('5 · Membership card'),
CouponCard(
style: CouponCardStyle.premium,
height: 180,
header: const _MembershipHeader(),
body: const SizedBox.shrink(),
footer: const _MembershipFooter(),
),
_section('6 · Gift voucher'),
VoucherCard(
shape: CouponShape.giftVoucher,
theme: CouponTheme.fromColor(const Color(0xFFEC4899)),
discountText: 'GIFT',
title: '\$50 Gift Card',
subtitle: 'Happy Birthday! 🎁',
condition: 'Redeemable in-store & online',
code: 'GIFT-7788-2231',
percentageText: '\$50',
),
_section('7 · Movie ticket (zig-zag stub)'),
TicketCard(
shape: CouponShape.movieTicket,
theme: CouponTheme.fromColor(
const Color(0xFFF59E0B),
brightness: CouponBrightness.dark,
).copyWith(backgroundColor: const Color(0xFF201A12)),
title: 'Dune: Part Three',
subtitle: 'IMAX · Screen 4',
description: '18:40 · Seat H7',
code: 'MOV-4471',
dividerStyle: CouponDividerStyle.dashed,
trailing: const CouponQr(
data: 'MOV-4471',
color: Color(0xFFF59E0B),
background: Color(0xFF201A12),
),
),
_section('8 · Glassmorphism'),
Container(
height: 200,
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(
colors: [Color(0xFF7C3AED), Color(0xFF2563EB)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
alignment: Alignment.center,
child: CouponCard(
style: CouponCardStyle.glassmorphism,
width: 340,
discountText: '20% OFF',
title: 'Frosted Deal',
subtitle: 'Limited edition',
code: 'GLASS20',
),
),
_section('9 · Loading skeleton (shimmer)'),
const CouponCard(isLoading: true),
const SizedBox(height: 32),
],
),
),
),
);
}
Widget _boardingPass(BuildContext context) {
return CouponCard(
style: CouponCardStyle.travel,
height: 170,
header: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
_Airport(code: 'PNH', city: 'Phnom Penh'),
Icon(Icons.flight_takeoff_rounded, color: Color(0xFF2D6CDF)),
_Airport(code: 'SIN', city: 'Singapore'),
],
),
body: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
_Field(label: 'FLIGHT', value: 'QR 934'),
_Field(label: 'GATE', value: 'A12'),
_Field(label: 'SEAT', value: '14C'),
_Field(label: 'BOARDING', value: '08:20'),
],
),
footer: const CouponBarcode(data: 'PNHSIN934-14C', width: double.infinity),
);
}
Widget _section(String title) => Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 8),
child: Text(
title,
style: const TextStyle(fontWeight: FontWeight.w800, fontSize: 15),
),
);
}
class _Airport extends StatelessWidget {
const _Airport({required this.code, required this.city});
final String code;
final String city;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(code,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: Color(0xFF2D6CDF))),
Text(city, style: const TextStyle(fontSize: 11, color: Colors.black54)),
],
);
}
}
class _Field extends StatelessWidget {
const _Field({required this.label, required this.value});
final String label;
final String value;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label,
style: const TextStyle(
fontSize: 9,
letterSpacing: 1,
color: Colors.black45,
fontWeight: FontWeight.w700)),
const SizedBox(height: 2),
Text(value,
style:
const TextStyle(fontSize: 14, fontWeight: FontWeight.w800)),
],
);
}
}
class _MembershipHeader extends StatelessWidget {
const _MembershipHeader();
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('GOLD MEMBER',
style: TextStyle(
color: Color(0xFFE7C46B),
fontWeight: FontWeight.w900,
letterSpacing: 2)),
Icon(Icons.workspace_premium_rounded, color: Color(0xFFE7C46B)),
],
);
}
}
class _MembershipFooter extends StatelessWidget {
const _MembershipFooter();
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text('4821 •••• •••• 9930',
style: TextStyle(
color: Colors.white,
fontSize: 18,
letterSpacing: 3,
fontWeight: FontWeight.w700)),
SizedBox(height: 6),
Text('NHEB PANHA · Member since 2021',
style: TextStyle(color: Colors.white54, fontSize: 12)),
],
);
}
}