Unverified Commit 95d6ef74 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Nested TickerMode cannot turn tickers back on (#50355)

parent 0ffecc68
......@@ -15,26 +15,35 @@ export 'package:flutter/scheduler.dart' show TickerProvider;
/// This only works if [AnimationController] objects are created using
/// widget-aware ticker providers. For example, using a
/// [TickerProviderStateMixin] or a [SingleTickerProviderStateMixin].
class TickerMode extends InheritedWidget {
class TickerMode extends StatelessWidget {
/// Creates a widget that enables or disables tickers.
///
/// The [enabled] argument must not be null.
const TickerMode({
Key key,
@required this.enabled,
Widget child,
this.child,
}) : assert(enabled != null),
super(key: key, child: child);
super(key: key);
/// The current ticker mode of this subtree.
/// The requested ticker mode for this subtree.
///
/// The effective ticker mode of this subtree may differ from this value
/// if there is an ancestor [TickerMode] with this field set to false.
///
/// If true, then tickers in this subtree will tick.
/// If true and all ancestor [TickerMode]s are also enabled, then tickers in
/// this subtree will tick.
///
/// If false, then tickers in this subtree will not tick. Animations driven by
/// such tickers are not paused, they just don't call their callbacks. Time
/// still elapses.
/// If false, then tickers in this subtree will not tick regardless of any
/// ancestor [TickerMode]s. Animations driven by such tickers are not paused,
/// they just don't call their callbacks. Time still elapses.
final bool enabled;
/// The widget below this widget in the tree.
///
/// {@template flutter.widgets.child}
final Widget child;
/// Whether tickers in the given subtree should be enabled or disabled.
///
/// This is used automatically by [TickerProviderStateMixin] and
......@@ -49,17 +58,42 @@ class TickerMode extends InheritedWidget {
/// bool tickingEnabled = TickerMode.of(context);
/// ```
static bool of(BuildContext context) {
final TickerMode widget = context.dependOnInheritedWidgetOfExactType<TickerMode>();
final _EffectiveTickerMode widget = context.dependOnInheritedWidgetOfExactType<_EffectiveTickerMode>();
return widget?.enabled ?? true;
}
@override
bool updateShouldNotify(TickerMode oldWidget) => enabled != oldWidget.enabled;
Widget build(BuildContext context) {
return _EffectiveTickerMode(
enabled: enabled && TickerMode.of(context),
child: child,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagProperty('requested mode', value: enabled, ifTrue: 'enabled', ifFalse: 'disabled', showName: true));
}
}
class _EffectiveTickerMode extends InheritedWidget {
const _EffectiveTickerMode({
Key key,
@required this.enabled,
Widget child,
}) : assert(enabled != null),
super(key: key, child: child);
final bool enabled;
@override
bool updateShouldNotify(_EffectiveTickerMode oldWidget) => enabled != oldWidget.enabled;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagProperty('mode', value: enabled, ifTrue: 'enabled', ifFalse: 'disabled', showName: true));
properties.add(FlagProperty('effective mode', value: enabled, ifTrue: 'enabled', ifFalse: 'disabled', showName: true));
}
}
......
......@@ -132,6 +132,7 @@ void main() {
' Offstage\n'
' _ModalScopeStatus\n'
' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#969b7]\n'
' _EffectiveTickerMode\n'
' TickerMode\n'
' _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#545d0]\n'
' _Theatre\n'
......
......@@ -5,6 +5,7 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
......@@ -1650,6 +1651,81 @@ void main() {
expect(find.text('Route: root'), findsOneWidget);
expect(find.text('Route: 4', skipOffstage: false), findsNothing);
});
testWidgets('Wrapping TickerMode can turn off ticking in routes', (WidgetTester tester) async {
int tickCount = 0;
Widget widgetUnderTest({bool enabled}) {
return TickerMode(
enabled: enabled,
child: Directionality(
textDirection: TextDirection.ltr,
child: Navigator(
initialRoute: 'root',
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) {
return _TickingWidget(
onTick: () {
tickCount++;
},
);
},
);
},
),
),
);
}
await tester.pumpWidget(widgetUnderTest(enabled: false));
expect(tickCount, 0);
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(tickCount, 0);
await tester.pumpWidget(widgetUnderTest(enabled: true));
expect(tickCount, 0);
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(tickCount, 4);
});
}
class _TickingWidget extends StatefulWidget {
const _TickingWidget({this.onTick});
final VoidCallback onTick;
@override
State<_TickingWidget> createState() => _TickingWidgetState();
}
class _TickingWidgetState extends State<_TickingWidget> with SingleTickerProviderStateMixin {
Ticker _ticker;
@override
void initState() {
super.initState();
_ticker = createTicker((Duration _) {
widget.onTick();
})..start();
}
@override
Widget build(BuildContext context) {
return Container();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
}
class NoAnimationPageRoute extends PageRouteBuilder<void> {
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Nested TickerMode cannot turn tickers back on', (WidgetTester tester) async {
int outerTickCount = 0;
int innerTickCount = 0;
Widget nestedTickerModes({bool innerEnabled, bool outerEnabled}) {
return Directionality(
textDirection: TextDirection.rtl,
child: TickerMode(
enabled: outerEnabled,
child: Row(
children: <Widget>[
_TickingWidget(
onTick: () {
outerTickCount++;
},
),
TickerMode(
enabled: innerEnabled,
child: _TickingWidget(
onTick: () {
innerTickCount++;
},
),
),
],
),
),
);
}
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: false,
innerEnabled: true,
),
);
expect(outerTickCount, 0);
expect(innerTickCount, 0);
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 0);
expect(innerTickCount, 0);
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: true,
innerEnabled: false,
),
);
outerTickCount = 0;
innerTickCount = 0;
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 4);
expect(innerTickCount, 0);
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: true,
innerEnabled: true,
),
);
outerTickCount = 0;
innerTickCount = 0;
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 4);
expect(innerTickCount, 4);
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: false,
innerEnabled: false,
),
);
outerTickCount = 0;
innerTickCount = 0;
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 0);
expect(innerTickCount, 0);
});
}
class _TickingWidget extends StatefulWidget {
const _TickingWidget({this.onTick});
final VoidCallback onTick;
@override
State<_TickingWidget> createState() => _TickingWidgetState();
}
class _TickingWidgetState extends State<_TickingWidget> with SingleTickerProviderStateMixin {
Ticker _ticker;
@override
void initState() {
super.initState();
_ticker = createTicker((Duration _) {
widget.onTick();
})..start();
}
@override
Widget build(BuildContext context) {
return Container();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment