A flexible, production-ready Flutter package for creating beautiful promotional carousels with support for user-specific widget injection. Perfect for onboarding flows, feature announcements, and personalized promotions.

pub package License: MIT

✨ Features

Core Features

  • 🎯 Modal Overlay - Beautiful dialog-style presentation with dimmed background
  • πŸ“± Swipeable Carousel - Smooth PageView-based navigation
  • 🎨 Built-in Visual Types - Images, animations, search bars, videos, and more
  • πŸ”§ Custom Widget Injection - Inject user-specific widgets without coupling
  • πŸ’Ύ Persistent State - Tracks "show once" slides using SharedPreferences
  • πŸŒ— Theme Support - Automatically adapts to light and dark themes

New in v1.2.0

  • 🎭 Hero Animations - Smooth transitions between the carousel and host app
  • πŸ”Š Audio Feedback - Global and per-slide sound effects
  • 🧊 3D Transitions - Cube and Flip 3D page transitions
  • πŸ—ΊοΈ Geolocation Rules - Filter slides by country and region
  • 🌐 Internationalization - Built-in support for 14 languages (EN, MY, ES, FR, DE, JA, ZH, KO, HI, AR, PT, RU, TH, VI)
  • πŸ–ΌοΈ Enhanced Images - Transparently handle local assets OR network images with caching

πŸ“± Platform Support

Feature Android iOS Web macOS Windows Linux
Core Carousel βœ… βœ… βœ… βœ… βœ… βœ…
Images (Local/Network) βœ… βœ… βœ… βœ… βœ… βœ…
Animations βœ… βœ… βœ… βœ… βœ… βœ…
Video Support βœ… βœ… βœ… βœ… ❌ ❌
Audio Feedback βœ… βœ… βœ… βœ… βœ… βœ…

πŸ“Έ Preview

Promo Carousel Demo

πŸš€ Getting Started

Installation

Add to your pubspec.yaml:

dependencies:
  promo_carousel: ^1.2.0

Then run:

flutter pub get

Basic Usage

import 'package:promo_carousel/promo_carousel.dart';

PromoCarousel.show(
  context: context,
  slides: [
    PromoSlide(
      id: 'welcome',
      title: 'Welcome to Our App',
      subtitle: 'Discover amazing features',
      visualType: PromoVisualType.featureHighlight,
      cta: PromoCTA(
        text: 'Get Started',
        action: PromoAction.close,
      ),
      rules: PromoRules(showOnce: true),
    ),
  ],
  onAction: (action, target) {
    print('Action: $action');
  },
);

πŸ“– Documentation

PromoSlide

The core model representing a single carousel slide:

PromoSlide(
  id: 'unique_id',                    // Unique identifier
  title: 'Main Title',                // Required title text
  subtitle: 'Optional description',   // Optional subtitle
  visualType: PromoVisualType.image,  // Built-in visual type
  cta: PromoCTA(                      // Call-to-action button
    text: 'Button Text',
    action: PromoAction.navigate,
    target: '/route-name',
  ),
  rules: PromoRules(                  // Display rules
    showOnce: true,
    minAppVersion: '1.2.0',
    maxAppVersion: '2.0.0',
    showAfterDate: DateTime(2025, 1, 1),
    showBeforeDate: DateTime(2025, 12, 31),
    userSegments: ['premium', 'beta'],
    deviceTypes: [DeviceType.mobile],
    countries: ['US', 'CA'],           // Geolocation: Countries
    regions: ['California', 'NY'],      // Geolocation: Regions
  ),
  audioAsset: 'assets/sounds/promo.mp3', // Slide-specific sound
  heroTag: 'promo_hero_image',         // Hero animation tag
  customContentBuilder: (context) {   // Optional custom widget
    return YourCustomWidget();
  },
  semanticLabel: 'Welcome screen',    // Accessibility
  metadata: {'experiment_id': '001'}, // A/B testing
)

Visual Types

Built-in visual types available:

  • PromoVisualType.image - Display an image asset
  • PromoVisualType.animation - Show an animated visual
  • PromoVisualType.searchBar - Search bar animation
  • PromoVisualType.featureHighlight - Gradient highlight
  • PromoVisualType.video - Video player (requires video asset)

    Important

    Video support (PromoVisualType.video) is currently not available on Windows and Linux platforms due to video_player package limitations.

  • PromoVisualType.custom - Use with customContentBuilder

Actions

