promotion_card 1.0.0
promotion_card: ^1.0.0 copied to clipboard
A flexible Flutter widget for promotion cards with built-in variants, RTL-aware overlay positioning, and Material 3 theme extension support.
import 'package:flutter/material.dart';
import 'package:promotion_card/promotion_card.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Promotion Card Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: Colors.deepOrange,
useMaterial3: true,
extensions: [_buildPromotionCardTheme()],
),
home: const DemoScreen(),
);
}
}
/// App-wide theme defaults for all four variants, mirroring the pattern used
/// in a real app: gradient/image backgrounds, shared overlay position,
/// and per-variant arrow colors.
PromotionCardThemeData _buildPromotionCardTheme() {
const overlayPosition = PromotionCardOverlayPosition(
top: 8,
end: 16,
width: 120,
height: 120,
);
const sharedLeading = PromotionCardStyle(
leadingWidth: 40,
leadingHeight: 40,
leadingEndPadding: 8,
);
return PromotionCardThemeData(
variants: {
PromotionCardVariant.primary: PromotionCardVariantData(
style: PromotionCardStyle(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFF7043), Color(0xFFBF360C)],
),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
padding: const EdgeInsetsDirectional.fromSTEB(16, 14, 16, 14),
arrowColor: Colors.white,
showOverlay: false,
).merge(sharedLeading),
),
PromotionCardVariant.soft: PromotionCardVariantData(
style: PromotionCardStyle(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFFFF3E0), Color(0xFFFFE0B2)],
),
showOverlay: true,
overlayPosition: overlayPosition,
padding: const EdgeInsetsDirectional.fromSTEB(16, 14, 16, 14),
arrowColor: Colors.deepOrange,
borderRadius: BorderRadius.zero,
).merge(sharedLeading),
),
PromotionCardVariant.imageHero: PromotionCardVariantData(
style: PromotionCardStyle(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xB3FCFCFC),
Color(0xE2FFE2D4),
Color(0xFFFFE2D4),
Color(0xB3FCFCFC),
],
stops: [0.0, 0.32, 0.75, 1.0],
),
showOverlay: true,
overlayPosition: overlayPosition,
padding: const EdgeInsetsDirectional.fromSTEB(16, 14, 16, 14),
arrowColor: Colors.deepOrange,
borderRadius: BorderRadius.zero,
).merge(sharedLeading),
),
PromotionCardVariant.minimal: PromotionCardVariantData(
style: PromotionCardStyle(
backgroundColor: const Color(0xFFF5F5F5),
showOverlay: true,
overlayPosition: overlayPosition,
padding: const EdgeInsetsDirectional.fromSTEB(16, 14, 16, 14),
arrowColor: Colors.deepOrange,
borderRadius: BorderRadius.zero,
).merge(sharedLeading),
),
},
);
}
// ── Demo screen ───────────────────────────────────────────────────────────────
class DemoScreen extends StatelessWidget {
const DemoScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Promotion Card Demo')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_label(context, 'primary variant'),
const SizedBox(height: 8),
const _RamadanCard(),
const SizedBox(height: 24),
_label(context, 'soft variant'),
const SizedBox(height: 8),
const _EidCard(),
const SizedBox(height: 24),
_label(context, 'imageHero variant'),
const SizedBox(height: 8),
const _SummerCard(),
const SizedBox(height: 24),
_label(context, 'minimal variant'),
const SizedBox(height: 8),
const _BackToSchoolCard(),
const SizedBox(height: 24),
_label(context, 'custom style override'),
const SizedBox(height: 8),
const _CustomCard(),
const SizedBox(height: 32),
],
),
);
}
Widget _label(BuildContext context, String text) => Text(
text,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}
// ── Variant cards ─────────────────────────────────────────────────────────────
class _RamadanCard extends StatelessWidget {
const _RamadanCard();
@override
Widget build(BuildContext context) {
return PromotionCard(
variant: PromotionCardVariant.primary,
leading: const Icon(Icons.nights_stay, color: Colors.white),
title: const Text(
'Ramadan Flash',
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
description: const Text(
'Bulk discounts — save more on your store essentials.',
style: TextStyle(color: Colors.white70),
),
onTap: () {},
body: const _ProductRow(color: Color(0xFFFBE9E7)),
);
}
}
class _EidCard extends StatelessWidget {
const _EidCard();
@override
Widget build(BuildContext context) {
return PromotionCard(
variant: PromotionCardVariant.soft,
leading: const Icon(Icons.star, color: Color(0xFFD383E1)),
overlay: const Icon(Icons.star, size: 120, color: Color(0x22D383E1)),
title: const Text(
'Eid Essentials',
style: TextStyle(
color: Colors.deepOrange,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
description: const Text(
'Special wholesale pricing on Eid sweets and drinks.',
style: TextStyle(color: Colors.brown),
),
onTap: () {},
body: const _ProductRow(color: Color(0xFFFFF8E1)),
);
}
}
class _SummerCard extends StatelessWidget {
const _SummerCard();
@override
Widget build(BuildContext context) {
return PromotionCard(
variant: PromotionCardVariant.imageHero,
leading: const Icon(Icons.wb_sunny, color: Color(0xFF4898A2)),
overlay: const Icon(Icons.wb_sunny, size: 120, color: Color(0x224898A2)),
title: const Text(
'Summer Refresh',
style: TextStyle(
color: Color(0xFF4898A2),
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
description: const Text(
'Cool down with seasonal picks for your shelves.',
style: TextStyle(color: Colors.blueGrey),
),
onTap: () {},
body: const _ProductRow(color: Color(0xFFE0F2F1)),
);
}
}
class _BackToSchoolCard extends StatelessWidget {
const _BackToSchoolCard();
@override
Widget build(BuildContext context) {
return PromotionCard(
variant: PromotionCardVariant.minimal,
leading: const Icon(Icons.school, color: Color(0xFFFABC51)),
overlay: const Icon(Icons.school, size: 120, color: Color(0x22FABC51)),
title: const Text(
'Back to School',
style: TextStyle(
color: Colors.deepOrange,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
description: const Text(
'Stock up on classroom and office must-haves.',
style: TextStyle(color: Colors.brown),
),
onTap: () {},
body: const _ProductRow(color: Color(0xFFFFF8E1)),
);
}
}
/// Demonstrates a per-widget [PromotionCardStyle] override — no variant or
/// theme involvement.
class _CustomCard extends StatelessWidget {
const _CustomCard();
@override
Widget build(BuildContext context) {
return PromotionCard(
leading: const Icon(Icons.palette, color: Colors.white),
title: const Text(
'Custom Style',
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
description: const Text(
'Gradient and border radius applied via PromotionCardStyle.',
style: TextStyle(color: Colors.white70),
),
onTap: () {},
style: PromotionCardStyle(
gradient: const LinearGradient(
colors: [Color(0xFF6A1B9A), Color(0xFF283593)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
arrowColor: Colors.white,
padding: const EdgeInsetsDirectional.all(20),
),
body: const _ProductRow(color: Color(0xFFEDE7F6)),
);
}
}
// ── Shared placeholder ────────────────────────────────────────────────────────
class _ProductRow extends StatelessWidget {
const _ProductRow({required this.color});
final Color color;
@override
Widget build(BuildContext context) {
return Container(
height: 100,
color: color,
child: Row(
children: List.generate(
4,
(i) => Expanded(
child: Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.06),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
),
),
),
),
);
}
}