promo_carousel 1.2.0
promo_carousel: ^1.2.0 copied to clipboard
A flexible, customizable promotional carousel package for Flutter.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:promo_carousel/promo_carousel.dart';
import 'l10n/app_localizations.dart';
import 'video_examples_page.dart';
void main() {
// Enable debug mode for testing
PromoCarousel.debugMode = true;
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Promo Carousel Demo',
localizationsDelegates: const [
PromoLocalizationsDelegate(),
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('en'), Locale('my')],
locale: const Locale('my'),
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const DemoPage(),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
String _lastAction = 'None';
int _slidesViewed = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Promo Carousel Demo'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () async {
await PromoCarousel.resetAll();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('All slides reset')),
);
}
},
tooltip: 'Reset all slides',
),
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const VideoExamplesPage();
},
),
);
},
icon: const Icon(Icons.video_call),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Text(
'Last Action: $_lastAction',
style: Theme.of(context).textTheme.titleMedium,
),
Text('Slides Viewed: $_slidesViewed'),
const SizedBox(height: 16),
// Hero Animation Target
Center(
child: Hero(
tag: 'promo_image_hero',
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.deepPurple.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.image,
size: 40,
color: Colors.deepPurple,
),
),
),
),
const SizedBox(height: 24),
_buildButton(
context,
'Hero Animation Demo',
() => _showHeroAnimationCarousel(context),
),
_buildButton(
context,
'Show Standard Carousel',
() => _showStandardCarousel(context),
),
_buildButton(
context,
'Show with Auto-Advance',
() => _showAutoAdvanceCarousel(context),
),
_buildButton(
context,
'Show with Custom Widgets',
() => _showCustomCarousel(context),
),
_buildButton(
context,
'Show Bottom Sheet',
() => _showBottomSheetCarousel(context),
),
_buildButton(
context,
'Show Fullscreen',
() => _showFullscreenCarousel(context),
),
_buildButton(
context,
'Show with Analytics',
() => _showAnalyticsCarousel(context),
),
_buildButton(
context,
'Show from JSON (Remote Config)',
() => _showJsonCarousel(context),
),
_buildButton(
context,
'Preview Mode (All Slides)',
() => _showPreviewCarousel(context),
),
const Divider(),
_buildButton(
context,
'3D Transitions (Cube)',
() => _show3DTransitionCarousel(context, TransitionType.cube),
),
_buildButton(
context,
'3D Transitions (Flip)',
() => _show3DTransitionCarousel(context, TransitionType.flip3D),
),
_buildButton(
context,
'Audio Feedback',
() => _showAudioCarousel(context),
),
_buildButton(
context,
'Geolocation Rules (US Only)',
() => _showGeolocationCarousel(context, 'US'),
),
],
),
);
}
Widget _buildButton(
BuildContext context,
String text,
VoidCallback onPressed,
) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(padding: const EdgeInsets.all(16)),
child: Text(text),
),
);
}
void _showStandardCarousel(BuildContext context) {
PromoCarousel.show(
context: context,
slides: _getStandardSlides(),
config: const PromoCarouselConfig(
showCloseButton: true,
showDontShowAgain: true,
enableHaptics: true,
),
onAction: _handleAction,
onDismiss: () => print('Carousel dismissed'),
);
}
void _showAutoAdvanceCarousel(BuildContext context) {
PromoCarousel.show(
context: context,
slides: _getStandardSlides(),
config: const PromoCarouselConfig(
autoAdvance: true,
autoAdvanceDuration: Duration(seconds: 3),
pauseOnInteraction: true,
showProgressBar: true,
progressBarPosition: ProgressPosition.top,
),
onAction: _handleAction,
);
}
void _showCustomCarousel(BuildContext context) {
// Simulated user data
final userZodiacSign = 'β Cancer';
final userTopSearch = 'why am I crying at 3am';
PromoCarousel.show(
context: context,
slides: [
PromoSlide(
id: 'zodiac_preview',
title: 'Late-Night Most\nGoogled\nSecrets',
visualType: PromoVisualType.custom,
cta: PromoCTA(
text: 'Face your secrets',
action: PromoAction.openFeature,
target: 'zodiac_detail',
),
rules: const PromoRules(showOnce: true),
customContentBuilder: (context) {
return _buildZodiacWidget(userZodiacSign, userTopSearch);
},
),
..._getStandardSlides().skip(1),
],
config: PromoCarouselConfig.announcement(),
onAction: _handleAction,
);
}
void _showBottomSheetCarousel(BuildContext context) {
PromoCarousel.showBottomSheet(
context: context,
slides: _getStandardSlides(),
config: const PromoCarouselConfig(
showSkipButton: true,
backdropBlur: 5.0,
),
onAction: _handleAction,
);
}
void _showFullscreenCarousel(BuildContext context) {
PromoCarousel.showFullscreen(
context: context,
slides: _getStandardSlides(),
config: PromoCarouselConfig.onboarding(),
onAction: _handleAction,
);
}
void _showAnalyticsCarousel(BuildContext context) {
PromoCarousel.show(
context: context,
slides: _getStandardSlides(),
analytics: PromoCarouselAnalytics(
onSlideViewed: (slideId, index) async {
print('π Viewed: $slideId at index $index');
await Future.delayed(const Duration(milliseconds: 500));
if (context.mounted) {
setState(() => _slidesViewed++);
}
},
onSlideSkipped: (slideId, index) {
print('βοΈ Skipped: $slideId');
},
onCTAClicked: (slideId, action, target) {
print('π CTA Clicked: $slideId - $action -> $target');
},
onCarouselCompleted: (viewedSlides) {
print('β
Completed! Viewed: $viewedSlides');
},
onCarouselDismissed: (lastIndex, viewedSlides) {
print('β Dismissed at $lastIndex. Viewed: $viewedSlides');
},
onSkipAll: () {
print('β© Skip all clicked');
},
),
config: const PromoCarouselConfig(showSkipButton: true),
onAction: _handleAction,
);
}
void _showJsonCarousel(BuildContext context) {
// Simulate loading from remote config
final jsonData = [
{
'id': 'remote_promo_1',
'title': 'New Feature Alert!',
'subtitle': 'Loaded from remote config',
'visualType': 'featureHighlight',
'cta': {
'text': 'Try Now',
'action': 'openFeature',
'target': 'new_feature',
},
'rules': {'showOnce': true, 'minAppVersion': '1.0.0'},
},
];
final slides = PromoCarousel.fromJson(jsonData);
PromoCarousel.show(
context: context,
slides: [...slides, ..._getStandardSlides()],
onAction: _handleAction,
);
}
void _showPreviewCarousel(BuildContext context) {
PromoCarousel.showPreview(
context: context,
slides: _getStandardSlides(),
config: const PromoCarouselConfig(showProgressBar: true),
onAction: _handleAction,
);
}
void _handleAction(PromoAction action, String? target) {
setState(() {
_lastAction = '$action -> $target';
});
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Action: $action β $target')));
}
void _show3DTransitionCarousel(BuildContext context, TransitionType type) {
PromoCarousel.show(
context: context,
slides: _getStandardSlides(),
config: PromoCarouselConfig(
transitionType: type,
transitionDuration: const Duration(milliseconds: 800),
showCloseButton: true,
),
onAction: _handleAction,
);
}
void _showAudioCarousel(BuildContext context) {
PromoCarousel.show(
context: context,
slides: [
PromoSlide(
id: 'audio_slide',
title: 'Per-Slide Sound',
subtitle: 'This slide plays a unique sound when shown',
visualType: PromoVisualType.featureHighlight,
audioAsset: 'sounds/alert.mp3',
cta: PromoCTA(text: 'Nice!', action: PromoAction.close),
rules: const PromoRules(),
),
..._getStandardSlides(),
],
config: const PromoCarouselConfig(
ctaClickSound: 'sounds/cta_click.mp3',
slideChangeSound: 'sounds/page_turn.mp3',
showCloseButton: true,
),
onAction: _handleAction,
);
}
void _showGeolocationCarousel(BuildContext context, String country) {
final slides = [
PromoSlide(
id: 'us_only_offer',
title: 'πΊπΈ US Exclusive Offer',
subtitle: 'Only visible for users in the United States',
visualType: PromoVisualType.featureHighlight,
cta: PromoCTA(text: 'Claim Offer', action: PromoAction.close),
rules: const PromoRules(countries: ['US']),
),
PromoSlide(
id: 'global_offer',
title: 'Global Feature',
subtitle: 'Visible to everyone everywhere',
visualType: PromoVisualType.featureHighlight,
cta: PromoCTA(text: 'Got it', action: PromoAction.close),
rules: const PromoRules(),
),
];
PromoCarousel.show(
context: context,
slides: slides,
currentCountry: country,
onAction: _handleAction,
);
}
void _showHeroAnimationCarousel(BuildContext context) {
PromoCarousel.show(
context: context,
slides: [
PromoSlide(
id: 'hero_demo',
title: 'Hero Animation',
subtitle: 'The image will animate back to the page!',
visualType: PromoVisualType.image,
imageAsset:
'https://image.shutterstock.com/image-vector/super-promo-3d-editable-text-260nw-2442897457.jpg',
heroTag: 'promo_image_hero',
cta: PromoCTA(text: 'Close and Watch', action: PromoAction.close),
rules: const PromoRules(),
),
],
config: const PromoCarouselConfig(enableHeroAnimations: true),
onAction: _handleAction,
);
}
List<PromoSlide> _getStandardSlides() {
return [
PromoSlide(
id: 'welcome',
title: 'Welcome to Our App!',
subtitle: 'Discover amazing features',
visualType: PromoVisualType.featureHighlight,
cta: PromoCTA(
text: 'Get Started',
action: PromoAction.navigate,
target: '/home',
),
rules: const PromoRules(showOnce: true, showAfterDate: null),
),
PromoSlide(
id: 'feature_2',
title: 'Search Made Easy',
subtitle: 'Find what you need instantly',
visualType: PromoVisualType.searchBar,
cta: PromoCTA(
text: 'Try Search',
action: PromoAction.openFeature,
target: 'search',
),
rules: const PromoRules(showOnce: false),
),
PromoSlide(
id: 'premium_offer',
title: 'Unlock Premium',
subtitle: 'Get access to exclusive features',
visualType: PromoVisualType.featureHighlight,
cta: PromoCTA(text: 'Upgrade Now', action: PromoAction.openPaywall),
rules: const PromoRules(showOnce: true, userSegments: ['free_tier']),
),
];
}
Widget _buildZodiacWidget(String sign, String search) {
return Container(
height: 200,
decoration: BoxDecoration(
color: Colors.grey[900],
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(24),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.search, color: Colors.grey[400], size: 20),
const SizedBox(width: 8),
Text(
'your search history is loading...',
style: TextStyle(color: Colors.grey[400], fontSize: 14),
),
],
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.auto_awesome, color: Colors.blue, size: 20),
const SizedBox(width: 8),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'The Google searches',
style: TextStyle(color: Colors.grey[400], fontSize: 13),
),
RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 13,
color: Colors.white,
),
children: [
const TextSpan(text: 'your zodiac sign '),
TextSpan(
text: sign,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
Text(
'hopes that it dies with them',
style: TextStyle(color: Colors.grey[400], fontSize: 13),
),
],
),
),
],
),
],
),
);
}
}