Available CTA actions:

  • PromoAction.navigate - Navigate to a route
  • PromoAction.openFeature - Open a specific feature
  • PromoAction.openPaywall - Show paywall/upgrade screen
  • PromoAction.close - Simply close the modal
  • PromoAction.custom - Custom action with callback

Transition Types

  • TransitionType.slide - Horizontal slide
  • TransitionType.fade - Cross-fade effect
  • TransitionType.scale - Scaling animation
  • TransitionType.rotate - Rotation effect
  • TransitionType.cube - (v1.2) 3D Cube transition
  • TransitionType.flip3D - (v1.2) 3D Flip transition

Configuration

Customize appearance and behavior:

PromoCarousel.show(
  context: context,
  slides: slides,
  config: PromoCarouselConfig(
    // Appearance
    borderRadius: 24.0,
    elevation: 8.0,
    barrierColor: Color(0x80000000),
    backdropBlur: 10.0,
    displayMode: DisplayMode.dialog,

    // Behavior
    barrierDismissible: true,
    showCloseButton: true,
    showDontShowAgain: false,
    showSkipButton: true,
    enableSwipeGestures: true,
    enableTapToAdvance: true,

    // Auto-advance
    autoAdvance: true,
    autoAdvanceDuration: Duration(seconds: 5),
    pauseOnInteraction: true,

    // Audio Feedback (v1.2)
    slideChangeSound: 'assets/sounds/page_turn.mp3',
    ctaClickSound: 'assets/sounds/click.mp3',

    // Hero Animations (v1.2)
    enableHeroAnimations: true,

    // Progress
    showProgressBar: true,
    progressBarPosition: ProgressPosition.top,

    // Accessibility
    enableHaptics: true,
    hapticFeedbackType: HapticType.light,
    respectReducedMotion: true,
  ),
);

Pre-built Configs

Use factory constructors for common scenarios:

// Onboarding flow
config: PromoCarouselConfig.onboarding()

// Feature announcement
config: PromoCarouselConfig.announcement()

// Marketing promotion
config: PromoCarouselConfig.marketing()

🎯 Advanced Usage

Custom User-Specific Widgets

The key feature - inject personalized content without coupling the package to your data models:

PromoSlide(
  id: 'zodiac_preview',
  title: 'Your Personal Insights',
  subtitle: 'Based on your zodiac sign',
  visualType: PromoVisualType.custom,
  cta: PromoCTA(
    text: 'Reveal',
    action: PromoAction.openFeature,
    target: 'zodiac_detail',
  ),
  rules: PromoRules(showOnce: true),
  customContentBuilder: (context) {
    // Access your app's user data here
    final user = Provider.of<User>(context);

    return ZodiacPreviewCard(
      sign: user.zodiacSign,
      topSearch: user.topSearch,
    );
  },
)

Analytics Tracking

Track user interactions with comprehensive callbacks:

PromoCarousel.show(
  context: context,
  slides: slides,
  analytics: PromoCarouselAnalytics(
    onSlideViewed: (slideId, index) {
      print('Viewed: $slideId at index $index');
      analytics.logEvent('promo_slide_viewed', {'slide_id': slideId});
    },
    onSlideSkipped: (slideId, index) {
      analytics.logEvent('promo_slide_skipped');
    },
    onCTAClicked: (slideId, action, target) {
      analytics.logEvent('promo_cta_clicked', {
        'slide_id': slideId,
        'action': action.name,
        'target': target,
      });
    },
    onCarouselCompleted: (viewedSlides) {
      analytics.logEvent('promo_completed', {
        'slides_viewed': viewedSlides.length,
      });
    },
    onCarouselDismissed: (lastIndex, viewedSlides) {
      analytics.logEvent('promo_dismissed', {
        'last_index': lastIndex,
        'completion_rate': viewedSlides.length / slides.length,
      });
    },
    onSkipAll: () {
      analytics.logEvent('promo_skipped_all');
    },
  ),
);

Auto-Advance with Pause

Automatically progress through slides:

PromoCarousel.show(
  context: context,
  slides: slides,
  config: PromoCarouselConfig(
    autoAdvance: true,
    autoAdvanceDuration: Duration(seconds: 5),
    pauseOnInteraction: true, // Pause when user interacts
    showProgressBar: true,
  ),
);

Display Modes

Choose how to present the carousel:

// Dialog (default)
PromoCarousel.show(context: context, slides: slides);

// Bottom sheet
PromoCarousel.showBottomSheet(context: context, slides: slides);

// Fullscreen
PromoCarousel.showFullscreen(context: context, slides: slides);

Remote Config Integration

Load slides from JSON:

