Commit 1097d92a authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix DatePicker (#5061)

Fundamentally the core problem was that we were not saying how wide a
date picker should be. It should be 330 pixels, if I'm measuring the
spec's mocks correctly.
parent 9f509daf
...@@ -118,8 +118,7 @@ class _DatePickerState extends State<DatePicker> { ...@@ -118,8 +118,7 @@ class _DatePickerState extends State<DatePicker> {
); );
break; break;
} }
return new Column( return new BlockBody(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
header, header,
new Container( new Container(
...@@ -213,7 +212,6 @@ const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCou ...@@ -213,7 +212,6 @@ const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCou
class _DayPickerGridDelegate extends GridDelegateWithInOrderChildPlacement { class _DayPickerGridDelegate extends GridDelegateWithInOrderChildPlacement {
@override @override
GridSpecification getGridSpecification(BoxConstraints constraints, int childCount) { GridSpecification getGridSpecification(BoxConstraints constraints, int childCount) {
assert(constraints.maxWidth < double.INFINITY);
final int columnCount = DateTime.DAYS_PER_WEEK; final int columnCount = DateTime.DAYS_PER_WEEK;
return new GridSpecification.fromRegularTiles( return new GridSpecification.fromRegularTiles(
tileWidth: constraints.maxWidth / columnCount, tileWidth: constraints.maxWidth / columnCount,
...@@ -459,34 +457,37 @@ class _MonthPickerState extends State<MonthPicker> { ...@@ -459,34 +457,37 @@ class _MonthPickerState extends State<MonthPicker> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Stack( return new SizedBox(
children: <Widget>[ width: 330.0,
new PageableLazyList( child: new Stack(
key: _dayPickerListKey, children: <Widget>[
initialScrollOffset: _monthDelta(config.firstDate, config.selectedDate).toDouble(), new PageableLazyList(
scrollDirection: Axis.horizontal, key: _dayPickerListKey,
itemCount: _monthDelta(config.firstDate, config.lastDate) + 1, initialScrollOffset: _monthDelta(config.firstDate, config.selectedDate).toDouble(),
itemBuilder: _buildItems scrollDirection: Axis.horizontal,
), itemCount: _monthDelta(config.firstDate, config.lastDate) + 1,
new Positioned( itemBuilder: _buildItems
top: 0.0, ),
left: 8.0, new Positioned(
child: new IconButton( top: 0.0,
icon: new Icon(Icons.chevron_left), left: 8.0,
tooltip: 'Previous month', child: new IconButton(
onPressed: _handlePreviousMonth icon: new Icon(Icons.chevron_left),
) tooltip: 'Previous month',
), onPressed: _handlePreviousMonth
new Positioned( )
top: 0.0, ),
right: 8.0, new Positioned(
child: new IconButton( top: 0.0,
icon: new Icon(Icons.chevron_right), right: 8.0,
tooltip: 'Next month', child: new IconButton(
onPressed: _handleNextMonth icon: new Icon(Icons.chevron_right),
tooltip: 'Next month',
onPressed: _handleNextMonth
)
) )
) ]
] )
); );
} }
......
...@@ -33,7 +33,7 @@ class TimeOfDay { ...@@ -33,7 +33,7 @@ class TimeOfDay {
/// ///
/// The [hour] argument must be between 0 and 23, inclusive. The [minute] /// The [hour] argument must be between 0 and 23, inclusive. The [minute]
/// argument must be between 0 and 59, inclusive. /// argument must be between 0 and 59, inclusive.
const TimeOfDay({ this.hour, this.minute }); const TimeOfDay({ @required this.hour, @required this.minute });
/// Returns a new TimeOfDay with the hour and/or minute replaced. /// Returns a new TimeOfDay with the hour and/or minute replaced.
TimeOfDay replacing({ int hour, int minute }) { TimeOfDay replacing({ int hour, int minute }) {
...@@ -42,7 +42,7 @@ class TimeOfDay { ...@@ -42,7 +42,7 @@ class TimeOfDay {
return new TimeOfDay(hour: hour ?? this.hour, minute: minute ?? this.minute); return new TimeOfDay(hour: hour ?? this.hour, minute: minute ?? this.minute);
} }
/// The selected hour, in 24 hour time from 0..23 /// The selected hour, in 24 hour time from 0..23.
final int hour; final int hour;
/// The selected minute. /// The selected minute.
......
...@@ -197,6 +197,8 @@ class RenderConstrainedBox extends RenderProxyBox { ...@@ -197,6 +197,8 @@ class RenderConstrainedBox extends RenderProxyBox {
@override @override
double computeMinIntrinsicWidth(double height) { double computeMinIntrinsicWidth(double height) {
if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth)
return _additionalConstraints.minWidth;
final double width = super.computeMinIntrinsicWidth(height); final double width = super.computeMinIntrinsicWidth(height);
if (_additionalConstraints.hasBoundedWidth) if (_additionalConstraints.hasBoundedWidth)
return _additionalConstraints.constrainWidth(width); return _additionalConstraints.constrainWidth(width);
...@@ -205,6 +207,8 @@ class RenderConstrainedBox extends RenderProxyBox { ...@@ -205,6 +207,8 @@ class RenderConstrainedBox extends RenderProxyBox {
@override @override
double computeMaxIntrinsicWidth(double height) { double computeMaxIntrinsicWidth(double height) {
if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth)
return _additionalConstraints.minWidth;
final double width = super.computeMaxIntrinsicWidth(height); final double width = super.computeMaxIntrinsicWidth(height);
if (_additionalConstraints.hasBoundedWidth) if (_additionalConstraints.hasBoundedWidth)
return _additionalConstraints.constrainWidth(width); return _additionalConstraints.constrainWidth(width);
...@@ -213,6 +217,8 @@ class RenderConstrainedBox extends RenderProxyBox { ...@@ -213,6 +217,8 @@ class RenderConstrainedBox extends RenderProxyBox {
@override @override
double computeMinIntrinsicHeight(double width) { double computeMinIntrinsicHeight(double width) {
if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight)
return _additionalConstraints.minHeight;
final double height = super.computeMinIntrinsicHeight(width); final double height = super.computeMinIntrinsicHeight(width);
if (_additionalConstraints.hasBoundedHeight) if (_additionalConstraints.hasBoundedHeight)
return _additionalConstraints.constrainHeight(height); return _additionalConstraints.constrainHeight(height);
...@@ -221,6 +227,8 @@ class RenderConstrainedBox extends RenderProxyBox { ...@@ -221,6 +227,8 @@ class RenderConstrainedBox extends RenderProxyBox {
@override @override
double computeMaxIntrinsicHeight(double width) { double computeMaxIntrinsicHeight(double width) {
if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight)
return _additionalConstraints.minHeight;
final double height = super.computeMaxIntrinsicHeight(width); final double height = super.computeMaxIntrinsicHeight(width);
if (_additionalConstraints.hasBoundedHeight) if (_additionalConstraints.hasBoundedHeight)
return _additionalConstraints.constrainHeight(height); return _additionalConstraints.constrainHeight(height);
......
...@@ -400,7 +400,7 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende ...@@ -400,7 +400,7 @@ abstract class RenderVirtualViewport<T extends ContainerBoxParentDataMixin<Rende
assert(() { assert(() {
if (!RenderObject.debugCheckingIntrinsics) { if (!RenderObject.debugCheckingIntrinsics) {
throw new FlutterError( throw new FlutterError(
'RenderVirtualViewport does not support returning intrinsic dimensions.\n' '$runtimeType does not support returning intrinsic dimensions.\n'
'Calculating the intrinsic dimensions would require walking the entire ' 'Calculating the intrinsic dimensions would require walking the entire '
'child list, which cannot reliably and efficiently be done for render ' 'child list, which cannot reliably and efficiently be done for render '
'objects that potentially generate their child list during layout.' 'objects that potentially generate their child list during layout.'
......
// Copyright 2016 The Chromium 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/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('tap-select an day', (WidgetTester tester) async {
Key _datePickerKey = new UniqueKey();
DateTime _selectedDate = new DateTime(2016, DateTime.JULY, 26);
await tester.pumpWidget(
new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) => new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Positioned(
width: 400.0,
child: new Block(
children: <Widget>[
new Material(
child: new DatePicker(
firstDate: new DateTime(0),
lastDate: new DateTime(9999),
key: _datePickerKey,
selectedDate: _selectedDate,
onChanged: (DateTime value) {
setState(() {
_selectedDate = value;
});
}
)
)
]
)
);
}
)
)
]
)
);
await tester.tapAt(const Point(50.0, 200.0));
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 26)));
await tester.pump(const Duration(seconds: 2));
await tester.tapAt(const Point(300.0, 200.0));
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 1)));
await tester.pump(const Duration(seconds: 2));
await tester.tapAt(const Point(380.0, 120.0));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
expect(_selectedDate, equals(new DateTime(2016, DateTime.JULY, 1)));
await tester.tapAt(const Point(300.0, 200.0));
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 5)));
await tester.pump(const Duration(seconds: 2));
await tester.scroll(find.byKey(_datePickerKey), const Offset(-300.0, 0.0));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 5)));
await tester.tapAt(const Point(45.0, 370.0));
expect(_selectedDate, equals(new DateTime(2016, DateTime.SEPTEMBER, 25)));
await tester.pump(const Duration(seconds: 2));
await tester.scroll(find.byKey(_datePickerKey), const Offset(300.0, 10.0));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
expect(_selectedDate, equals(new DateTime(2016, DateTime.SEPTEMBER, 25)));
await tester.tapAt(const Point(210.0, 280.0));
expect(_selectedDate, equals(new DateTime(2016, DateTime.AUGUST, 17)));
await tester.pump(const Duration(seconds: 2));
});
testWidgets('render picker with intrinsic dimensions', (WidgetTester tester) async {
await tester.pumpWidget(
new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) => new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new IntrinsicWidth(
child: new IntrinsicHeight(
child: new Material(
child: new Block(
children: <Widget>[
new DatePicker(
firstDate: new DateTime(0),
lastDate: new DateTime(9999),
onChanged: null,
selectedDate: new DateTime(2000, DateTime.JANUARY, 1)
)
]
)
)
)
);
}
)
)
]
)
);
await tester.pump(const Duration(seconds: 5));
});
}
...@@ -15,7 +15,7 @@ void main() { ...@@ -15,7 +15,7 @@ void main() {
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return new Material( return new Material(
child: new Center( child: new Center(
child: new SizedBox( child: new SizedBox(
width: 200.0, width: 200.0,
height: 400.0, height: 400.0,
child: new TimePicker( child: new TimePicker(
...@@ -56,6 +56,20 @@ void main() { ...@@ -56,6 +56,20 @@ void main() {
await tester.pump(const Duration(seconds: 1)); // Finish settling animation. await tester.pump(const Duration(seconds: 1)); // Finish settling animation.
}); });
testWidgets('render picker with intrinsic dimensions', (WidgetTester tester) async {
await tester.pumpWidget(
new IntrinsicWidth(
child: new IntrinsicHeight(
child: new TimePicker(
onChanged: null,
selectedTime: new TimeOfDay(hour: 0, minute: 0)
)
)
)
);
await tester.pump(const Duration(seconds: 5));
});
testWidgets('drag-select an hour', (WidgetTester tester) async { testWidgets('drag-select an hour', (WidgetTester tester) async {
Key _timePickerKey = new UniqueKey(); Key _timePickerKey = new UniqueKey();
TimeOfDay _selectedTime = const TimeOfDay(hour: 7, minute: 0); TimeOfDay _selectedTime = const TimeOfDay(hour: 7, minute: 0);
......
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