Jaime Blasco

Use PrimaryScrollController

... ... @@ -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,
primary: true,
slivers: <Widget>[
SliverSafeArea(
bottom: false,
... ...
... ... @@ -50,11 +50,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,
... ... @@ -130,8 +128,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'),
... ... @@ -139,9 +136,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'),
... ... @@ -149,16 +144,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'),
... ... @@ -166,8 +158,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(
... ... @@ -176,8 +167,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'),
... ... @@ -185,10 +175,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'),
... ... @@ -196,9 +184,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'),
... ... @@ -206,9 +192,7 @@ class _MyHomePageState extends State<MyHomePage> {
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalWithNavigator(
scrollController: scrollController),
builder: (context) => ModalWithNavigator(),
)),
ListTile(
title:
... ... @@ -217,9 +201,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'),
... ... @@ -227,18 +210,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(),
)),
SizedBox(
height: 60,
... ...
... ... @@ -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(
... ...
... ... @@ -2,12 +2,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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) {
final scrollController = PrimaryScrollController.of(context);
return Material(
child: WillPopScope(
onWillPop: () async {
... ... @@ -46,6 +47,7 @@ class ComplexModal extends StatelessWidget {
bottom: false,
child: ListView(
shrinkWrap: true,
controller: scrollController,
children: ListTile.divideTiles(
context: context,
... ...
... ... @@ -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,14 +3,13 @@ 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) {
final scrollController = PrimaryScrollController.of(context);
return Material(
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
... ... @@ -20,7 +19,7 @@ class ModalInsideModal extends StatelessWidget {
child: ListView(
reverse: reverse,
shrinkWrap: true,
controller: scrollController,
controller: scrollController,
physics: ClampingScrollPhysics(),
children: ListTile.divideTiles(
context: context,
... ... @@ -33,10 +32,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) {
... ...
... ... @@ -2,12 +2,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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) {
final scrollController = PrimaryScrollController.of(context);
return Material(
child: Navigator(
onGenerateRoute: (_) => MaterialPageRoute(
... ...
... ... @@ -2,12 +2,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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) {
final scrollController = PrimaryScrollController.of(context);
return NestedScrollView(
controller: ScrollController(),
physics: ScrollPhysics(parent: PageScrollPhysics()),
... ...
... ... @@ -2,12 +2,11 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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) {
final scrollController = PrimaryScrollController.of(context);
return Material(
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
... ...
... ... @@ -21,9 +21,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 animation controller that controls the bottom sheet's entrance and
... ... @@ -96,7 +93,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.
... ... @@ -131,7 +128,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
with TickerProviderStateMixin {
final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
ScrollController _scrollController;
ScrollController get _scrollController => widget.scrollController;
AnimationController _bounceDragController;
... ... @@ -260,6 +257,9 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
//Check if scrollController is used
if (!_scrollController.hasClients) return;
if (_scrollController !=
Scrollable.of(notification.context).widget.controller) return;
final scrollPosition = _scrollController.position;
if (scrollPosition.axis == Axis.horizontal) return;
... ... @@ -270,14 +270,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
... ... @@ -323,7 +315,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();
}
... ... @@ -335,8 +327,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,
... ... @@ -394,8 +385,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
child: RepaintBoundary(child: child),
);
return PrimaryScrollStatusBarHandler(
scrollController: _scrollController, child: child);
return PrimaryScrollStatusBarHandler(child: child);
}
}
... ...
... ... @@ -14,7 +14,6 @@ class _ModalBottomSheet<T> extends StatefulWidget {
this.route,
this.secondAnimationController,
this.bounce = false,
this.scrollController,
this.expanded = false,
this.enableDrag = true,
this.animationCurve,
... ... @@ -28,7 +27,6 @@ class _ModalBottomSheet<T> extends StatefulWidget {
final bool enableDrag;
final AnimationController secondAnimationController;
final Curve animationCurve;
final ScrollController scrollController;
@override
_ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
... ... @@ -54,6 +52,8 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
return null;
}
ScrollController _scrollController;
@override
void initState() {
widget.route.animation.addListener(updateController);
... ... @@ -63,6 +63,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
@override
void dispose() {
widget.route.animation.removeListener(updateController);
_scrollController?.dispose();
super.dispose();
}
... ... @@ -74,39 +75,46 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
return AnimatedBuilder(
animation: widget.route._animationController,
builder: (BuildContext context, Widget child) {
// Disable the initial animation when accessible navigation is on so
// that the semantics are added to the tree at the correct time.
return Semantics(
scopesRoute: true,
namesRoute: true,
label: _getRouteLabel(),
explicitChildNodes: true,
child: ModalBottomSheet(
expanded: widget.route.expanded,
containerBuilder: widget.route.containerBuilder,
animationController: widget.route._animationController,
shouldClose: widget.route._hasScopedWillPopCallback
? () async {
final willPop = await widget.route.willPop();
return willPop != RoutePopDisposition.doNotPop;
return PrimaryScrollController(
controller: PrimaryScrollController.of(context) ??
(_scrollController ??= 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
// that the semantics are added to the tree at the correct time.
return Semantics(
scopesRoute: true,
namesRoute: true,
label: _getRouteLabel(),
explicitChildNodes: true,
child: ModalBottomSheet(
expanded: widget.route.expanded,
containerBuilder: widget.route.containerBuilder,
animationController: widget.route._animationController,
shouldClose: widget.route._hasScopedWillPopCallback
? () async {
final willPop = await widget.route.willPop();
return willPop != RoutePopDisposition.doNotPop;
}
: null,
onClosing: () {
if (widget.route.isCurrent) {
Navigator.of(context).pop();
}
: null,
onClosing: () {
if (widget.route.isCurrent) {
Navigator.of(context).pop();
}
},
builder: widget.route.builder,
enableDrag: widget.enableDrag,
bounce: widget.bounce,
scrollController: widget.scrollController,
animationCurve: widget.animationCurve,
),
);
},
},
child: child,
enableDrag: widget.enableDrag,
bounce: widget.bounce,
scrollController: PrimaryScrollController.of(context),
animationCurve: widget.animationCurve,
),
);
},
child: widget.route.builder(context),
),
),
);
}
}
... ... @@ -132,7 +140,7 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
super(settings: settings);
final WidgetWithChildBuilder containerBuilder;
final ScrollWidgetBuilder builder;
final WidgetBuilder builder;
final bool expanded;
final bool bounce;
final Color modalBarrierColor;
... ... @@ -183,7 +191,6 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
route: this,
secondAnimationController: secondAnimationController,
expanded: expanded,
scrollController: scrollController,
bounce: bounce,
enableDrag: enableDrag,
animationCurve: animationCurve,
... ... @@ -212,7 +219,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,
... ... @@ -226,7 +233,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,
ShapeBorder shape,
... ... @@ -137,7 +137,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
final Color transitionBackgroundColor;
CupertinoModalBottomSheetRoute({
ScrollWidgetBuilder builder,
WidgetBuilder builder,
WidgetWithChildBuilder containerBuilder,
String barrierLabel,
double elevation,
... ... @@ -317,7 +317,7 @@ class CupertinoScaffold extends StatefulWidget {
static Future<T> showCupertinoModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
@required WidgetBuilder builder,
Curve animationCurve,
Curve previousRouteAnimationCurve,
Color backgroundColor,
... ...
... ... @@ -5,7 +5,7 @@ import 'dart:async';
/// Shows a modal material design bottom sheet.
Future<T> showMaterialModalBottomSheet<T>({
@required BuildContext context,
@required ScrollWidgetBuilder builder,
@required WidgetBuilder builder,
Color backgroundColor,
double elevation,
ShapeBorder shape,
... ...
... ... @@ -4,55 +4,47 @@ import 'package:flutter/widgets.dart';
/// scroll to the top when tapped on the status bar
///
class PrimaryScrollStatusBarHandler extends StatefulWidget {
final ScrollController scrollController;
final Widget child;
const PrimaryScrollStatusBarHandler(
{Key key, this.child, this.scrollController})
: super(key: key);
const PrimaryScrollStatusBarHandler({Key key, this.child}) : super(key: key);
@override
_PrimaryScrollWidgetState createState() => _PrimaryScrollWidgetState();
}
class _PrimaryScrollWidgetState extends State<PrimaryScrollStatusBarHandler> {
ScrollController controller;
@override
void initState() {
controller = widget.scrollController ?? ScrollController();
super.initState();
}
@override
Widget build(BuildContext context) {
return PrimaryScrollController(
controller: controller,
child: Stack(
fit: StackFit.expand,
children: [
widget.child,
Positioned(
top: 0,
left: 0,
right: 0,
height: MediaQuery.of(context).padding.top,
child: Builder(
builder: (context) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _handleStatusBarTap(context),
// iOS accessibility automatically adds scroll-to-top to the clock in the status bar
excludeFromSemantics: true,
),
return Stack(
fit: StackFit.expand,
children: [
widget.child,
Positioned(
top: 0,
left: 0,
right: 0,
height: MediaQuery.of(context).padding.top,
child: Builder(
builder: (context) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _handleStatusBarTap(context),
// iOS accessibility automatically adds scroll-to-top to the clock in the status bar
excludeFromSemantics: true,
),
),
],
),
),
],
);
}
void _handleStatusBarTap(BuildContext context) {
final controller = PrimaryScrollController.of(context);
if (controller.hasClients) {
if (controller != null && controller.hasClients) {
controller.animateTo(
0.0,
duration: const Duration(milliseconds: 300),
... ...