├── .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 |
28 |
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 | 
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 |
--------------------------------------------------------------------------------