Jaime Blasco

Merge branch 'primary-scroll'

... ... @@ -53,8 +53,8 @@ class CupertinoSharePage extends StatelessWidget {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
PhotoShareBottomSheet(scrollController: scrollController),
builder: (context) =>
PhotoShareBottomSheet(),
);
},
),
... ... @@ -73,9 +73,9 @@ class CupertinoSharePage extends StatelessWidget {
}
class PhotoShareBottomSheet extends StatelessWidget {
final ScrollController scrollController;
const PhotoShareBottomSheet({Key key, this.scrollController})
const PhotoShareBottomSheet({Key key})
: super(key: key);
@override
... ... @@ -92,7 +92,7 @@ class PhotoShareBottomSheet extends StatelessWidget {
appBar: appBar(context),
body: CustomScrollView(
physics: ClampingScrollPhysics(),
controller: scrollController,
controller: ModalScrollController.of(context),
slivers: <Widget>[
SliverSafeArea(
bottom: false,
... ...
... ... @@ -51,11 +51,9 @@ class MyApp extends StatelessWidget {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
Stack(
builder: (context) => Stack(
children: <Widget>[
ModalWithScroll(
scrollController: scrollController),
ModalWithScroll(),
Positioned(
height: 40,
left: 40,
... ... @@ -131,8 +129,7 @@ class _MyHomePageState extends State<MyHomePage> {
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Bar Modal'),
... ... @@ -140,9 +137,7 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Avatar Modal'),
... ... @@ -150,16 +145,13 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Float Modal'),
onTap: () => showFloatingModalBottomSheet(
context: context,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Cupertino Modal fit'),
... ... @@ -167,8 +159,7 @@ class _MyHomePageState extends State<MyHomePage> {
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
builder: (context) => ModalFit(),
)),
section('COMPLEX CASES'),
ListTile(
... ... @@ -177,8 +168,7 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Reverse list'),
... ... @@ -186,10 +176,8 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController,
reverse: true),
builder: (context) =>
ModalInsideModal(reverse: true),
)),
ListTile(
title: Text('Cupertino Modal inside modal'),
... ... @@ -197,9 +185,7 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Cupertino Modal with inside navigation'),
... ... @@ -207,9 +193,7 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalWithNavigator(
scrollController: scrollController),
builder: (context) => ModalWithNavigator(),
)),
ListTile(
title:
... ... @@ -218,9 +202,8 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ComplexModal(
scrollController: scrollController),
builder: (context) =>
ComplexModal(),
)),
ListTile(
title: Text('Modal with WillPopScope'),
... ... @@ -228,18 +211,16 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalWillScope(
scrollController: scrollController),
builder: (context) =>
ModalWillScope(),
)),
ListTile(
title: Text('Modal with Nested Scroll'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context, scrollController) =>
NestedScrollModal(
scrollController: scrollController),
builder: (context) =>
NestedScrollModal(),
)),
ListTile(
title: Text('Modal with PageView'),
... ...
... ... @@ -69,7 +69,7 @@ class AvatarBottomSheet extends StatelessWidget {
Future<T> showAvatarModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
@required WidgetBuilder builder,
Color backgroundColor,
double elevation,
ShapeBorder shape,
... ...
... ... @@ -27,7 +27,7 @@ class FloatingModal extends StatelessWidget {
Future<T> showFloatingModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
@required WidgetBuilder builder,
Color backgroundColor,
}) async {
final result = await showCustomModalBottomSheet(
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class ComplexModal extends StatelessWidget {
final ScrollController scrollController;
const ComplexModal({Key key, this.scrollController}) : super(key: key);
const ComplexModal({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
... ... @@ -46,7 +45,7 @@ class ComplexModal extends StatelessWidget {
bottom: false,
child: ListView(
shrinkWrap: true,
controller: scrollController,
controller: ModalScrollController.of(context),
children: ListTile.divideTiles(
context: context,
tiles: List.generate(
... ...
... ... @@ -2,9 +2,9 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ModalFit extends StatelessWidget {
final ScrollController scrollController;
const ModalFit({Key key, this.scrollController}) : super(key: key);
const ModalFit({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
... ...
... ... @@ -3,11 +3,9 @@ import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class ModalInsideModal extends StatelessWidget {
final ScrollController scrollController;
final bool reverse;
const ModalInsideModal({Key key, this.scrollController, this.reverse = false})
: super(key: key);
const ModalInsideModal({Key key, this.reverse = false}) : super(key: key);
@override
Widget build(BuildContext context) {
... ... @@ -20,7 +18,7 @@ class ModalInsideModal extends StatelessWidget {
child: ListView(
reverse: reverse,
shrinkWrap: true,
controller: scrollController,
controller: ModalScrollController.of(context),
physics: ClampingScrollPhysics(),
children: ListTile.divideTiles(
context: context,
... ... @@ -33,10 +31,8 @@ class ModalInsideModal extends StatelessWidget {
isDismissible: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController,
reverse: reverse),
builder: (context) =>
ModalInsideModal(reverse: reverse),
)),
)).toList(),
),
... ...
... ... @@ -2,9 +2,9 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ModalWillScope extends StatelessWidget {
final ScrollController scrollController;
const ModalWillScope({Key key, this.scrollController}) : super(key: key);
const ModalWillScope({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class ModalWithNavigator extends StatelessWidget {
final ScrollController scrollController;
const ModalWithNavigator({Key key, this.scrollController}) : super(key: key);
const ModalWithNavigator({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
... ... @@ -20,7 +21,7 @@ class ModalWithNavigator extends StatelessWidget {
bottom: false,
child: ListView(
shrinkWrap: true,
controller: scrollController,
controller: ModalScrollController.of(context),
children: ListTile.divideTiles(
context: context,
tiles: List.generate(
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class NestedScrollModal extends StatelessWidget {
final ScrollController scrollController;
const NestedScrollModal({Key key}) : super(key: key);
const NestedScrollModal({Key key, this.scrollController}) : super(key: key);
@override
Widget build(BuildContext context) {
... ... @@ -23,7 +23,7 @@ class NestedScrollModal extends StatelessWidget {
];
},
body: ListView.builder(
controller: scrollController,
controller: ModalScrollController.of(context),
itemBuilder: (context, index) {
return Container(
height: 100,
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class ModalWithScroll extends StatelessWidget {
final ScrollController scrollController;
const ModalWithScroll({Key key, this.scrollController}) : super(key: key);
const ModalWithScroll({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
... ... @@ -16,7 +16,7 @@ class ModalWithScroll extends StatelessWidget {
bottom: false,
child: ListView(
shrinkWrap: true,
controller: scrollController,
controller: ModalScrollController.of(context),
children: ListTile.divideTiles(
context: context,
tiles: List.generate(
... ...
... ... @@ -4,3 +4,4 @@ export 'src/material_with_modal_page_route.dart';
export 'src/bottom_sheets/cupertino_bottom_sheet.dart';
export 'src/bottom_sheets/material_bottom_sheet.dart';
export 'src/bottom_sheets/bar_bottom_sheet.dart';
export 'src/utils/modal_scroll_controller.dart';
\ No newline at end of file
... ...
... ... @@ -9,7 +9,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:modal_bottom_sheet/src/utils/primary_scroll_status_bar.dart';
import 'package:modal_bottom_sheet/src/utils/scroll_to_top_status_bar.dart';
import 'package:modal_bottom_sheet/src/utils/bottom_sheet_suspended_curve.dart';
... ... @@ -20,9 +20,6 @@ const double _minFlingVelocity = 500.0;
const double _closeProgressThreshold = 0.6;
const double _willPopThreshold = 0.8;
typedef ScrollWidgetBuilder = Widget Function(
BuildContext context, ScrollController controller);
typedef WidgetWithChildBuilder = Widget Function(
BuildContext context, Animation<double> animation, Widget child);
... ... @@ -51,10 +48,10 @@ class ModalBottomSheet extends StatefulWidget {
this.scrollController,
this.expanded,
@required this.onClosing,
@required this.builder,
@required this.child,
}) : assert(enableDrag != null),
assert(onClosing != null),
assert(builder != null),
assert(child != null),
super(key: key);
/// The closeProgressThreshold parameter
... ... @@ -100,7 +97,7 @@ class ModalBottomSheet extends StatefulWidget {
/// A builder for the contents of the sheet.
///
final ScrollWidgetBuilder builder;
final Widget child;
/// If true, the bottom sheet can be dragged up and down and dismissed by
/// swiping downwards.
... ... @@ -135,7 +132,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
with TickerProviderStateMixin {
final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
ScrollController _scrollController;
ScrollController get _scrollController => widget.scrollController;
AnimationController _bounceDragController;
... ... @@ -267,6 +264,9 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
//Check if there is more than 1 attached ScrollController e.g. swiping page in PageView
if (_scrollController.positions.length > 1) return;
if (_scrollController !=
Scrollable.of(notification.context).widget.controller) return;
final scrollPosition = _scrollController.position;
if (scrollPosition.axis == Axis.horizontal) return;
... ... @@ -277,14 +277,6 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
: scrollPosition.maxScrollExtent - scrollPosition.pixels;
if (offset <= 0) {
// Check if listener is same from scrollController.
// TODO: Improve the way it checks if it the same view controller
// Use PrimaryScrollController
if (_scrollController.position.pixels != notification.metrics.pixels &&
!(_scrollController.position.pixels == 0 &&
notification.metrics.pixels >= 0)) {
return;
}
// Clamping Scroll Physics end with a ScrollEndNotification with a DragEndDetail class
// while Bouncing Scroll Physics or other physics that Overflow don't return a drag end info
... ... @@ -297,7 +289,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
return;
}
// Otherwise the calculate the velocity with a VelocityTracker
// Otherwise the calculate the velocity with a VelocityTracker
if (_velocityTracker == null) {
final pointerKind = defaultPointerDeviceKind(context);
_velocityTracker = VelocityTracker();
... ... @@ -331,7 +323,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
animationCurve = _defaultCurve;
_bounceDragController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_scrollController = widget.scrollController ?? ScrollController();
// Todo: Check if we can remove scroll Controller
super.initState();
}
... ... @@ -343,8 +335,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
curve: Curves.easeOutSine,
);
var child = widget.builder(context, _scrollController);
var child = widget.child;
if (widget.containerBuilder != null) {
child = widget.containerBuilder(
context,
... ... @@ -402,8 +393,10 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
child: RepaintBoundary(child: child),
);
return PrimaryScrollStatusBarHandler(
scrollController: _scrollController, child: child);
return ScrollToTopStatusBarHandler(
child: child,
scrollController: _scrollController,
);
}
}
... ...
... ... @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/src/utils/modal_scroll_controller.dart';
import '../modal_bottom_sheet.dart';
... ... @@ -15,7 +16,6 @@ class _ModalBottomSheet<T> extends StatefulWidget {
this.route,
this.secondAnimationController,
this.bounce = false,
this.scrollController,
this.expanded = false,
this.enableDrag = true,
this.animationCurve,
... ... @@ -30,7 +30,6 @@ class _ModalBottomSheet<T> extends StatefulWidget {
final bool enableDrag;
final AnimationController secondAnimationController;
final Curve animationCurve;
final ScrollController scrollController;
@override
_ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
... ... @@ -56,6 +55,8 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
return null;
}
ScrollController _scrollController;
@override
void initState() {
widget.route.animation.addListener(updateController);
... ... @@ -65,6 +66,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
@override
void dispose() {
widget.route.animation.removeListener(updateController);
_scrollController?.dispose();
super.dispose();
}
... ... @@ -75,8 +77,12 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
return AnimatedBuilder(
final scrollController = PrimaryScrollController.of(context) ??
(_scrollController ??= ScrollController());
return ModalScrollController(
controller: scrollController,
child: Builder(
builder: (context) => AnimatedBuilder(
animation: widget.route._animationController,
builder: (BuildContext context, Widget child) {
// Disable the initial animation when accessible navigation is on so
... ... @@ -87,7 +93,6 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
label: _getRouteLabel(),
explicitChildNodes: true,
child: ModalBottomSheet(
closeProgressThreshold: widget.closeProgressThreshold,
expanded: widget.route.expanded,
containerBuilder: widget.route.containerBuilder,
animationController: widget.route._animationController,
... ... @@ -102,14 +107,17 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
Navigator.of(context).pop();
}
},
builder: widget.route.builder,
child: child,
enableDrag: widget.enableDrag,
bounce: widget.bounce,
scrollController: widget.scrollController,
scrollController: scrollController,
animationCurve: widget.animationCurve,
),
);
},
child: widget.route.builder(context),
),
),
);
}
}
... ... @@ -137,7 +145,7 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
final double closeProgressThreshold;
final WidgetWithChildBuilder containerBuilder;
final ScrollWidgetBuilder builder;
final WidgetBuilder builder;
final bool expanded;
final bool bounce;
final Color modalBarrierColor;
... ... @@ -189,7 +197,6 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
route: this,
secondAnimationController: secondAnimationController,
expanded: expanded,
scrollController: scrollController,
bounce: bounce,
enableDrag: enableDrag,
animationCurve: animationCurve,
... ... @@ -218,7 +225,7 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
/// Shows a modal material design bottom sheet.
Future<T> showCustomModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
@required WidgetBuilder builder,
@required WidgetWithChildBuilder containerWidget,
Color backgroundColor,
double elevation,
... ... @@ -232,7 +239,6 @@ Future<T> showCustomModalBottomSheet<T>({
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
ScrollController scrollController,
Duration duration,
}) async {
assert(context != null);
... ...
... ... @@ -71,7 +71,7 @@ class BarBottomSheet extends StatelessWidget {
Future<T> showBarModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
@required WidgetBuilder builder,
Color backgroundColor,
double elevation,
ShapeBorder shape,
... ...
... ... @@ -67,7 +67,7 @@ class _CupertinoBottomSheetContainer extends StatelessWidget {
Future<T> showCupertinoModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
@required WidgetBuilder builder,
Color backgroundColor,
double elevation,
double closeProgressThreshold,
... ... @@ -139,7 +139,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
final Color transitionBackgroundColor;
CupertinoModalBottomSheetRoute({
ScrollWidgetBuilder builder,
WidgetBuilder builder,
WidgetWithChildBuilder containerBuilder,
double closeProgressThreshold,
String barrierLabel,
... ... @@ -321,8 +321,8 @@ class CupertinoScaffold extends StatefulWidget {
static Future<T> showCupertinoModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
double closeProgressThreshold,
@required WidgetBuilder builder,
Curve animationCurve,
Curve previousRouteAnimationCurve,
Color backgroundColor,
... ...
... ... @@ -5,8 +5,8 @@ import 'dart:async';
/// Shows a modal material design bottom sheet.
Future<T> showMaterialModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
double closeProgressThreshold,
@required WidgetBuilder builder,
Color backgroundColor,
double elevation,
ShapeBorder shape,
... ...
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
/// Associates a [ScrollController] with a subtree.
///
/// This mechanism can be used to provide default behavior for scroll views in a
/// subtree inside a modal bottom sheet.
///
/// We want to remove this and use [PrimaryScrollController].
/// This issue should be solved first https://github.com/flutter/flutter/issues/64236
///
/// See [PrimaryScrollController]
class ModalScrollController extends InheritedWidget {
/// Creates a widget that associates a [ScrollController] with a subtree.
ModalScrollController({
Key key,
@required this.controller,
@required Widget child,
}) : assert(controller != null),
super(
key: key,
child: PrimaryScrollController(
controller: controller,
child: child,
),
);
/// The [ScrollController] associated with the subtree.
///
/// See also:
///
/// * [ScrollView.controller], which discusses the purpose of specifying a
/// scroll controller.
final ScrollController controller;
/// Returns the [ScrollController] most closely associated with the given
/// context.
///
/// Returns null if there is no [ScrollController] associated with the given
/// context.
static ScrollController of(BuildContext context) {
final ModalScrollController result =
context.dependOnInheritedWidgetOfExactType<ModalScrollController>();
return result?.controller;
}
@override
bool updateShouldNotify(ModalScrollController oldWidget) =>
controller != oldWidget.controller;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ScrollController>(
'controller', controller,
ifNull: 'no controller', showName: false));
}
}
... ...
import 'package:flutter/widgets.dart';
/// Creates a primary scroll controller that will
/// scroll to the top when tapped on the status bar
/// Widget that that will scroll to the top the ScrollController
/// when tapped on the status bar
///
class PrimaryScrollStatusBarHandler extends StatefulWidget {
final ScrollController scrollController;
class ScrollToTopStatusBarHandler extends StatefulWidget {
final Widget child;
final ScrollController scrollController;
const ScrollToTopStatusBarHandler({
Key key,
@required this.child,
@required this.scrollController,
}) : super(key: key);
const PrimaryScrollStatusBarHandler(
{Key key, this.child, this.scrollController})
: super(key: key);
@override
_PrimaryScrollWidgetState createState() => _PrimaryScrollWidgetState();
_ScrollToTopStatusBarState createState() => _ScrollToTopStatusBarState();
}
class _PrimaryScrollWidgetState extends State<PrimaryScrollStatusBarHandler> {
ScrollController controller;
class _ScrollToTopStatusBarState extends State<ScrollToTopStatusBarHandler> {
@override
void initState() {
controller = widget.scrollController ?? ScrollController();
super.initState();
}
@override
Widget build(BuildContext context) {
return PrimaryScrollController(
controller: controller,
child: Stack(
return Stack(
fit: StackFit.expand,
children: [
widget.child,
... ... @@ -46,13 +44,12 @@ class _PrimaryScrollWidgetState extends State<PrimaryScrollStatusBarHandler> {
),
),
],
),
);
}
void _handleStatusBarTap(BuildContext context) {
final controller = PrimaryScrollController.of(context);
if (controller.hasClients) {
final controller = widget.scrollController;
if (controller != null && controller.hasClients) {
controller.animateTo(
0.0,
duration: const Duration(milliseconds: 300),
... ...