dupe_modal_bottom_sheet 1.0.0 copy "dupe_modal_bottom_sheet: ^1.0.0" to clipboard
dupe_modal_bottom_sheet: ^1.0.0 copied to clipboard

A customizable bottom sheet with Hero animation support, implementing all features of showModalBottomSheet.

Dupe Modal Bottom Sheet #

pub package License: MIT

A Flutter package that provides a modal bottom sheet with full Hero animation support, while maintaining 100% API compatibility with Flutter's standard showModalBottomSheet.

中文文档

Why Dupe Modal Bottom Sheet? #

Flutter's built-in showModalBottomSheet doesn't properly support Hero animations due to its ModalRoute implementation. This package solves that by using a custom PageRoute that:

  • Enables Hero animations in modal bottom sheets
  • Maintains full API compatibility - drop-in replacement
  • Zero breaking changes - all parameters work identically
  • Production ready - 100% test coverage

Features #

  • 🎭 Hero Animation Support - Smooth shared element transitions
  • 🎨 Complete Customization - Background, shape, elevation, and more
  • 📱 Material 3 Ready - Surface tint color support
  • 👆 Interactive Gestures - Drag to dismiss with optional handle
  • 📐 Flexible Sizing - Scroll-controlled or fixed height
  • 🛡️ Safe Area Aware - Proper handling of screen notches
  • 🔄 Custom Animations - Bring your own animation controller
  • Accessible - Full screen reader support
  • 📱 Foldable Device Support - Display feature awareness

Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  dupe_modal_bottom_sheet: ^1.0.0

Then run:

flutter pub get

Quick Start #

Simply replace showModalBottomSheet with showDupeModalBottomSheet:

import 'package:dupe_modal_bottom_sheet/dupe_modal_bottom_sheet.dart';

// That's it! All parameters work exactly the same
showDupeModalBottomSheet(
  context: context,
  builder: (BuildContext context) {
    return Container(
      height: 200,
      child: Center(
        child: Text('Hello from Bottom Sheet!'),
      ),
    );
  },
);

Usage Examples #

Basic Bottom Sheet #

import 'package:flutter/material.dart';
import 'package:dupe_modal_bottom_sheet/dupe_modal_bottom_sheet.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dupe Modal Bottom Sheet')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            showDupeModalBottomSheet(
              context: context,
              builder: (context) {
                return Container(
                  padding: EdgeInsets.all(20),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text('Bottom Sheet Content'),
                      SizedBox(height: 20),
                      ElevatedButton(
                        onPressed: () => Navigator.pop(context),
                        child: Text('Close'),
                      ),
                    ],
                  ),
                );
              },
            );
          },
          child: Text('Show Bottom Sheet'),
        ),
      ),
    );
  }
}
// Main page with thumbnail
class GalleryPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      itemCount: images.length,
      itemBuilder: (context, index) {
        return GestureDetector(
          onTap: () {
            showDupeModalBottomSheet(
              context: context,
              isScrollControlled: true,
              builder: (context) => ImagePreview(
                imageUrl: images[index],
                heroTag: 'image_$index',
              ),
            );
          },
          child: Hero(
            tag: 'image_$index',
            child: Image.network(images[index], fit: BoxFit.cover),
          ),
        );
      },
    );
  }
}

// Bottom sheet with full-size image
class ImagePreview extends StatelessWidget {
  final String imageUrl;
  final String heroTag;

  const ImagePreview({required this.imageUrl, required this.heroTag});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: MediaQuery.of(context).size.height * 0.9,
      child: Column(
        children: [
          AppBar(
            title: Text('Image Preview'),
            leading: IconButton(
              icon: Icon(Icons.close),
              onPressed: () => Navigator.pop(context),
            ),
          ),
          Expanded(
            child: Hero(
              tag: heroTag,
              child: Image.network(imageUrl, fit: BoxFit.contain),
            ),
          ),
        ],
      ),
    );
  }
}

Styled Bottom Sheet with Rounded Corners #

showDupeModalBottomSheet(
  context: context,
  backgroundColor: Colors.white,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
  ),
  clipBehavior: Clip.antiAlias,
  builder: (context) {
    return Container(
      padding: EdgeInsets.all(20),
      child: Text('Styled Bottom Sheet'),
    );
  },
);

Full-Height Scrollable Sheet #

showDupeModalBottomSheet(
  context: context,
  isScrollControlled: true,
  useSafeArea: true,
  builder: (context) {
    return DraggableScrollableSheet(
      initialChildSize: 0.9,
      minChildSize: 0.5,
      maxChildSize: 0.95,
      builder: (context, scrollController) {
        return ListView.builder(
          controller: scrollController,
          itemCount: 50,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text('Item $index'),
            );
          },
        );
      },
    );
  },
);

Custom Animation #

final animationController = AnimationController(
  vsync: this,
  duration: Duration(milliseconds: 500),
);

showDupeModalBottomSheet(
  context: context,
  transitionAnimationController: animationController,
  sheetAnimationStyle: AnimationStyle(
    duration: Duration(milliseconds: 500),
    curve: Curves.easeInOutCubic,
  ),
  builder: (context) => YourContent(),
);

API Reference #

showDupeModalBottomSheet #

All parameters are identical to Flutter's showModalBottomSheet:

Parameter Type Default Description
context BuildContext required The build context from which to show the bottom sheet
builder WidgetBuilder required A builder function that creates the sheet content
backgroundColor Color? null The background color of the sheet
elevation double? null The z-coordinate at which to place the sheet
shape ShapeBorder? null The shape of the sheet (e.g., rounded corners)
clipBehavior Clip? null The clipping behavior for custom shapes
constraints BoxConstraints? null Additional size constraints for the sheet
barrierColor Color? Colors.black54 The color of the modal barrier behind the sheet
surfaceTintColor Color? null Material 3 surface tint color
isScrollControlled bool false Whether the sheet can expand to full height
scrollControlDisabledMaxHeightRatio double 9.0 / 16.0 Max height ratio when not scroll-controlled
useRootNavigator bool false Whether to use the root navigator
isDismissible bool true Whether tapping the barrier dismisses the sheet
enableDrag bool true Whether the sheet can be dragged to dismiss
showDragHandle bool? null Whether to show a drag handle indicator
useSafeArea bool false Whether to wrap the sheet in SafeArea
routeSettings RouteSettings? null Settings for the route
transitionAnimationController AnimationController? null Custom animation controller
anchorPoint Offset? null Anchor point for the sheet
sheetAnimationStyle AnimationStyle? null Custom animation style
barrierLabel String? null Semantic label for the barrier
requestFocus bool? null Whether to request focus when shown

Migration Guide #

Migrating from showModalBottomSheet is seamless:

// Before
showModalBottomSheet(
  context: context,
  builder: (context) => MySheet(),
  backgroundColor: Colors.white,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
  ),
);

// After - just change the function name
showDupeModalBottomSheet(
  context: context,
  builder: (context) => MySheet(),
  backgroundColor: Colors.white,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
  ),
);

Example App #

Check out the example directory for a complete demo featuring:

  • 📸 Image gallery with Hero animations
  • 🔍 Interactive image preview with gestures
  • ⚖️ Side-by-side comparison with standard showModalBottomSheet

To run the example:

cd example
flutter run

How It Works #

The package uses a custom PageRoute implementation instead of ModalRoute. This allows the Hero animation system to properly calculate widget positions during the build phase, ensuring smooth transitions.

Key differences:

  • Bottom sheet is aligned during buildPage (not buildTransitions)
  • Uses PageRoute for proper Hero support
  • Maintains all standard modal bottom sheet features

Requirements #

  • Flutter SDK: >=3.0.0
  • Dart SDK: >=3.0.0 <4.0.0

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License #

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

Changelog #

See CHANGELOG.md for a detailed list of changes.

Support #

If you find this package helpful, please give it a ⭐ on GitHub!

For issues and feature requests, please use the issue tracker.

0
likes
160
points
41
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A customizable bottom sheet with Hero animation support, implementing all features of showModalBottomSheet.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on dupe_modal_bottom_sheet