dupe_modal_bottom_sheet 1.0.0
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 #
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'),
),
),
);
}
}
Hero Animation with Image Gallery #
// 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(notbuildTransitions) - Uses
PageRoutefor 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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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.