Commit 036f7ef5 authored by Adam Barth's avatar Adam Barth Committed by Adam Barth

Add support for landscape date pickers

Fixes #5141
parent a3b06f31
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application android:icon="@mipmap/ic_launcher" android:label="Gallery" android:name="org.domokit.sky.shell.SkyApplication"> <application android:icon="@mipmap/ic_launcher" android:label="Gallery" android:name="org.domokit.sky.shell.SkyApplication">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTask" android:name="org.domokit.sky.shell.SkyActivity" android:theme="@android:style/Theme.Light.NoTitleBar" android:screenOrientation="sensorPortrait"> <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTask" android:name="org.domokit.sky.shell.SkyActivity" android:theme="@android:style/Theme.Light.NoTitleBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -25,14 +26,20 @@ import 'typography.dart'; ...@@ -25,14 +26,20 @@ import 'typography.dart';
enum _DatePickerMode { day, year } enum _DatePickerMode { day, year }
const double _kDatePickerHeaderHeight = 100.0; const double _kDatePickerHeaderPortraitHeight = 100.0;
const double _kDatePickerHeaderLandscapeWidth = 168.0;
const Duration _kMonthScrollDuration = const Duration(milliseconds: 200); const Duration _kMonthScrollDuration = const Duration(milliseconds: 200);
const double _kDayPickerRowHeight = 42.0; const double _kDayPickerRowHeight = 42.0;
const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday. const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
// Two extra rows: one for the day-of-week header and one for the month header. // Two extra rows: one for the day-of-week header and one for the month header.
const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2); const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2);
const double _kMonthPickerWidth = 330.0;
const double _kMonthPickerPortraitWidth = 330.0;
const double _kMonthPickerLandscapeWidth = 344.0;
const double _kDialogActionBarHeight = 52.0;
const double _kDatePickerLandscapeHeight = _kMaxDayPickerHeight + _kDialogActionBarHeight;
// Shows the selected date in large font and toggles between year and day mode // Shows the selected date in large font and toggles between year and day mode
class _DatePickerHeader extends StatelessWidget { class _DatePickerHeader extends StatelessWidget {
...@@ -40,15 +47,18 @@ class _DatePickerHeader extends StatelessWidget { ...@@ -40,15 +47,18 @@ class _DatePickerHeader extends StatelessWidget {
Key key, Key key,
@required this.selectedDate, @required this.selectedDate,
@required this.mode, @required this.mode,
@required this.onModeChanged @required this.onModeChanged,
@required this.orientation,
}) : super(key: key) { }) : super(key: key) {
assert(selectedDate != null); assert(selectedDate != null);
assert(mode != null); assert(mode != null);
assert(orientation != null);
} }
final DateTime selectedDate; final DateTime selectedDate;
final _DatePickerMode mode; final _DatePickerMode mode;
final ValueChanged<_DatePickerMode> onModeChanged; final ValueChanged<_DatePickerMode> onModeChanged;
final Orientation orientation;
void _handleChangeMode(_DatePickerMode value) { void _handleChangeMode(_DatePickerMode value) {
if (value != mode) if (value != mode)
...@@ -84,12 +94,30 @@ class _DatePickerHeader extends StatelessWidget { ...@@ -84,12 +94,30 @@ class _DatePickerHeader extends StatelessWidget {
break; break;
} }
double height;
double width;
EdgeInsets padding;
MainAxisAlignment mainAxisAlignment;
switch (orientation) {
case Orientation.portrait:
height = _kDatePickerHeaderPortraitHeight;
padding = const EdgeInsets.symmetric(horizontal: 24.0);
mainAxisAlignment = MainAxisAlignment.center;
break;
case Orientation.landscape:
width = _kDatePickerHeaderLandscapeWidth;
padding = const EdgeInsets.all(16.0);
mainAxisAlignment = MainAxisAlignment.start;
break;
}
return new Container( return new Container(
height: _kDatePickerHeaderHeight, width: width,
padding: const EdgeInsets.symmetric(horizontal: 24.0), height: height,
padding: padding,
decoration: new BoxDecoration(backgroundColor: backgroundColor), decoration: new BoxDecoration(backgroundColor: backgroundColor),
child: new Column( child: new Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
new GestureDetector( new GestureDetector(
...@@ -98,7 +126,7 @@ class _DatePickerHeader extends StatelessWidget { ...@@ -98,7 +126,7 @@ class _DatePickerHeader extends StatelessWidget {
), ),
new GestureDetector( new GestureDetector(
onTap: () => _handleChangeMode(_DatePickerMode.day), onTap: () => _handleChangeMode(_DatePickerMode.day),
child: new Text(new DateFormat('MMMEd').format(selectedDate), style: dayStyle) child: new Text(new DateFormat('E, MMM\u00a0d').format(selectedDate), style: dayStyle)
), ),
] ]
) )
...@@ -112,7 +140,7 @@ class _DayPickerGridDelegate extends GridDelegateWithInOrderChildPlacement { ...@@ -112,7 +140,7 @@ class _DayPickerGridDelegate extends GridDelegateWithInOrderChildPlacement {
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,
tileHeight: _kDayPickerRowHeight, tileHeight: math.min(_kDayPickerRowHeight, constraints.maxHeight / (_kMaxDayPickerRowCount + 1)),
columnCount: columnCount, columnCount: columnCount,
rowCount: (childCount / columnCount).ceil() rowCount: (childCount / columnCount).ceil()
); );
...@@ -236,9 +264,12 @@ class DayPicker extends StatelessWidget { ...@@ -236,9 +264,12 @@ class DayPicker extends StatelessWidget {
) )
) )
), ),
new CustomGrid( new Flexible(
delegate: _kDayPickerGridDelegate, fit: FlexFit.loose,
children: labels child: new CustomGrid(
delegate: _kDayPickerGridDelegate,
children: labels
)
) )
] ]
) )
...@@ -357,7 +388,7 @@ class _MonthPickerState extends State<MonthPicker> { ...@@ -357,7 +388,7 @@ class _MonthPickerState extends State<MonthPicker> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new SizedBox( return new SizedBox(
width: _kMonthPickerWidth, width: _kMonthPickerPortraitWidth,
height: _kMaxDayPickerHeight, height: _kMaxDayPickerHeight,
child: new Stack( child: new Stack(
children: <Widget>[ children: <Widget>[
...@@ -507,6 +538,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -507,6 +538,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
DateTime _selectedDate; DateTime _selectedDate;
_DatePickerMode _mode = _DatePickerMode.day; _DatePickerMode _mode = _DatePickerMode.day;
GlobalKey _pickerKey = new GlobalKey();
void _handleModeChanged(_DatePickerMode mode) { void _handleModeChanged(_DatePickerMode mode) {
HapticFeedback.vibrate(); HapticFeedback.vibrate();
...@@ -543,6 +575,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -543,6 +575,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
switch (_mode) { switch (_mode) {
case _DatePickerMode.day: case _DatePickerMode.day:
return new MonthPicker( return new MonthPicker(
key: _pickerKey,
selectedDate: _selectedDate, selectedDate: _selectedDate,
onChanged: _handleDayChanged, onChanged: _handleDayChanged,
firstDate: config.firstDate, firstDate: config.firstDate,
...@@ -550,6 +583,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -550,6 +583,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
); );
case _DatePickerMode.year: case _DatePickerMode.year:
return new YearPicker( return new YearPicker(
key: _pickerKey,
selectedDate: _selectedDate, selectedDate: _selectedDate,
onChanged: _handleYearChanged, onChanged: _handleYearChanged,
firstDate: config.firstDate, firstDate: config.firstDate,
...@@ -561,39 +595,71 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -561,39 +595,71 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Dialog( Widget picker = new Flexible(
fit: FlexFit.loose,
child: new SizedBox( child: new SizedBox(
width: _kMonthPickerWidth, height: _kMaxDayPickerHeight,
child: new Column( child: _buildPicker(),
mainAxisSize: MainAxisSize.min, )
crossAxisAlignment: CrossAxisAlignment.stretch, );
children: <Widget>[ Widget actions = new ButtonTheme.bar(
new _DatePickerHeader( child: new ButtonBar(
selectedDate: _selectedDate, alignment: MainAxisAlignment.end,
mode: _mode, children: <Widget>[
onModeChanged: _handleModeChanged new FlatButton(
), child: new Text('CANCEL'),
new Container( onPressed: _handleCancel
height: _kMaxDayPickerHeight, ),
child: _buildPicker() new FlatButton(
), child: new Text('OK'),
new ButtonTheme.bar( onPressed: _handleOk
child: new ButtonBar( ),
alignment: MainAxisAlignment.end, ]
children: <Widget>[ )
new FlatButton( );
child: new Text('CANCEL'),
onPressed: _handleCancel return new Dialog(
), child: new OrientationBuilder(
new FlatButton( builder: (BuildContext context, Orientation orientation) {
child: new Text('OK'), Widget header = new _DatePickerHeader(
onPressed: _handleOk selectedDate: _selectedDate,
), mode: _mode,
] onModeChanged: _handleModeChanged,
) orientation: orientation
) );
] assert(orientation != null);
) switch (orientation) {
case Orientation.portrait:
return new SizedBox(
width: _kMonthPickerPortraitWidth,
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[header, picker, actions]
)
);
case Orientation.landscape:
return new SizedBox(
height: _kDatePickerLandscapeHeight,
child: new Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
new SizedBox(
width: _kMonthPickerLandscapeWidth,
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[picker, actions]
)
)
]
)
);
}
return null;
}
) )
); );
} }
......
// 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:meta/meta.dart';
import 'basic.dart';
import 'framework.dart';
import 'layout_builder.dart';
import 'media_query.dart';
/// Signature for a function that builds a widget given an [Orientation].
typedef Widget OrientationWidgetBuilder(BuildContext context, Orientation orientation);
class OrientationBuilder extends StatelessWidget {
/// Creates an orientation builder.
///
/// The [builder] argument must not be null.
OrientationBuilder({
Key key,
@required this.builder
}) : super(key: key) {
assert(builder != null);
}
/// Builds the widgets below this widget given this widget's orientation.
final OrientationWidgetBuilder builder;
Widget _buildWithConstraints(BuildContext context, BoxConstraints constraints) {
// If the constraints are fully unbounded (i.e., maxWidth and maxHeight are
// both infinite), we prefer Orientation.portrait because its more common to
// scroll vertially than horizontally.
final Orientation orientation = constraints.maxWidth > constraints.maxHeight ? Orientation.landscape : Orientation.portrait;
return builder(context, orientation);
}
@override
Widget build(BuildContext context) {
return new LayoutBuilder(builder: _buildWithConstraints);
}
}
...@@ -38,6 +38,7 @@ export 'src/widgets/mimic.dart'; ...@@ -38,6 +38,7 @@ export 'src/widgets/mimic.dart';
export 'src/widgets/modal_barrier.dart'; export 'src/widgets/modal_barrier.dart';
export 'src/widgets/navigator.dart'; export 'src/widgets/navigator.dart';
export 'src/widgets/notification_listener.dart'; export 'src/widgets/notification_listener.dart';
export 'src/widgets/orientation_builder.dart';
export 'src/widgets/overlay.dart'; export 'src/widgets/overlay.dart';
export 'src/widgets/page_storage.dart'; export 'src/widgets/page_storage.dart';
export 'src/widgets/pageable_list.dart'; export 'src/widgets/pageable_list.dart';
......
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