Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
5fa937ed
Unverified
Commit
5fa937ed
authored
Jul 07, 2020
by
Darren Austin
Committed by
GitHub
Jul 07, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Keyboard navigation fo the Material Date Range Picker (#60497)
parent
6f4c4b3c
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
484 additions
and
66 deletions
+484
-66
calendar_date_picker.dart
...lutter/lib/src/material/pickers/calendar_date_picker.dart
+8
-11
calendar_date_range_picker.dart
.../lib/src/material/pickers/calendar_date_range_picker.dart
+281
-54
date_picker_common.dart
.../flutter/lib/src/material/pickers/date_picker_common.dart
+3
-0
date_range_picker_test.dart
packages/flutter/test/material/date_range_picker_test.dart
+192
-1
No files found.
packages/flutter/lib/src/material/pickers/calendar_date_picker.dart
View file @
5fa937ed
...
@@ -470,7 +470,7 @@ class _MonthPicker extends StatefulWidget {
...
@@ -470,7 +470,7 @@ class _MonthPicker extends StatefulWidget {
final
SelectableDayPredicate
selectableDayPredicate
;
final
SelectableDayPredicate
selectableDayPredicate
;
@override
@override
State
<
StatefulWidget
>
createState
()
=>
_MonthPickerState
();
_MonthPickerState
createState
()
=>
_MonthPickerState
();
}
}
class
_MonthPickerState
extends
State
<
_MonthPicker
>
{
class
_MonthPickerState
extends
State
<
_MonthPicker
>
{
...
@@ -754,16 +754,13 @@ class _MonthPickerState extends State<_MonthPicker> {
...
@@ -754,16 +754,13 @@ class _MonthPickerState extends State<_MonthPicker> {
onFocusChange:
_handleGridFocusChange
,
onFocusChange:
_handleGridFocusChange
,
child:
_FocusedDate
(
child:
_FocusedDate
(
date:
_dayGridFocus
.
hasFocus
?
_focusedDay
:
null
,
date:
_dayGridFocus
.
hasFocus
?
_focusedDay
:
null
,
child:
Container
(
child:
PageView
.
builder
(
color:
_dayGridFocus
.
hasFocus
?
Theme
.
of
(
context
).
focusColor
:
null
,
key:
_pageViewKey
,
child:
PageView
.
builder
(
controller:
_pageController
,
key:
_pageViewKey
,
itemBuilder:
_buildItems
,
controller:
_pageController
,
itemCount:
utils
.
monthDelta
(
widget
.
firstDate
,
widget
.
lastDate
)
+
1
,
itemBuilder:
_buildItems
,
scrollDirection:
Axis
.
horizontal
,
itemCount:
utils
.
monthDelta
(
widget
.
firstDate
,
widget
.
lastDate
)
+
1
,
onPageChanged:
_handleMonthPageChanged
,
scrollDirection:
Axis
.
horizontal
,
onPageChanged:
_handleMonthPageChanged
,
),
),
),
),
),
),
),
...
...
packages/flutter/lib/src/material/pickers/calendar_date_range_picker.dart
View file @
5fa937ed
...
@@ -13,12 +13,15 @@ import 'package:flutter/widgets.dart';
...
@@ -13,12 +13,15 @@ import 'package:flutter/widgets.dart';
import
'../color_scheme.dart'
;
import
'../color_scheme.dart'
;
import
'../divider.dart'
;
import
'../divider.dart'
;
import
'../ink_well.dart'
;
import
'../material_localizations.dart'
;
import
'../material_localizations.dart'
;
import
'../text_theme.dart'
;
import
'../text_theme.dart'
;
import
'../theme.dart'
;
import
'../theme.dart'
;
import
'date_utils.dart'
as
utils
;
import
'date_utils.dart'
as
utils
;
const
Duration
_monthScrollDuration
=
Duration
(
milliseconds:
200
);
const
double
_monthItemHeaderHeight
=
58.0
;
const
double
_monthItemHeaderHeight
=
58.0
;
const
double
_monthItemFooterHeight
=
12.0
;
const
double
_monthItemFooterHeight
=
12.0
;
const
double
_monthItemRowHeight
=
42.0
;
const
double
_monthItemRowHeight
=
42.0
;
...
@@ -87,6 +90,7 @@ class CalendarDateRangePicker extends StatefulWidget {
...
@@ -87,6 +90,7 @@ class CalendarDateRangePicker extends StatefulWidget {
}
}
class
_CalendarDateRangePickerState
extends
State
<
CalendarDateRangePicker
>
{
class
_CalendarDateRangePickerState
extends
State
<
CalendarDateRangePicker
>
{
final
GlobalKey
_scrollViewKey
=
GlobalKey
();
DateTime
_startDate
;
DateTime
_startDate
;
DateTime
_endDate
;
DateTime
_endDate
;
int
_initialMonthIndex
=
0
;
int
_initialMonthIndex
=
0
;
...
@@ -195,26 +199,34 @@ class _CalendarDateRangePickerState extends State<CalendarDateRangePicker> {
...
@@ -195,26 +199,34 @@ class _CalendarDateRangePickerState extends State<CalendarDateRangePicker> {
_DayHeaders
(),
_DayHeaders
(),
if
(
_showWeekBottomDivider
)
const
Divider
(
height:
0
),
if
(
_showWeekBottomDivider
)
const
Divider
(
height:
0
),
Expanded
(
Expanded
(
// In order to prevent performance issues when displaying the
child:
_CalendarKeyboardNavigator
(
// correct initial month, 2 `SliverList`s are used to split the
firstDate:
widget
.
firstDate
,
// months. The first item in the second SliverList is the initial
lastDate:
widget
.
lastDate
,
// month to be displayed.
initialFocusedDay:
_startDate
??
widget
.
initialStartDate
??
widget
.
currentDate
,
child:
CustomScrollView
(
// In order to prevent performance issues when displaying the
controller:
_controller
,
// correct initial month, 2 `SliverList`s are used to split the
center:
sliverAfterKey
,
// months. The first item in the second SliverList is the initial
slivers:
<
Widget
>[
// month to be displayed.
SliverList
(
child:
CustomScrollView
(
delegate:
SliverChildBuilderDelegate
((
BuildContext
context
,
int
index
)
=>
_buildMonthItem
(
context
,
index
,
true
),
key:
_scrollViewKey
,
childCount:
_initialMonthIndex
,
controller:
_controller
,
center:
sliverAfterKey
,
slivers:
<
Widget
>[
SliverList
(
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
_buildMonthItem
(
context
,
index
,
true
),
childCount:
_initialMonthIndex
,
),
),
),
),
SliverList
(
SliverList
(
key:
sliverAfterKey
,
key:
sliverAfterKey
,
delegate:
SliverChildBuilderDelegate
(
delegate:
SliverChildBuilderDelegate
((
BuildContext
context
,
int
index
)
=>
_buildMonthItem
(
context
,
index
,
false
),
(
BuildContext
context
,
int
index
)
=>
_buildMonthItem
(
context
,
index
,
false
),
childCount:
_numberOfMonths
-
_initialMonthIndex
,
childCount:
_numberOfMonths
-
_initialMonthIndex
,
),
),
),
)
,
]
,
]
,
)
,
),
),
),
),
],
],
...
@@ -222,6 +234,165 @@ class _CalendarDateRangePickerState extends State<CalendarDateRangePicker> {
...
@@ -222,6 +234,165 @@ class _CalendarDateRangePickerState extends State<CalendarDateRangePicker> {
}
}
}
}
class
_CalendarKeyboardNavigator
extends
StatefulWidget
{
const
_CalendarKeyboardNavigator
({
Key
key
,
@required
this
.
child
,
@required
this
.
firstDate
,
@required
this
.
lastDate
,
@required
this
.
initialFocusedDay
,
})
:
super
(
key:
key
);
final
Widget
child
;
final
DateTime
firstDate
;
final
DateTime
lastDate
;
final
DateTime
initialFocusedDay
;
@override
_CalendarKeyboardNavigatorState
createState
()
=>
_CalendarKeyboardNavigatorState
();
}
class
_CalendarKeyboardNavigatorState
extends
State
<
_CalendarKeyboardNavigator
>
{
Map
<
LogicalKeySet
,
Intent
>
_shortcutMap
;
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
FocusNode
_dayGridFocus
;
TraversalDirection
_dayTraversalDirection
;
DateTime
_focusedDay
;
@override
void
initState
()
{
super
.
initState
();
_shortcutMap
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
arrowLeft
):
const
DirectionalFocusIntent
(
TraversalDirection
.
left
),
LogicalKeySet
(
LogicalKeyboardKey
.
arrowRight
):
const
DirectionalFocusIntent
(
TraversalDirection
.
right
),
LogicalKeySet
(
LogicalKeyboardKey
.
arrowDown
):
const
DirectionalFocusIntent
(
TraversalDirection
.
down
),
LogicalKeySet
(
LogicalKeyboardKey
.
arrowUp
):
const
DirectionalFocusIntent
(
TraversalDirection
.
up
),
};
_actionMap
=
<
Type
,
Action
<
Intent
>>{
NextFocusIntent:
CallbackAction
<
NextFocusIntent
>(
onInvoke:
_handleGridNextFocus
),
PreviousFocusIntent:
CallbackAction
<
PreviousFocusIntent
>(
onInvoke:
_handleGridPreviousFocus
),
DirectionalFocusIntent:
CallbackAction
<
DirectionalFocusIntent
>(
onInvoke:
_handleDirectionFocus
),
};
_dayGridFocus
=
FocusNode
(
debugLabel:
'Day Grid'
);
}
@override
void
dispose
()
{
_dayGridFocus
.
dispose
();
super
.
dispose
();
}
void
_handleGridFocusChange
(
bool
focused
)
{
setState
(()
{
if
(
focused
)
{
_focusedDay
??=
widget
.
initialFocusedDay
;
}
});
}
/// Move focus to the next element after the day grid.
void
_handleGridNextFocus
(
NextFocusIntent
intent
)
{
_dayGridFocus
.
requestFocus
();
_dayGridFocus
.
nextFocus
();
}
/// Move focus to the previous element before the day grid.
void
_handleGridPreviousFocus
(
PreviousFocusIntent
intent
)
{
_dayGridFocus
.
requestFocus
();
_dayGridFocus
.
previousFocus
();
}
/// Move the internal focus date in the direction of the given intent.
///
/// This will attempt to move the focused day to the next selectable day in
/// the given direction. If the new date is not in the current month, then
/// the page view will be scrolled to show the new date's month.
///
/// For horizontal directions, it will move forward or backward a day (depending
/// on the current [TextDirection]). For vertical directions it will move up and
/// down a week at a time.
void
_handleDirectionFocus
(
DirectionalFocusIntent
intent
)
{
assert
(
_focusedDay
!=
null
);
setState
(()
{
final
DateTime
nextDate
=
_nextDateInDirection
(
_focusedDay
,
intent
.
direction
);
if
(
nextDate
!=
null
)
{
_focusedDay
=
nextDate
;
_dayTraversalDirection
=
intent
.
direction
;
}
});
}
static
const
Map
<
TraversalDirection
,
int
>
_directionOffset
=
<
TraversalDirection
,
int
>{
TraversalDirection
.
up
:
-
DateTime
.
daysPerWeek
,
TraversalDirection
.
right
:
1
,
TraversalDirection
.
down
:
DateTime
.
daysPerWeek
,
TraversalDirection
.
left
:
-
1
,
};
int
_dayDirectionOffset
(
TraversalDirection
traversalDirection
,
TextDirection
textDirection
)
{
// Swap left and right if the text direction if RTL
if
(
textDirection
==
TextDirection
.
rtl
)
{
if
(
traversalDirection
==
TraversalDirection
.
left
)
traversalDirection
=
TraversalDirection
.
right
;
else
if
(
traversalDirection
==
TraversalDirection
.
right
)
traversalDirection
=
TraversalDirection
.
left
;
}
return
_directionOffset
[
traversalDirection
];
}
DateTime
_nextDateInDirection
(
DateTime
date
,
TraversalDirection
direction
)
{
final
TextDirection
textDirection
=
Directionality
.
of
(
context
);
final
DateTime
nextDate
=
utils
.
addDaysToDate
(
date
,
_dayDirectionOffset
(
direction
,
textDirection
));
if
(!
nextDate
.
isBefore
(
widget
.
firstDate
)
&&
!
nextDate
.
isAfter
(
widget
.
lastDate
))
{
return
nextDate
;
}
return
null
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
FocusableActionDetector
(
shortcuts:
_shortcutMap
,
actions:
_actionMap
,
focusNode:
_dayGridFocus
,
onFocusChange:
_handleGridFocusChange
,
child:
_FocusedDate
(
date:
_dayGridFocus
.
hasFocus
?
_focusedDay
:
null
,
scrollDirection:
_dayGridFocus
.
hasFocus
?
_dayTraversalDirection
:
null
,
child:
widget
.
child
,
),
);
}
}
/// InheritedWidget indicating what the current focused date is for its children.
///
/// This is used by the [_MonthPicker] to let its children [_DayPicker]s know
/// what the currently focused date (if any) should be.
class
_FocusedDate
extends
InheritedWidget
{
const
_FocusedDate
({
Key
key
,
Widget
child
,
this
.
date
,
this
.
scrollDirection
,
})
:
super
(
key:
key
,
child:
child
);
final
DateTime
date
;
final
TraversalDirection
scrollDirection
;
@override
bool
updateShouldNotify
(
_FocusedDate
oldWidget
)
{
return
!
utils
.
isSameDay
(
date
,
oldWidget
.
date
)
||
scrollDirection
!=
oldWidget
.
scrollDirection
;
}
static
_FocusedDate
of
(
BuildContext
context
)
{
return
context
.
dependOnInheritedWidgetOfExactType
<
_FocusedDate
>();
}
}
class
_DayHeaders
extends
StatelessWidget
{
class
_DayHeaders
extends
StatelessWidget
{
/// Builds widgets showing abbreviated days of week. The first widget in the
/// Builds widgets showing abbreviated days of week. The first widget in the
/// returned list corresponds to the first day of week for the current locale.
/// returned list corresponds to the first day of week for the current locale.
...
@@ -401,7 +572,7 @@ class _MonthSliverGridLayout extends SliverGridLayout {
...
@@ -401,7 +572,7 @@ class _MonthSliverGridLayout extends SliverGridLayout {
///
///
/// The days are arranged in a rectangular grid with one column for each day of
/// The days are arranged in a rectangular grid with one column for each day of
/// the week.
/// the week.
class
_MonthItem
extends
State
less
Widget
{
class
_MonthItem
extends
State
ful
Widget
{
/// Creates a month item.
/// Creates a month item.
_MonthItem
({
_MonthItem
({
Key
key
,
Key
key
,
...
@@ -471,9 +642,67 @@ class _MonthItem extends StatelessWidget {
...
@@ -471,9 +642,67 @@ class _MonthItem extends StatelessWidget {
/// the different behaviors.
/// the different behaviors.
final
DragStartBehavior
dragStartBehavior
;
final
DragStartBehavior
dragStartBehavior
;
@override
_MonthItemState
createState
()
=>
_MonthItemState
();
}
class
_MonthItemState
extends
State
<
_MonthItem
>
{
/// List of [FocusNode]s, one for each day of the month.
List
<
FocusNode
>
_dayFocusNodes
;
@override
void
initState
()
{
super
.
initState
();
final
int
daysInMonth
=
utils
.
getDaysInMonth
(
widget
.
displayedMonth
.
year
,
widget
.
displayedMonth
.
month
);
_dayFocusNodes
=
List
<
FocusNode
>.
generate
(
daysInMonth
,
(
int
index
)
=>
FocusNode
(
skipTraversal:
true
,
debugLabel:
'Day
${index + 1}
'
)
);
}
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
// Check to see if the focused date is in this month, if so focus it.
final
DateTime
focusedDate
=
_FocusedDate
.
of
(
context
)?.
date
;
if
(
focusedDate
!=
null
&&
utils
.
isSameMonth
(
widget
.
displayedMonth
,
focusedDate
))
{
_dayFocusNodes
[
focusedDate
.
day
-
1
].
requestFocus
();
}
}
@override
void
dispose
()
{
for
(
final
FocusNode
node
in
_dayFocusNodes
)
{
node
.
dispose
();
}
super
.
dispose
();
}
Color
_highlightColor
(
BuildContext
context
)
{
Color
_highlightColor
(
BuildContext
context
)
{
final
ColorScheme
colors
=
Theme
.
of
(
context
).
colorScheme
;
return
Theme
.
of
(
context
).
colorScheme
.
primary
.
withOpacity
(
0.12
);
return
Color
.
alphaBlend
(
colors
.
primary
.
withOpacity
(
0.12
),
colors
.
background
);
}
void
_dayFocusChanged
(
bool
focused
)
{
if
(
focused
)
{
final
TraversalDirection
focusDirection
=
_FocusedDate
.
of
(
context
)?.
scrollDirection
;
if
(
focusDirection
!=
null
)
{
ScrollPositionAlignmentPolicy
policy
=
ScrollPositionAlignmentPolicy
.
explicit
;
switch
(
focusDirection
)
{
case
TraversalDirection
.
up
:
case
TraversalDirection
.
left
:
policy
=
ScrollPositionAlignmentPolicy
.
keepVisibleAtStart
;
break
;
case
TraversalDirection
.
right
:
case
TraversalDirection
.
down
:
policy
=
ScrollPositionAlignmentPolicy
.
keepVisibleAtEnd
;
break
;
}
Scrollable
.
ensureVisible
(
primaryFocus
.
context
,
duration:
_monthScrollDuration
,
alignmentPolicy:
policy
,
);
}
}
}
}
Widget
_buildDayItem
(
BuildContext
context
,
DateTime
dayToBuild
,
int
firstDayOffset
,
int
daysInMonth
)
{
Widget
_buildDayItem
(
BuildContext
context
,
DateTime
dayToBuild
,
int
firstDayOffset
,
int
daysInMonth
)
{
...
@@ -485,17 +714,17 @@ class _MonthItem extends StatelessWidget {
...
@@ -485,17 +714,17 @@ class _MonthItem extends StatelessWidget {
final
Color
highlightColor
=
_highlightColor
(
context
);
final
Color
highlightColor
=
_highlightColor
(
context
);
final
int
day
=
dayToBuild
.
day
;
final
int
day
=
dayToBuild
.
day
;
final
bool
isDisabled
=
dayToBuild
.
isAfter
(
lastDate
)
||
dayToBuild
.
isBefore
(
firstDate
);
final
bool
isDisabled
=
dayToBuild
.
isAfter
(
widget
.
lastDate
)
||
dayToBuild
.
isBefore
(
widget
.
firstDate
);
BoxDecoration
decoration
;
BoxDecoration
decoration
;
TextStyle
itemStyle
=
textTheme
.
bodyText2
;
TextStyle
itemStyle
=
textTheme
.
bodyText2
;
final
bool
isRangeSelected
=
selectedDateStart
!=
null
&&
selectedDateEnd
!=
null
;
final
bool
isRangeSelected
=
widget
.
selectedDateStart
!=
null
&&
widget
.
selectedDateEnd
!=
null
;
final
bool
isSelectedDayStart
=
selectedDateStart
!=
null
&&
dayToBuild
.
isAtSameMomentAs
(
selectedDateStart
);
final
bool
isSelectedDayStart
=
widget
.
selectedDateStart
!=
null
&&
dayToBuild
.
isAtSameMomentAs
(
widget
.
selectedDateStart
);
final
bool
isSelectedDayEnd
=
selectedDateEnd
!=
null
&&
dayToBuild
.
isAtSameMomentAs
(
selectedDateEnd
);
final
bool
isSelectedDayEnd
=
widget
.
selectedDateEnd
!=
null
&&
dayToBuild
.
isAtSameMomentAs
(
widget
.
selectedDateEnd
);
final
bool
isInRange
=
isRangeSelected
&&
final
bool
isInRange
=
isRangeSelected
&&
dayToBuild
.
isAfter
(
selectedDateStart
)
&&
dayToBuild
.
isAfter
(
widget
.
selectedDateStart
)
&&
dayToBuild
.
isBefore
(
selectedDateEnd
);
dayToBuild
.
isBefore
(
widget
.
selectedDateEnd
);
_HighlightPainter
highlightPainter
;
_HighlightPainter
highlightPainter
;
...
@@ -508,7 +737,7 @@ class _MonthItem extends StatelessWidget {
...
@@ -508,7 +737,7 @@ class _MonthItem extends StatelessWidget {
shape:
BoxShape
.
circle
,
shape:
BoxShape
.
circle
,
);
);
if
(
isRangeSelected
&&
selectedDateStart
!=
selectedDateEnd
)
{
if
(
isRangeSelected
&&
widget
.
selectedDateStart
!=
widget
.
selectedDateEnd
)
{
final
_HighlightPainterStyle
style
=
isSelectedDayStart
final
_HighlightPainterStyle
style
=
isSelectedDayStart
?
_HighlightPainterStyle
.
highlightTrailing
?
_HighlightPainterStyle
.
highlightTrailing
:
_HighlightPainterStyle
.
highlightLeading
;
:
_HighlightPainterStyle
.
highlightLeading
;
...
@@ -527,7 +756,7 @@ class _MonthItem extends StatelessWidget {
...
@@ -527,7 +756,7 @@ class _MonthItem extends StatelessWidget {
);
);
}
else
if
(
isDisabled
)
{
}
else
if
(
isDisabled
)
{
itemStyle
=
textTheme
.
bodyText2
?.
apply
(
color:
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
itemStyle
=
textTheme
.
bodyText2
?.
apply
(
color:
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
}
else
if
(
utils
.
isSameDay
(
currentDate
,
dayToBuild
))
{
}
else
if
(
utils
.
isSameDay
(
widget
.
currentDate
,
dayToBuild
))
{
// The current day gets a different text color and a circle stroke
// The current day gets a different text color and a circle stroke
// border.
// border.
itemStyle
=
textTheme
.
bodyText2
?.
apply
(
color:
colorScheme
.
primary
);
itemStyle
=
textTheme
.
bodyText2
?.
apply
(
color:
colorScheme
.
primary
);
...
@@ -543,12 +772,11 @@ class _MonthItem extends StatelessWidget {
...
@@ -543,12 +772,11 @@ class _MonthItem extends StatelessWidget {
// day of month before the rest of the date, as they are looking
// day of month before the rest of the date, as they are looking
// for the day of month. To do that we prepend day of month to the
// for the day of month. To do that we prepend day of month to the
// formatted full date.
// formatted full date.
final
String
fullDate
=
localizations
.
formatFullDate
(
dayToBuild
);
String
semanticLabel
=
'
${localizations.formatDecimal(day)}
,
${localizations.formatFullDate(dayToBuild)}
'
;
String
semanticLabel
=
fullDate
;
if
(
isSelectedDayStart
)
{
if
(
isSelectedDayStart
)
{
semanticLabel
=
localizations
.
dateRangeStartDateSemanticLabel
(
fullDate
);
semanticLabel
=
localizations
.
dateRangeStartDateSemanticLabel
(
semanticLabel
);
}
else
if
(
isSelectedDayEnd
)
{
}
else
if
(
isSelectedDayEnd
)
{
semanticLabel
=
localizations
.
dateRangeEndDateSemanticLabel
(
fullDate
);
semanticLabel
=
localizations
.
dateRangeEndDateSemanticLabel
(
semanticLabel
);
}
}
Widget
dayWidget
=
Container
(
Widget
dayWidget
=
Container
(
...
@@ -572,11 +800,13 @@ class _MonthItem extends StatelessWidget {
...
@@ -572,11 +800,13 @@ class _MonthItem extends StatelessWidget {
}
}
if
(!
isDisabled
)
{
if
(!
isDisabled
)
{
dayWidget
=
GestureDetector
(
dayWidget
=
InkResponse
(
behavior:
HitTestBehavior
.
opaque
,
focusNode:
_dayFocusNodes
[
day
-
1
],
onTap:
()
{
onChanged
(
dayToBuild
);
},
onTap:
()
=>
widget
.
onChanged
(
dayToBuild
),
radius:
_monthItemRowHeight
/
2
+
4
,
splashColor:
colorScheme
.
primary
.
withOpacity
(
0.38
),
onFocusChange:
_dayFocusChanged
,
child:
dayWidget
,
child:
dayWidget
,
dragStartBehavior:
dragStartBehavior
,
);
);
}
}
...
@@ -592,8 +822,8 @@ class _MonthItem extends StatelessWidget {
...
@@ -592,8 +822,8 @@ class _MonthItem extends StatelessWidget {
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
TextTheme
textTheme
=
themeData
.
textTheme
;
final
TextTheme
textTheme
=
themeData
.
textTheme
;
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
int
year
=
displayedMonth
.
year
;
final
int
year
=
widget
.
displayedMonth
.
year
;
final
int
month
=
displayedMonth
.
month
;
final
int
month
=
widget
.
displayedMonth
.
month
;
final
int
daysInMonth
=
utils
.
getDaysInMonth
(
year
,
month
);
final
int
daysInMonth
=
utils
.
getDaysInMonth
(
year
,
month
);
final
int
dayOffset
=
utils
.
firstDayOffset
(
year
,
month
,
localizations
);
final
int
dayOffset
=
utils
.
firstDayOffset
(
year
,
month
,
localizations
);
final
int
weeks
=
((
daysInMonth
+
dayOffset
)
/
DateTime
.
daysPerWeek
).
ceil
();
final
int
weeks
=
((
daysInMonth
+
dayOffset
)
/
DateTime
.
daysPerWeek
).
ceil
();
...
@@ -637,10 +867,10 @@ class _MonthItem extends StatelessWidget {
...
@@ -637,10 +867,10 @@ class _MonthItem extends StatelessWidget {
// on/before the end date.
// on/before the end date.
final
bool
isLeadingInRange
=
final
bool
isLeadingInRange
=
!(
dayOffset
>
0
&&
i
==
0
)
&&
!(
dayOffset
>
0
&&
i
==
0
)
&&
selectedDateStart
!=
null
&&
widget
.
selectedDateStart
!=
null
&&
selectedDateEnd
!=
null
&&
widget
.
selectedDateEnd
!=
null
&&
dateAfterLeadingPadding
.
isAfter
(
selectedDateStart
)
&&
dateAfterLeadingPadding
.
isAfter
(
widget
.
selectedDateStart
)
&&
!
dateAfterLeadingPadding
.
isAfter
(
selectedDateEnd
);
!
dateAfterLeadingPadding
.
isAfter
(
widget
.
selectedDateEnd
);
weekList
.
insert
(
0
,
_buildEdgeContainer
(
context
,
isLeadingInRange
));
weekList
.
insert
(
0
,
_buildEdgeContainer
(
context
,
isLeadingInRange
));
// Only add a trailing edge container if it is for a full week and not a
// Only add a trailing edge container if it is for a full week and not a
...
@@ -651,10 +881,10 @@ class _MonthItem extends StatelessWidget {
...
@@ -651,10 +881,10 @@ class _MonthItem extends StatelessWidget {
// Only color the edge container if it is on/after the start date and
// Only color the edge container if it is on/after the start date and
// before the end date.
// before the end date.
final
bool
isTrailingInRange
=
final
bool
isTrailingInRange
=
selectedDateStart
!=
null
&&
widget
.
selectedDateStart
!=
null
&&
selectedDateEnd
!=
null
&&
widget
.
selectedDateEnd
!=
null
&&
!
dateBeforeTrailingPadding
.
isBefore
(
selectedDateStart
)
&&
!
dateBeforeTrailingPadding
.
isBefore
(
widget
.
selectedDateStart
)
&&
dateBeforeTrailingPadding
.
isBefore
(
selectedDateEnd
);
dateBeforeTrailingPadding
.
isBefore
(
widget
.
selectedDateEnd
);
weekList
.
add
(
_buildEdgeContainer
(
context
,
isTrailingInRange
));
weekList
.
add
(
_buildEdgeContainer
(
context
,
isTrailingInRange
));
}
}
...
@@ -673,7 +903,7 @@ class _MonthItem extends StatelessWidget {
...
@@ -673,7 +903,7 @@ class _MonthItem extends StatelessWidget {
alignment:
AlignmentDirectional
.
centerStart
,
alignment:
AlignmentDirectional
.
centerStart
,
child:
ExcludeSemantics
(
child:
ExcludeSemantics
(
child:
Text
(
child:
Text
(
localizations
.
formatMonthYear
(
displayedMonth
),
localizations
.
formatMonthYear
(
widget
.
displayedMonth
),
style:
textTheme
.
bodyText2
.
apply
(
color:
themeData
.
colorScheme
.
onSurface
),
style:
textTheme
.
bodyText2
.
apply
(
color:
themeData
.
colorScheme
.
onSurface
),
),
),
),
),
...
@@ -740,11 +970,8 @@ class _HighlightPainter extends CustomPainter {
...
@@ -740,11 +970,8 @@ class _HighlightPainter extends CustomPainter {
..
color
=
color
..
color
=
color
..
style
=
PaintingStyle
.
fill
;
..
style
=
PaintingStyle
.
fill
;
// This ensures no gaps in the highlight track due to floating point
final
Rect
rectLeft
=
Rect
.
fromLTWH
(
0
,
0
,
size
.
width
/
2
,
size
.
height
);
// division of the available screen width.
final
Rect
rectRight
=
Rect
.
fromLTWH
(
size
.
width
/
2
,
0
,
size
.
width
/
2
,
size
.
height
);
final
double
width
=
size
.
width
+
1
;
final
Rect
rectLeft
=
Rect
.
fromLTWH
(
0
,
0
,
width
/
2
,
size
.
height
);
final
Rect
rectRight
=
Rect
.
fromLTWH
(
size
.
width
/
2
,
0
,
width
/
2
,
size
.
height
);
switch
(
style
)
{
switch
(
style
)
{
case
_HighlightPainterStyle
.
highlightTrailing
:
case
_HighlightPainterStyle
.
highlightTrailing
:
...
@@ -761,7 +988,7 @@ class _HighlightPainter extends CustomPainter {
...
@@ -761,7 +988,7 @@ class _HighlightPainter extends CustomPainter {
break
;
break
;
case
_HighlightPainterStyle
.
highlightAll
:
case
_HighlightPainterStyle
.
highlightAll
:
canvas
.
drawRect
(
canvas
.
drawRect
(
Rect
.
fromLTWH
(
0
,
0
,
width
,
size
.
height
),
Rect
.
fromLTWH
(
0
,
0
,
size
.
width
,
size
.
height
),
paint
,
paint
,
);
);
break
;
break
;
...
...
packages/flutter/lib/src/material/pickers/date_picker_common.dart
View file @
5fa937ed
...
@@ -88,4 +88,7 @@ class DateTimeRange {
...
@@ -88,4 +88,7 @@ class DateTimeRange {
@override
@override
int
get
hashCode
=>
hashValues
(
start
,
end
);
int
get
hashCode
=>
hashValues
(
start
,
end
);
@override
String
toString
()
=>
'
$start
-
$end
'
;
}
}
packages/flutter/test/material/date_range_picker_test.dart
View file @
5fa937ed
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
// @dart = 2.8
// @dart = 2.8
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'feedback_tester.dart'
;
import
'feedback_tester.dart'
;
...
@@ -49,7 +50,11 @@ void main() {
...
@@ -49,7 +50,11 @@ void main() {
saveText
=
null
;
saveText
=
null
;
});
});
Future
<
void
>
preparePicker
(
WidgetTester
tester
,
Future
<
void
>
callback
(
Future
<
DateTimeRange
>
date
))
async
{
Future
<
void
>
preparePicker
(
WidgetTester
tester
,
Future
<
void
>
callback
(
Future
<
DateTimeRange
>
date
),
{
TextDirection
textDirection
=
TextDirection
.
ltr
}
)
async
{
BuildContext
buttonContext
;
BuildContext
buttonContext
;
await
tester
.
pumpWidget
(
MaterialApp
(
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
home:
Material
(
...
@@ -86,6 +91,12 @@ void main() {
...
@@ -86,6 +91,12 @@ void main() {
fieldEndLabelText:
fieldEndLabelText
,
fieldEndLabelText:
fieldEndLabelText
,
helpText:
helpText
,
helpText:
helpText
,
saveText:
saveText
,
saveText:
saveText
,
builder:
(
BuildContext
context
,
Widget
child
)
{
return
Directionality
(
textDirection:
textDirection
,
child:
child
,
);
},
);
);
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
1
));
...
@@ -237,6 +248,186 @@ void main() {
...
@@ -237,6 +248,186 @@ void main() {
});
});
});
});
group
(
'Keyboard navigation'
,
()
{
testWidgets
(
'Can toggle to calendar entry mode'
,
(
WidgetTester
tester
)
async
{
await
preparePicker
(
tester
,
(
Future
<
DateTimeRange
>
range
)
async
{
expect
(
find
.
byType
(
TextField
),
findsNothing
);
// Navigate to the entry toggle button and activate it
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Should be in the input mode
expect
(
find
.
byType
(
TextField
),
findsNWidgets
(
2
));
});
});
testWidgets
(
'Can navigate date grid with arrow keys'
,
(
WidgetTester
tester
)
async
{
await
preparePicker
(
tester
,
(
Future
<
DateTimeRange
>
range
)
async
{
// Navigate to the grid
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
pumpAndSettle
();
// Navigate from Jan 15 to Jan 18 with arrow keys
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pumpAndSettle
();
// Activate it to select the beginning of the range
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Navigate to Jan 29
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pumpAndSettle
();
// Activate it to select the end of the range
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Navigate out of the grid and to the OK button
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
// Activate OK
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Should have selected Jan 18 - Jan 29
expect
(
await
range
,
DateTimeRange
(
start:
DateTime
(
2016
,
DateTime
.
january
,
18
),
end:
DateTime
(
2016
,
DateTime
.
january
,
29
),
));
});
});
testWidgets
(
'Navigating with arrow keys scrolls as needed'
,
(
WidgetTester
tester
)
async
{
await
preparePicker
(
tester
,
(
Future
<
DateTimeRange
>
range
)
async
{
// Jan and Feb headers should be showing, but no Mar
expect
(
find
.
text
(
'January 2016'
),
findsOneWidget
);
expect
(
find
.
text
(
'February 2016'
),
findsOneWidget
);
expect
(
find
.
text
(
'Mar 2016'
),
findsNothing
);
// Navigate to the grid
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
// Navigate from Jan 15 to Jan 18 with arrow keys
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pumpAndSettle
();
// Activate it to select the beginning of the range
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Navigate to Mar 17
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pumpAndSettle
();
// Jan should have scrolled off, Mar should be visible
expect
(
find
.
text
(
'January 2016'
),
findsNothing
);
expect
(
find
.
text
(
'February 2016'
),
findsOneWidget
);
expect
(
find
.
text
(
'March 2016'
),
findsOneWidget
);
// Activate it to select the end of the range
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Navigate out of the grid and to the OK button
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
// Activate OK
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Should have selected Jan 18 - Mar 17
expect
(
await
range
,
DateTimeRange
(
start:
DateTime
(
2016
,
DateTime
.
january
,
18
),
end:
DateTime
(
2016
,
DateTime
.
march
,
17
),
));
});
});
testWidgets
(
'RTL text direction reverses the horizontal arrow key navigation'
,
(
WidgetTester
tester
)
async
{
await
preparePicker
(
tester
,
(
Future
<
DateTimeRange
>
range
)
async
{
// Navigate to the grid
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
// Navigate from Jan 15 to 19 with arrow keys
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pumpAndSettle
();
// Activate it
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Navigate to Jan 21
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pumpAndSettle
();
// Activate it
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Navigate out of the grid and to the OK button
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
// Activate OK
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
space
);
await
tester
.
pumpAndSettle
();
// Should have selected Jan 19 - Mar 21
expect
(
await
range
,
DateTimeRange
(
start:
DateTime
(
2016
,
DateTime
.
january
,
19
),
end:
DateTime
(
2016
,
DateTime
.
january
,
21
),
));
},
textDirection:
TextDirection
.
rtl
);
});
});
group
(
'Input mode'
,
()
{
group
(
'Input mode'
,
()
{
setUp
(()
{
setUp
(()
{
firstDate
=
DateTime
(
2015
,
DateTime
.
january
,
1
);
firstDate
=
DateTime
(
2015
,
DateTime
.
january
,
1
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment