mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Cupertino RTL (#13273)
Fixes the remaining known issues with widgets supporting RTL.
This commit is contained in:
parent
6493c8b43d
commit
4a4fa2a7c7
@ -39,7 +39,7 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
|
||||
});
|
||||
}
|
||||
),
|
||||
const Text('Cupertino Continuous'),
|
||||
new Text('Cupertino Continuous: ${_value.toStringAsFixed(1)}'),
|
||||
]
|
||||
),
|
||||
new Column(
|
||||
@ -56,7 +56,7 @@ class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
|
||||
});
|
||||
}
|
||||
),
|
||||
const Text('Cupertino Discrete'),
|
||||
new Text('Cupertino Discrete: $_discreteValue'),
|
||||
]
|
||||
),
|
||||
],
|
||||
|
@ -31,16 +31,17 @@ class CupertinoIcons {
|
||||
|
||||
/// The icon font used for Cupertino icons.
|
||||
static const String iconFont = 'CupertinoIcons';
|
||||
|
||||
/// The dependent package providing the Cupertino icons font.
|
||||
static const String iconFontPackage = 'cupertino_icons';
|
||||
|
||||
// Manually maintained list
|
||||
// Manually maintained list.
|
||||
|
||||
/// A thin left chevron.
|
||||
static const IconData left_chevron = const IconData(0xf3f0, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
static const IconData left_chevron = const IconData(0xf3f0, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
|
||||
|
||||
/// A thin right chevron.
|
||||
static const IconData right_chevron = const IconData(0xf3f2, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
static const IconData right_chevron = const IconData(0xf3f2, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
|
||||
|
||||
/// iOS style share icon with an arrow pointing up from a box.
|
||||
static const IconData share = const IconData(0xf4ca, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
@ -79,14 +80,14 @@ class CupertinoIcons {
|
||||
static const IconData check_mark_circled = const IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
|
||||
/// A thicker left chevron used in iOS for the nav bar back button.
|
||||
static const IconData back = const IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
static const IconData back = const IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
|
||||
|
||||
/// Outline of a simple front-facing house.
|
||||
static const IconData home = const IconData(0xf447, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
|
||||
/// A right facing shopping cart outline.
|
||||
/// A right-facing shopping cart outline.
|
||||
static const IconData shopping_cart = const IconData(0xf3f7, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
|
||||
/// 3 solid dots.
|
||||
/// Three solid dots.
|
||||
static const IconData ellipsis = const IconData(0xf46a, fontFamily: iconFont, fontPackage: iconFontPackage);
|
||||
}
|
||||
|
@ -376,7 +376,7 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe
|
||||
? new Container(
|
||||
height: _kNavBarPersistentHeight,
|
||||
width: _kNavBarBackButtonTapWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: const Icon(CupertinoIcons.back, size: 34.0,)
|
||||
)
|
||||
: const Text('Close'),
|
||||
@ -394,10 +394,10 @@ class _CupertinoPersistentNavigationBar extends StatelessWidget implements Prefe
|
||||
size: 22.0,
|
||||
),
|
||||
child: new Padding(
|
||||
padding: new EdgeInsets.only(
|
||||
padding: new EdgeInsetsDirectional.only(
|
||||
top: MediaQuery.of(context).padding.top,
|
||||
left: useBackButton ? _kNavBarBackButtonPadding : _kNavBarEdgePadding,
|
||||
right: _kNavBarEdgePadding,
|
||||
start: useBackButton ? _kNavBarBackButtonPadding : _kNavBarEdgePadding,
|
||||
end: _kNavBarEdgePadding,
|
||||
),
|
||||
child: new MediaQuery.removePadding(
|
||||
context: context,
|
||||
|
@ -35,9 +35,9 @@ final DecorationTween _kGradientShadowTween = new DecorationTween(
|
||||
end: const _CupertinoEdgeShadowDecoration(
|
||||
edgeGradient: const LinearGradient(
|
||||
// Spans 5% of the page.
|
||||
begin: const Alignment(0.90, 0.0),
|
||||
end: Alignment.centerRight,
|
||||
// Eyeballed gradient used to mimic a drop shadow on the left side only.
|
||||
begin: const AlignmentDirectional(0.90, 0.0),
|
||||
end: AlignmentDirectional.centerEnd,
|
||||
// Eyeballed gradient used to mimic a drop shadow on the start side only.
|
||||
colors: const <Color>[
|
||||
const Color(0x00000000),
|
||||
const Color(0x04000000),
|
||||
@ -293,31 +293,31 @@ class CupertinoPageTransition extends StatelessWidget {
|
||||
@required Animation<double> primaryRouteAnimation,
|
||||
@required Animation<double> secondaryRouteAnimation,
|
||||
@required this.child,
|
||||
bool linearTransition,
|
||||
}) :
|
||||
_primaryPositionAnimation = linearTransition
|
||||
? _kRightMiddleTween.animate(primaryRouteAnimation)
|
||||
: _kRightMiddleTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: primaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
_secondaryPositionAnimation = _kMiddleLeftTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: secondaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
_primaryShadowAnimation = _kGradientShadowTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: primaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
)
|
||||
),
|
||||
super(key: key);
|
||||
@required bool linearTransition,
|
||||
}) : assert(linearTransition != null),
|
||||
_primaryPositionAnimation = linearTransition
|
||||
? _kRightMiddleTween.animate(primaryRouteAnimation)
|
||||
: _kRightMiddleTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: primaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
_secondaryPositionAnimation = _kMiddleLeftTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: secondaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
_primaryShadowAnimation = _kGradientShadowTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: primaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
)
|
||||
),
|
||||
super(key: key);
|
||||
|
||||
// When this page is coming in to cover another page.
|
||||
final Animation<Offset> _primaryPositionAnimation;
|
||||
@ -330,12 +330,16 @@ class CupertinoPageTransition extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasDirectionality(context));
|
||||
final TextDirection textDirection = Directionality.of(context);
|
||||
// TODO(ianh): tell the transform to be un-transformed for hit testing
|
||||
// but not while being controlled by a gesture.
|
||||
return new SlideTransition(
|
||||
position: _secondaryPositionAnimation,
|
||||
textDirection: textDirection,
|
||||
child: new SlideTransition(
|
||||
position: _primaryPositionAnimation,
|
||||
textDirection: textDirection,
|
||||
child: new DecoratedBoxTransition(
|
||||
decoration: _primaryShadowAnimation,
|
||||
child: child,
|
||||
@ -382,6 +386,9 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget {
|
||||
/// This widget provides a gesture recognizer which, when it determines the
|
||||
/// route can be closed with a back gesture, creates the controller and
|
||||
/// feeds it the input from the gesture recognizer.
|
||||
///
|
||||
/// The gesture data is converted from absolute coordinates to logical
|
||||
/// coordinates by this widget.
|
||||
class _CupertinoBackGestureDetector extends StatefulWidget {
|
||||
const _CupertinoBackGestureDetector({
|
||||
Key key,
|
||||
@ -433,13 +440,13 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete
|
||||
void _handleDragUpdate(DragUpdateDetails details) {
|
||||
assert(mounted);
|
||||
assert(_backGestureController != null);
|
||||
_backGestureController.dragUpdate(details.primaryDelta / context.size.width);
|
||||
_backGestureController.dragUpdate(_convertToLogical(details.primaryDelta / context.size.width));
|
||||
}
|
||||
|
||||
void _handleDragEnd(DragEndDetails details) {
|
||||
assert(mounted);
|
||||
assert(_backGestureController != null);
|
||||
_backGestureController.dragEnd(details.velocity.pixelsPerSecond.dx / context.size.width);
|
||||
_backGestureController.dragEnd(_convertToLogical(details.velocity.pixelsPerSecond.dx / context.size.width));
|
||||
_backGestureController = null;
|
||||
}
|
||||
|
||||
@ -456,14 +463,25 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete
|
||||
_recognizer.addPointer(event);
|
||||
}
|
||||
|
||||
double _convertToLogical(double value) {
|
||||
switch (Directionality.of(context)) {
|
||||
case TextDirection.rtl:
|
||||
return -value;
|
||||
case TextDirection.ltr:
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasDirectionality(context));
|
||||
return new Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: <Widget>[
|
||||
widget.child,
|
||||
new Positioned(
|
||||
left: 0.0,
|
||||
new PositionedDirectional(
|
||||
start: 0.0,
|
||||
width: _kBackGestureWidth,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
@ -484,6 +502,9 @@ class _CupertinoBackGestureDetectorState extends State<_CupertinoBackGestureDete
|
||||
/// by a [_CupertinoBackGestureDetector] widget, which then also feeds it input
|
||||
/// from the gesture. It controls the animation controller owned by the route,
|
||||
/// based on the input provided by the gesture detector.
|
||||
///
|
||||
/// This class works entirely in logical coordinates (0.0 is new page dismissed,
|
||||
/// 1.0 is new page on top).
|
||||
class _CupertinoBackGestureController {
|
||||
/// Creates a controller for an iOS-style back gesture.
|
||||
///
|
||||
@ -553,9 +574,15 @@ class _CupertinoBackGestureController {
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom [Decoration] used to paint an extra shadow on the left edge of the
|
||||
/// box it's decorating. It's like a [BoxDecoration] with only a gradient except
|
||||
/// it paints to the left of the box instead of behind the box.
|
||||
// A custom [Decoration] used to paint an extra shadow on the start edge of the
|
||||
// box it's decorating. It's like a [BoxDecoration] with only a gradient except
|
||||
// it paints on the start side of the box instead of behind the box.
|
||||
//
|
||||
// The [edgeGradient] will be given a [TextDirection] when its shader is
|
||||
// created, and so can be direction-sensitive; in this file we set it to a
|
||||
// gradient that uses an AlignmentDirectional to position the gradient on the
|
||||
// end edge of the gradient's box (which will be the edge adjacent to the start
|
||||
// edge of the actual box we're supposed to paint in).
|
||||
class _CupertinoEdgeShadowDecoration extends Decoration {
|
||||
const _CupertinoEdgeShadowDecoration({ this.edgeGradient });
|
||||
|
||||
@ -604,18 +631,14 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
if (other.runtimeType != _CupertinoEdgeShadowDecoration)
|
||||
if (runtimeType != other.runtimeType)
|
||||
return false;
|
||||
final _CupertinoEdgeShadowDecoration typedOther = other;
|
||||
return edgeGradient == typedOther.edgeGradient;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return edgeGradient.hashCode;
|
||||
}
|
||||
int get hashCode => edgeGradient.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
@ -628,7 +651,7 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
|
||||
class _CupertinoEdgeShadowPainter extends BoxPainter {
|
||||
_CupertinoEdgeShadowPainter(
|
||||
this._decoration,
|
||||
VoidCallback onChange
|
||||
VoidCallback onChange,
|
||||
) : assert(_decoration != null),
|
||||
super(onChange);
|
||||
|
||||
@ -640,11 +663,21 @@ class _CupertinoEdgeShadowPainter extends BoxPainter {
|
||||
if (gradient == null)
|
||||
return;
|
||||
// The drawable space for the gradient is a rect with the same size as
|
||||
// its parent box one box width to the left of the box.
|
||||
final Rect rect =
|
||||
(offset & configuration.size).translate(-configuration.size.width, 0.0);
|
||||
// its parent box one box width on the start side of the box.
|
||||
final TextDirection textDirection = configuration.textDirection;
|
||||
assert(textDirection != null);
|
||||
double deltaX;
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
deltaX = configuration.size.width;
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
deltaX = -configuration.size.width;
|
||||
break;
|
||||
}
|
||||
final Rect rect = (offset & configuration.size).translate(deltaX, 0.0);
|
||||
final Paint paint = new Paint()
|
||||
..shader = gradient.createShader(rect);
|
||||
..shader = gradient.createShader(rect, textDirection: textDirection);
|
||||
|
||||
canvas.drawRect(rect, paint);
|
||||
}
|
||||
|
@ -343,13 +343,13 @@ class _RenderCupertinoSlider extends RenderConstrainedBox {
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
visualPosition = 1.0 - _position.value;
|
||||
leftColor = _kTrackColor;
|
||||
rightColor = _activeColor;
|
||||
leftColor = _activeColor;
|
||||
rightColor = _kTrackColor;
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
visualPosition = _position.value;
|
||||
leftColor = _activeColor;
|
||||
rightColor = _kTrackColor;
|
||||
leftColor = _kTrackColor;
|
||||
rightColor = _activeColor;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -101,6 +101,12 @@ class _AnimatedState extends State<AnimatedWidget> {
|
||||
/// The translation is expressed as a [Offset] scaled to the child's size. For
|
||||
/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
|
||||
/// translation of one quarter the width of the child.
|
||||
///
|
||||
/// By default, the offsets are applied in the coordinate system of the canvas
|
||||
/// (so positive x offsets move the child towards the right). If a
|
||||
/// [textDirection] is provided, then the offsets are applied in the reading
|
||||
/// direction, so in right-to-left text, positive x offsets move towards the
|
||||
/// left, and in left-to-right text, positive x offsets move towards the right.
|
||||
class SlideTransition extends AnimatedWidget {
|
||||
/// Creates a fractional translation transition.
|
||||
///
|
||||
@ -109,6 +115,7 @@ class SlideTransition extends AnimatedWidget {
|
||||
Key key,
|
||||
@required Animation<Offset> position,
|
||||
this.transformHitTests: true,
|
||||
this.textDirection,
|
||||
this.child,
|
||||
}) : assert(position != null),
|
||||
super(key: key, listenable: position);
|
||||
@ -117,9 +124,22 @@ class SlideTransition extends AnimatedWidget {
|
||||
///
|
||||
/// If the current value of the position animation is `(dx, dy)`, the child
|
||||
/// will be translated horizontally by `width * dx` and vertically by
|
||||
/// `height * dy`.
|
||||
/// `height * dy`, after applying the [textDirection] if available.
|
||||
Animation<Offset> get position => listenable;
|
||||
|
||||
/// The direction to use for the x offset described by the [position].
|
||||
///
|
||||
/// If [textDirection] is null, the x offset is applied in the coordinate
|
||||
/// system of the canvas (so positive x offsets move the child towards the
|
||||
/// right).
|
||||
///
|
||||
/// If [textDirection] is [TextDirection.rtl], the x offset is applied in the
|
||||
/// reading direction such that x offsets move the child towards the left.
|
||||
///
|
||||
/// If [textDirection] is [TextDirection.ltr], the x offset is applied in the
|
||||
/// reading direction such that x offsets move the child towards the right.
|
||||
final TextDirection textDirection;
|
||||
|
||||
/// Whether hit testing should be affected by the slide animation.
|
||||
///
|
||||
/// If false, hit testing will proceed as if the child was not translated at
|
||||
@ -133,8 +153,11 @@ class SlideTransition extends AnimatedWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Offset offset = position.value;
|
||||
if (textDirection == TextDirection.rtl)
|
||||
offset = new Offset(-offset.dx, offset.dy);
|
||||
return new FractionalTranslation(
|
||||
translation: position.value,
|
||||
translation: offset,
|
||||
transformHitTests: transformHitTests,
|
||||
child: child,
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('test iOS page transition', (WidgetTester tester) async {
|
||||
testWidgets('test iOS page transition (LTR)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
@ -74,6 +74,78 @@ void main() {
|
||||
expect(widget1InitialTopLeft, equals(widget1TransientTopLeft));
|
||||
});
|
||||
|
||||
testWidgets('test iOS page transition (RTL)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
|
||||
const RtlOverrideWidgetsDelegate(),
|
||||
],
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return new CupertinoPageRoute<Null>(
|
||||
settings: settings,
|
||||
builder: (BuildContext context) {
|
||||
final String pageNumber = settings.name == '/' ? '1' : '2';
|
||||
return new Center(child: new Text('Page $pageNumber'));
|
||||
}
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
await tester.pump(); // to load the localization, since it doesn't use a synchronous future
|
||||
|
||||
final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
|
||||
Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
|
||||
// Page 1 is moving to the right.
|
||||
expect(widget1TransientTopLeft.dx, greaterThan(widget1InitialTopLeft.dx));
|
||||
// Page 1 isn't moving vertically.
|
||||
expect(widget1TransientTopLeft.dy, equals(widget1InitialTopLeft.dy));
|
||||
// iOS transition is horizontal only.
|
||||
expect(widget1InitialTopLeft.dy, equals(widget2TopLeft.dy));
|
||||
// Page 2 is coming in from the left.
|
||||
expect(widget2TopLeft.dx, lessThan(widget1InitialTopLeft.dx));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Page 2 covers page 1.
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
|
||||
|
||||
// Page 1 is coming back from the right.
|
||||
expect(widget1TransientTopLeft.dx, greaterThan(widget1InitialTopLeft.dx));
|
||||
// Page 1 isn't moving vertically.
|
||||
expect(widget1TransientTopLeft.dy, equals(widget1InitialTopLeft.dy));
|
||||
// iOS transition is horizontal only.
|
||||
expect(widget1InitialTopLeft.dy, equals(widget2TopLeft.dy));
|
||||
// Page 2 is leaving towards the left.
|
||||
expect(widget2TopLeft.dx, lessThan(widget1InitialTopLeft.dx));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Page 1'), isOnstage);
|
||||
expect(find.text('Page 2'), findsNothing);
|
||||
|
||||
widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
|
||||
|
||||
// Page 1 is back where it started.
|
||||
expect(widget1InitialTopLeft, equals(widget1TransientTopLeft));
|
||||
});
|
||||
|
||||
testWidgets('test iOS fullscreen dialog transition', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
@ -142,7 +214,7 @@ void main() {
|
||||
expect(widget1InitialTopLeft, equals(widget1TransientTopLeft));
|
||||
});
|
||||
|
||||
testWidgets('test only edge swipes work', (WidgetTester tester) async {
|
||||
testWidgets('test only edge swipes work (LTR)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
@ -176,7 +248,25 @@ void main() {
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Now drag from the edge.
|
||||
// Drag from the right to the left.
|
||||
gesture = await tester.startGesture(const Offset(795.0, 200.0));
|
||||
await gesture.moveBy(const Offset(-300.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Nothing should happen.
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Drag from the right to the further right.
|
||||
gesture = await tester.startGesture(const Offset(795.0, 200.0));
|
||||
await gesture.moveBy(const Offset(300.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Nothing should happen.
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Now drag from the left edge.
|
||||
gesture = await tester.startGesture(const Offset(5.0, 200.0));
|
||||
await gesture.moveBy(const Offset(300.0, 0.0));
|
||||
await tester.pump();
|
||||
@ -185,4 +275,86 @@ void main() {
|
||||
expect(find.text('Page 1'), isOnstage);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
});
|
||||
|
||||
testWidgets('test only edge swipes work (RTL)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new WidgetsApp(
|
||||
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
|
||||
const RtlOverrideWidgetsDelegate(),
|
||||
],
|
||||
color: const Color(0xFFFFFFFF),
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return new CupertinoPageRoute<Null>(
|
||||
settings: settings,
|
||||
builder: (BuildContext context) {
|
||||
final String pageNumber = settings.name == '/' ? '1' : '2';
|
||||
return new Center(child: new Text('Page $pageNumber'));
|
||||
}
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
await tester.pump(); // to load the localization, since it doesn't use a synchronous future
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 400));
|
||||
|
||||
// Page 2 covers page 1.
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Drag from the middle to the left.
|
||||
TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0));
|
||||
await gesture.moveBy(const Offset(-300.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Nothing should happen.
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Drag from the left to the right.
|
||||
gesture = await tester.startGesture(const Offset(5.0, 200.0));
|
||||
await gesture.moveBy(const Offset(300.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Nothing should happen.
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Drag from the left to the further left.
|
||||
gesture = await tester.startGesture(const Offset(5.0, 200.0));
|
||||
await gesture.moveBy(const Offset(-300.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Nothing should happen.
|
||||
expect(find.text('Page 1'), findsNothing);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
|
||||
// Now drag from the right edge.
|
||||
gesture = await tester.startGesture(const Offset(795.0, 200.0));
|
||||
await gesture.moveBy(const Offset(-300.0, 0.0));
|
||||
await tester.pump();
|
||||
|
||||
// Page 1 is now visible.
|
||||
expect(find.text('Page 1'), isOnstage);
|
||||
expect(find.text('Page 2'), isOnstage);
|
||||
});
|
||||
}
|
||||
|
||||
class RtlOverrideWidgetsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
||||
const RtlOverrideWidgetsDelegate();
|
||||
@override
|
||||
bool isSupported(Locale locale) => true;
|
||||
@override
|
||||
Future<WidgetsLocalizations> load(Locale locale) async => const RtlOverrideWidgetsLocalization();
|
||||
@override
|
||||
bool shouldReload(LocalizationsDelegate<WidgetsLocalizations> oldDelegate) => false;
|
||||
}
|
||||
|
||||
class RtlOverrideWidgetsLocalization implements WidgetsLocalizations {
|
||||
const RtlOverrideWidgetsLocalization();
|
||||
@override
|
||||
TextDirection get textDirection => TextDirection.rtl;
|
||||
}
|
Loading…
Reference in New Issue
Block a user