├── .gitignore ├── .idea ├── libraries │ └── Dart_SDK.xml ├── modules.xml └── workspace.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── app │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── flutter │ │ └── plugins │ │ └── GeneratedPluginRegistrant.java └── local.properties ├── lib └── rounded_modal.dart ├── pubspec.lock ├── pubspec.yaml └── rounded_modal.iml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.1] - 2019-08-05 2 | 3 | This plugin is now DEPRECATED as you can now do proper borders with the default implementation from `showModalBottomSheet` as follows: 4 | 5 | ```dart 6 | showModalBottomSheet( 7 | shape: RoundedRectangleBorder( 8 | borderRadius: BorderRadius.circular(10.0), 9 | ), 10 | backgroundColor: Colors.white, 11 | ); 12 | ``` 13 | 14 | ## [1.0.0] - 2018-10-12 15 | 16 | * Added fix from [@slightfoot](https://gist.github.com/slightfoot/5af4c5dfa52194a3f8577bf83af2e391) that handles the issue of modals hiding the keyboard if any text input is part of its layout 17 | 18 | ## [0.0.1] - 2018-09-01 19 | 20 | * First release for this amazingly simple package 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gildásio Filho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rounded_modal 2 | 3 | ## THIS PLUGIN IS NOW DEPRECATED 4 | 5 | You can now properly use borders with the default implementation of `showModalBottomSheet`! 6 | 7 | ```dart 8 | showModalBottomSheet( 9 | shape: RoundedRectangleBorder( 10 | borderRadius: BorderRadius.circular(10.0), 11 | ), 12 | backgroundColor: Colors.white, 13 | ); 14 | ``` 15 | 16 | ## 17 | 18 | A custom implementation of `showModalBottomSheet`. 19 | 20 | Instead of overriding the entire theme of the app (which caused problems in various parts of my app) as suggested by other solutions on "How to set rounded corners of a modal?", I decided to take a look at the implementation for `showModalBottomSheet` and find the problem myself. 21 | 22 | ## Getting Started 23 | 24 | Turns out that all that was needed was wrapping the main code for the modal in a Theme widget that contains the `canvasColor: Colors.transparent` trick. I also made it easier to customize the radius and the background color of the modal itself. 25 | 26 | ![screenshot](https://user-images.githubusercontent.com/1339236/55436807-9b807d80-5573-11e9-8486-1178024a4caf.png) 27 | 28 | ```Dart 29 | import 'package:rounded_modal/rounded_modal.dart'; 30 | 31 | showRoundedModalBottomSheet( 32 | context: context, 33 | radius: 10.0, // This is the default 34 | color: Colors.white, // Also default 35 | builder: (context) => ???, 36 | ); 37 | ``` 38 | 39 | For help getting started with Flutter, view our online [documentation](https://flutter.io/). 40 | 41 | For help on editing package code, view the [documentation](https://flutter.io/developing-packages/). 42 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import io.flutter.plugin.common.PluginRegistry; 4 | 5 | /** 6 | * Generated file. Do not edit. 7 | */ 8 | public final class GeneratedPluginRegistrant { 9 | public static void registerWith(PluginRegistry registry) { 10 | if (alreadyRegisteredWith(registry)) { 11 | return; 12 | } 13 | } 14 | 15 | private static boolean alreadyRegisteredWith(PluginRegistry registry) { 16 | final String key = GeneratedPluginRegistrant.class.getCanonicalName(); 17 | if (registry.hasPlugin(key)) { 18 | return true; 19 | } 20 | registry.registrarFor(key); 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/local.properties: -------------------------------------------------------------------------------- 1 | sdk.dir=/Users/gildaswise/Library/Android/sdk 2 | flutter.sdk=/Users/gildaswise/flutter 3 | flutter.versionName=1.0.0 -------------------------------------------------------------------------------- /lib/rounded_modal.dart: -------------------------------------------------------------------------------- 1 | library rounded_modal; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Adapted from https://gist.github.com/slightfoot/5af4c5dfa52194a3f8577bf83af2e391 8 | 9 | /// Below is the usage for this function, you'll only have to import this file 10 | /// [radius] takes a double and will be the radius to the rounded corners of this modal 11 | /// [color] will color the modal itself, the default being `Colors.white` 12 | /// [builder] takes the content of the modal, if you're using [Column] 13 | /// or a similar widget, remember to set `mainAxisSize: MainAxisSize.min` 14 | /// so it will only take the needed space. 15 | /// 16 | /// This newer version also fixes the issue of keyboard overlap based on 17 | /// [this gist](https://gist.github.com/slightfoot/5af4c5dfa52194a3f8577bf83af2e391). 18 | /// 19 | /// ```dart 20 | /// showRoundedModalBottomSheet( 21 | /// context: context, 22 | /// radius: 10.0, // This is the default 23 | /// color: Colors.white, // Also default 24 | /// builder: (context) => ???, 25 | /// ); 26 | /// ``` 27 | Future showRoundedModalBottomSheet({ 28 | @required BuildContext context, 29 | @required WidgetBuilder builder, 30 | Color color: Colors.white, 31 | double radius: 10.0, 32 | bool autoResize: true, 33 | bool dismissOnTap: true, 34 | }) { 35 | assert(context != null); 36 | assert(builder != null); 37 | assert(radius != null && radius > 0.0); 38 | assert(color != null && color != Colors.transparent); 39 | return Navigator.push( 40 | context, 41 | RoundedCornerModalRoute( 42 | builder: builder, 43 | color: color, 44 | radius: radius, 45 | autoResize: autoResize, 46 | dismissOnTap: dismissOnTap, 47 | barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, 48 | ), 49 | ); 50 | } 51 | 52 | const Duration _kRoundedBottomSheetDuration = const Duration(milliseconds: 300); 53 | const double _kMinFlingVelocity = 600.0; 54 | const double _kCloseProgressThreshold = 0.5; 55 | 56 | /// A material design modal bottom sheet. 57 | /// 58 | /// * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and 59 | /// prevents the user from interacting with the rest of the app. Modal bottom 60 | /// sheets can be created and displayed with the [showRoundedModalBottomSheet] 61 | /// function. 62 | /// 63 | /// The [RoundedBottomSheet] widget itself is rarely used directly. Instead, prefer to 64 | /// create a modal bottom sheet with [showRoundedModalBottomSheet]. 65 | /// 66 | /// See also: 67 | /// 68 | /// * [showRoundedModalBottomSheet] 69 | /// * 70 | class RoundedBottomSheet extends StatefulWidget { 71 | /// Creates a bottom sheet. 72 | /// 73 | /// Typically, bottom sheets are created implicitly by 74 | /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by 75 | /// [showModalBottomSheet], for modal bottom sheets. 76 | const RoundedBottomSheet( 77 | {Key key, 78 | this.animationController, 79 | @required this.onClosing, 80 | @required this.builder}) 81 | : assert(onClosing != null), 82 | assert(builder != null), 83 | super(key: key); 84 | 85 | /// The animation that controls the bottom sheet's position. 86 | /// 87 | /// The BottomSheet widget will manipulate the position of this animation, it 88 | /// is not just a passive observer. 89 | final AnimationController animationController; 90 | 91 | /// Called when the bottom sheet begins to close. 92 | /// 93 | /// A bottom sheet might be be prevented from closing (e.g., by user 94 | /// interaction) even after this callback is called. For this reason, this 95 | /// callback might be call multiple times for a given bottom sheet. 96 | final VoidCallback onClosing; 97 | 98 | /// A builder for the contents of the sheet. 99 | /// 100 | /// The bottom sheet will wrap the widget produced by this builder in a 101 | /// [Material] widget. 102 | final WidgetBuilder builder; 103 | 104 | @override 105 | _RoundedBottomSheetState createState() => _RoundedBottomSheetState(); 106 | 107 | /// Creates an animation controller suitable for controlling a [RoundedBottomSheet]. 108 | static AnimationController createAnimationController(TickerProvider vsync) { 109 | return AnimationController( 110 | duration: _kRoundedBottomSheetDuration, 111 | debugLabel: 'RoundedBottomSheet', 112 | vsync: vsync, 113 | ); 114 | } 115 | } 116 | 117 | class _RoundedBottomSheetState extends State { 118 | final GlobalKey _childKey = GlobalKey(debugLabel: 'RoundedBottomSheet child'); 119 | 120 | double get _childHeight { 121 | final RenderBox renderBox = _childKey.currentContext.findRenderObject(); 122 | return renderBox.size.height; 123 | } 124 | 125 | bool get _dismissUnderway => 126 | widget.animationController.status == AnimationStatus.reverse; 127 | 128 | void _handleDragUpdate(DragUpdateDetails details) { 129 | if (_dismissUnderway) return; 130 | widget.animationController.value -= 131 | details.primaryDelta / (_childHeight ?? details.primaryDelta); 132 | } 133 | 134 | void _handleDragEnd(DragEndDetails details) { 135 | if (_dismissUnderway) return; 136 | if (details.velocity.pixelsPerSecond.dy > _kMinFlingVelocity) { 137 | final double flingVelocity = 138 | -details.velocity.pixelsPerSecond.dy / _childHeight; 139 | if (widget.animationController.value > 0.0) 140 | widget.animationController.fling(velocity: flingVelocity); 141 | if (flingVelocity < 0.0) widget.onClosing(); 142 | } else if (widget.animationController.value < _kCloseProgressThreshold) { 143 | if (widget.animationController.value > 0.0) 144 | widget.animationController.fling(velocity: -1.0); 145 | widget.onClosing(); 146 | } else { 147 | widget.animationController.forward(); 148 | } 149 | } 150 | 151 | @override 152 | Widget build(BuildContext context) { 153 | return GestureDetector( 154 | onVerticalDragUpdate: _handleDragUpdate, 155 | onVerticalDragEnd: _handleDragEnd, 156 | child: Material( 157 | key: _childKey, 158 | child: widget.builder(context), 159 | ), 160 | ); 161 | } 162 | } 163 | 164 | class _RoundedModalBottomSheetLayout extends SingleChildLayoutDelegate { 165 | _RoundedModalBottomSheetLayout(this.bottomInset, this.progress); 166 | 167 | final double bottomInset; 168 | final double progress; 169 | 170 | @override 171 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) { 172 | return BoxConstraints( 173 | minWidth: constraints.maxWidth, 174 | maxWidth: constraints.maxWidth, 175 | minHeight: 0.0, 176 | maxHeight: constraints.maxHeight * 9.0 / 16.0); 177 | } 178 | 179 | @override 180 | Offset getPositionForChild(Size size, Size childSize) { 181 | return Offset(0.0, size.height - bottomInset - childSize.height * progress); 182 | } 183 | 184 | @override 185 | bool shouldRelayout(_RoundedModalBottomSheetLayout oldDelegate) { 186 | return progress != oldDelegate.progress || 187 | bottomInset != oldDelegate.bottomInset; 188 | } 189 | } 190 | 191 | class RoundedCornerModalRoute extends PopupRoute { 192 | RoundedCornerModalRoute({ 193 | this.builder, 194 | this.barrierLabel, 195 | this.color, 196 | this.radius, 197 | this.autoResize: false, 198 | this.dismissOnTap: true, 199 | RouteSettings settings, 200 | }) : super(settings: settings); 201 | 202 | final WidgetBuilder builder; 203 | final double radius; 204 | final Color color; 205 | final bool autoResize; 206 | final bool dismissOnTap; 207 | 208 | @override 209 | Duration get transitionDuration => _kRoundedBottomSheetDuration; 210 | 211 | @override 212 | Color get barrierColor => Colors.black54; 213 | 214 | @override 215 | bool get barrierDismissible => true; 216 | 217 | @override 218 | bool get opaque => false; 219 | 220 | @override 221 | bool get maintainState => false; 222 | 223 | @override 224 | String barrierLabel; 225 | 226 | AnimationController animationController; 227 | 228 | @override 229 | AnimationController createAnimationController() { 230 | assert(animationController == null); 231 | animationController = 232 | BottomSheet.createAnimationController(navigator.overlay); 233 | return animationController; 234 | } 235 | 236 | @override 237 | Widget buildPage(BuildContext context, Animation animation, 238 | Animation secondaryAnimation) { 239 | return MediaQuery.removePadding( 240 | context: context, 241 | removeTop: true, 242 | child: Theme( 243 | data: Theme.of(context).copyWith(canvasColor: Colors.transparent), 244 | child: RoundedModalBottomSheet(route: this), 245 | ), 246 | ); 247 | } 248 | } 249 | 250 | class RoundedModalBottomSheet extends StatefulWidget { 251 | const RoundedModalBottomSheet({Key key, this.route}) : super(key: key); 252 | 253 | final RoundedCornerModalRoute route; 254 | 255 | @override 256 | _RoundedModalBottomSheetState createState() => 257 | _RoundedModalBottomSheetState(); 258 | } 259 | 260 | class _RoundedModalBottomSheetState 261 | extends State> { 262 | @override 263 | Widget build(BuildContext context) { 264 | return GestureDetector( 265 | onTap: widget.route.dismissOnTap ? () => Navigator.pop(context) : null, 266 | child: AnimatedBuilder( 267 | animation: widget.route.animation, 268 | builder: (context, child) => CustomSingleChildLayout( 269 | delegate: _RoundedModalBottomSheetLayout( 270 | widget.route.autoResize 271 | ? MediaQuery.of(context).viewInsets.bottom 272 | : 0.0, 273 | widget.route.animation.value), 274 | child: RoundedBottomSheet( 275 | animationController: widget.route.animationController, 276 | onClosing: () => Navigator.pop(context), 277 | builder: (context) => Container( 278 | decoration: BoxDecoration( 279 | color: widget.route.color, 280 | borderRadius: BorderRadius.only( 281 | topLeft: Radius.circular(widget.route.radius), 282 | topRight: Radius.circular(widget.route.radius), 283 | ), 284 | ), 285 | child: SafeArea( 286 | child: Builder(builder: widget.route.builder), 287 | ), 288 | ), 289 | ), 290 | ), 291 | ), 292 | ); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | collection: 5 | dependency: transitive 6 | description: 7 | name: collection 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.14.11" 11 | flutter: 12 | dependency: "direct main" 13 | description: flutter 14 | source: sdk 15 | version: "0.0.0" 16 | meta: 17 | dependency: transitive 18 | description: 19 | name: meta 20 | url: "https://pub.dartlang.org" 21 | source: hosted 22 | version: "1.1.6" 23 | sky_engine: 24 | dependency: transitive 25 | description: flutter 26 | source: sdk 27 | version: "0.0.99" 28 | typed_data: 29 | dependency: transitive 30 | description: 31 | name: typed_data 32 | url: "https://pub.dartlang.org" 33 | source: hosted 34 | version: "1.1.6" 35 | vector_math: 36 | dependency: transitive 37 | description: 38 | name: vector_math 39 | url: "https://pub.dartlang.org" 40 | source: hosted 41 | version: "2.0.8" 42 | sdks: 43 | dart: ">=2.2.2 <3.0.0" 44 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: rounded_modal 2 | description: A custom implementation of showModalBottomSheet 3 | version: 1.0.1 4 | author: Gildásio Filho 5 | homepage: https://github.com/gildaswise/rounded_modal 6 | 7 | environment: 8 | sdk: ">=2.1.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | flutter: 15 | uses-material-design: true 16 | -------------------------------------------------------------------------------- /rounded_modal.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------