// From your Firebase Remote Config, API, etc.
final jsonData = await remoteConfig.getJson('promo_slides');

// Parse to slides
final slides = PromoCarousel.fromJson(jsonData);

// Show carousel
PromoCarousel.show(context: context, slides: slides);

Conditional Display Rules

Advanced filtering based on multiple criteria:

PromoSlide(
  id: 'premium_offer',
  title: 'Upgrade to Premium',
  rules: PromoRules(
    showOnce: true,
    minAppVersion: '1.5.0',
    maxAppVersion: '2.0.0',
    showAfterDate: DateTime(2025, 1, 1),
    showBeforeDate: DateTime(2025, 12, 31),
    userSegments: ['free_tier', 'trial'],
    deviceTypes: [DeviceType.mobile, DeviceType.tablet],
    customCondition: () {
      // Custom logic
      return user.hasUsedAppForDays(7);
    },
  ),
  // ...
)

Custom Page Indicators

Replace the default page counter:

PromoCarousel.show(
  context: context,
  slides: slides,
  config: PromoCarouselConfig(
    indicatorBuilder: (context, currentPage, totalPages) {
      return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: List.generate(totalPages, (index) {
          return Container(
            margin: EdgeInsets.symmetric(horizontal: 4),
            width: index == currentPage ? 12 : 8,
            height: 8,
            decoration: BoxDecoration(
              color: index == currentPage ? Colors.blue : Colors.grey,
              borderRadius: BorderRadius.circular(4),
            ),
          );
        }),
      );
    },
  ),
);

Preview Mode

Test all slides regardless of rules:

PromoCarousel.showPreview(
  context: context,
  slides: slides,
  // Ignores showOnce, date ranges, version checks, etc.
);

Manual State Management

Control slide visibility programmatically:

// Check if slide was seen
final seen = await PromoCarousel.hasSeenSlide('welcome');

// Reset a specific slide
await PromoCarousel.resetSlide('welcome');

// Reset multiple slides
await PromoCarousel.resetSlides(['slide1', 'slide2']);

// Reset all slides
await PromoCarousel.resetAll();

// Get all seen slides
final seenSlides = await PromoCarousel.getSeenSlides();

// Export analytics
final analytics = await PromoCarousel.exportAnalytics();

Debug Mode

Enable detailed logging during development:

void main() {
  PromoCarousel.debugMode = true;
  runApp(MyApp());
}

Internationalization (v1.2)

The package includes built-in localization. To enable it, add the delegates to your MaterialApp:

MaterialApp(
  localizationsDelegates: [
    PromoLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    // ...
  ],
  supportedLocales: [
    Locale('en'),
    Locale('my'),
  ],
)

🎨 Theming

The carousel automatically adapts to your app's theme:

MaterialApp(
  theme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
  // Carousel will adapt to both themes
)

πŸ“± Use Cases

Perfect for:

  • βœ… Onboarding flows - Welcome new users
  • βœ… Feature announcements - Showcase new capabilities
  • βœ… Personalized promotions - Zodiac insights, user stats, achievements
  • βœ… Contextual tips - Show relevant guidance
  • βœ… Marketing campaigns - Promote offers and upgrades
  • βœ… Product tours - Guide users through features
  • βœ… A/B testing - Test different messaging with metadata tracking
  • βœ… Seasonal campaigns - Date-based promotions

πŸ—οΈ Architecture

The package follows clean architecture principles:

lib/
β”œβ”€β”€ models/
β”‚   β”œβ”€β”€ promo_slide.dart              # Core data models
β”‚   └── promo_carousel_config.dart    # Configuration classes
β”œβ”€β”€ controllers/
β”‚   └── promo_carousel_controller.dart # State management
β”œβ”€β”€ widgets/
β”‚   β”œβ”€β”€ promo_carousel_modal.dart     # Main modal widget
β”‚   └── promo_slide_content.dart      # Content renderer
└── promo_carousel.dart               # Public API

🚫 What This Package Does NOT Do

To keep the package generic and reusable:

  • ❌ No Firebase or remote config integration (but provides JSON parsing)
  • ❌ No analytics tracking implementation (but provides callbacks)
  • ❌ No app-specific navigation logic
  • ❌ No user data models or assumptions
  • ❌ No network requests

These features should be implemented in your app layer using the provided callbacks and hooks.

🀝 Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ“ Changelog

See CHANGELOG.md for release history.

πŸ™ Acknowledgments

Built with ❀️ for the Flutter community.

πŸ“ž Support


Made with Flutter πŸ’™

Libraries

Promo Carousel