siyao
4 年前
当前提交
9aa680bd
共有 20 个文件被更改,包括 844 次插入 和 941 次删除
-
2com.unity.uiwidgets/Runtime/material/banner.cs.meta
-
2com.unity.uiwidgets/Runtime/material/banner_theme.cs.meta
-
11com.unity.uiwidgets/Runtime/material/button_bar_theme.cs.meta
-
11com.unity.uiwidgets/Runtime/material/button_sheet_theme.cs.meta
-
11com.unity.uiwidgets/Runtime/material/checkbox_list_tile.cs.meta
-
11com.unity.uiwidgets/Runtime/material/constants.cs.meta
-
11com.unity.uiwidgets/Runtime/material/curves.cs.meta
-
702com.unity.uiwidgets/Runtime/material/data_table.cs
-
3com.unity.uiwidgets/Runtime/material/data_table.cs.meta
-
13com.unity.uiwidgets/Runtime/material/data_table_source.cs
-
3com.unity.uiwidgets/Runtime/material/data_table_source.cs.meta
-
11com.unity.uiwidgets/Runtime/material/divider_theme.cs.meta
-
11com.unity.uiwidgets/Runtime/material/material_state.cs.meta
-
11com.unity.uiwidgets/Runtime/material/popup_menu_theme.cs.meta
-
11com.unity.uiwidgets/Runtime/material/toggle_buttons.cs.meta
-
11com.unity.uiwidgets/Runtime/material/toggle_buttons_theme.cs.meta
-
11com.unity.uiwidgets/Runtime/material/tooltip_theme.cs.meta
-
939com.unity.uiwidgets/Runtime/material/date_picker.cs
-
0/com.unity.uiwidgets/Runtime/material/banner.cs.meta
-
0/com.unity.uiwidgets/Runtime/material/banner_theme.cs.meta
|
|||
fileFormatVersion: 2 |
|||
guid: 62d1a3bfa55142cdbea1f6ed4f0cfcc7 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 45f5292abc6f4a73bdcad12fd0436806 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 087d40381b8c489ca5f975855c9b0a7b |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 458362f6f20be1348bf63c0a5a912024 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 8c93567e6a99494680dad3e00fd605b7 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.gestures; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
using Color = Unity.UIWidgets.ui.Color; |
|||
using Rect = Unity.UIWidgets.ui.Rect; |
|||
using TextStyle = Unity.UIWidgets.painting.TextStyle; |
|||
|
|||
namespace Unity.UIWidgets.material { |
|||
public partial class material_ { |
|||
public delegate void DataColumnSortCallback(int columnIndex, bool ascending); |
|||
} |
|||
|
|||
public class DataColumn { |
|||
public DataColumn( |
|||
Widget label, |
|||
string tooltip = null, |
|||
bool numeric = false, |
|||
material_.DataColumnSortCallback onSort = null |
|||
) { |
|||
D.assert(label != null); |
|||
this.label = label; |
|||
this.tooltip = tooltip; |
|||
this.numeric = numeric; |
|||
this.onSort = onSort; |
|||
} |
|||
|
|||
public readonly Widget label; |
|||
|
|||
public readonly string tooltip; |
|||
|
|||
public readonly bool numeric; |
|||
|
|||
public readonly material_.DataColumnSortCallback onSort; |
|||
|
|||
internal bool _debugInteractive { |
|||
get { return onSort != null; } |
|||
} |
|||
} |
|||
|
|||
public class DataRow { |
|||
public DataRow( |
|||
LocalKey key = null, |
|||
bool selected = false, |
|||
ValueChanged<bool> onSelectChanged = null, |
|||
List<DataCell> cells = null |
|||
) { |
|||
D.assert(cells != null); |
|||
this.key = key; |
|||
this.selected = selected; |
|||
this.onSelectChanged = onSelectChanged; |
|||
this.cells = cells; |
|||
} |
|||
|
|||
public static DataRow byIndex( |
|||
int index = 0, |
|||
bool selected = false, |
|||
ValueChanged<bool> onSelectChanged = null, |
|||
List<DataCell> cells = null |
|||
) { |
|||
D.assert(cells != null); |
|||
return new DataRow( |
|||
new ValueKey<int>(index), |
|||
selected, |
|||
onSelectChanged, |
|||
cells |
|||
); |
|||
} |
|||
|
|||
|
|||
public readonly LocalKey key; |
|||
|
|||
public readonly ValueChanged<bool> onSelectChanged; |
|||
|
|||
public readonly bool selected; |
|||
|
|||
public readonly List<DataCell> cells; |
|||
|
|||
internal bool _debugInteractive { |
|||
get { return onSelectChanged != null || cells.Any((DataCell cell) => cell._debugInteractive); } |
|||
} |
|||
} |
|||
|
|||
|
|||
public class DataCell { |
|||
public DataCell( |
|||
Widget child, |
|||
bool placeholder = false, |
|||
bool showEditIcon = false, |
|||
VoidCallback onTap = null |
|||
) { |
|||
D.assert(child != null); |
|||
this.child = child; |
|||
this.placeholder = placeholder; |
|||
this.showEditIcon = showEditIcon; |
|||
this.onTap = onTap; |
|||
} |
|||
|
|||
static readonly DataCell empty = new DataCell(new Container(width: 0.0f, height: 0.0f)); |
|||
|
|||
public readonly Widget child; |
|||
|
|||
public readonly bool placeholder; |
|||
|
|||
public readonly bool showEditIcon; |
|||
|
|||
public readonly VoidCallback onTap; |
|||
|
|||
internal bool _debugInteractive { |
|||
get { return onTap != null; } |
|||
} |
|||
} |
|||
|
|||
public class DataTable : StatelessWidget { |
|||
public DataTable( |
|||
Key key = null, |
|||
List<DataColumn> columns = null, |
|||
int? sortColumnIndex = 0, |
|||
bool sortAscending = true, |
|||
ValueSetter<bool> onSelectAll = null, |
|||
float dataRowHeight = material_.kMinInteractiveDimension, |
|||
float headingRowHeight = 56.0f, |
|||
float horizontalMargin = 24.0f, |
|||
float columnSpacing = 56.0f, |
|||
bool showCheckboxColumn = true, |
|||
float dividerThickness = 1.0f, |
|||
List<DataRow> rows = null |
|||
) : base(key: key) { |
|||
D.assert(columns != null); |
|||
D.assert(columns.isNotEmpty); |
|||
D.assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.Count)); |
|||
D.assert(rows != null); |
|||
D.assert(!rows.Any((DataRow row) => row.cells.Count != columns.Count)); |
|||
D.assert(dividerThickness >= 0); |
|||
this.columns = columns; |
|||
this.sortColumnIndex = sortColumnIndex; |
|||
this.sortAscending = sortAscending; |
|||
this.onSelectAll = onSelectAll; |
|||
this.dataRowHeight = dataRowHeight; |
|||
this.headingRowHeight = headingRowHeight; |
|||
this.horizontalMargin = horizontalMargin; |
|||
this.columnSpacing = columnSpacing; |
|||
this.showCheckboxColumn = showCheckboxColumn; |
|||
this.dividerThickness = dividerThickness; |
|||
this.rows = rows; |
|||
_onlyTextColumn = _initOnlyTextColumn(columns); |
|||
} |
|||
|
|||
public readonly List<DataColumn> columns; |
|||
public readonly int? sortColumnIndex; |
|||
public readonly bool sortAscending; |
|||
public readonly ValueSetter<bool> onSelectAll; |
|||
public readonly float dataRowHeight; |
|||
public readonly float headingRowHeight; |
|||
public readonly float horizontalMargin; |
|||
public readonly float columnSpacing; |
|||
public readonly bool showCheckboxColumn; |
|||
public readonly List<DataRow> rows; |
|||
|
|||
public readonly int? _onlyTextColumn; |
|||
|
|||
static int? _initOnlyTextColumn(List<DataColumn> columns) { |
|||
int? result = null; |
|||
for (int index = 0; index < columns.Count; index += 1) { |
|||
DataColumn column = columns[index]; |
|||
if (!column.numeric) { |
|||
if (result != null) { |
|||
return null; |
|||
} |
|||
|
|||
result = index; |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
bool _debugInteractive { |
|||
get { |
|||
return columns.Any((DataColumn column) => column._debugInteractive) |
|||
|| rows.Any((DataRow row) => row._debugInteractive); |
|||
} |
|||
} |
|||
|
|||
static readonly LocalKey _headingRowKey = new UniqueKey(); |
|||
|
|||
void _handleSelectAll(bool isChecked) { |
|||
if (onSelectAll != null) { |
|||
onSelectAll(isChecked); |
|||
} |
|||
else { |
|||
foreach (DataRow row in rows) { |
|||
if ((row.onSelectChanged != null) && (row.selected != isChecked)) |
|||
row.onSelectChanged(isChecked); |
|||
} |
|||
} |
|||
} |
|||
|
|||
static readonly float _sortArrowPadding = 2.0f; |
|||
static readonly float _headingFontSize = 12.0f; |
|||
static readonly TimeSpan _sortArrowAnimationDuration = new TimeSpan(0, 0, 0, 0, 150); |
|||
static readonly Color _grey100Opacity = new Color(0x0A000000); |
|||
static readonly Color _grey300Opacity = new Color(0x1E000000); |
|||
public readonly float dividerThickness; |
|||
|
|||
Widget _buildCheckbox( |
|||
Color color = null, |
|||
bool isChecked = false, |
|||
VoidCallback onRowTap = null, |
|||
ValueChanged<bool?> onCheckboxChanged = null |
|||
) { |
|||
Widget contents = new Padding( |
|||
//TODO: update EdgeInsets
|
|||
padding: (EdgeInsets) (EdgeInsetsGeometry) EdgeInsetsDirectional.only(start: horizontalMargin, |
|||
end: horizontalMargin / 2.0f), |
|||
child: new Center( |
|||
child: new Checkbox( |
|||
activeColor: color, |
|||
value: isChecked, |
|||
onChanged: onCheckboxChanged |
|||
) |
|||
) |
|||
); |
|||
if (onRowTap != null) { |
|||
contents = new TableRowInkWell( |
|||
onTap: () => onRowTap(), |
|||
child: contents |
|||
); |
|||
} |
|||
|
|||
return new TableCell( |
|||
verticalAlignment: TableCellVerticalAlignment.fill, |
|||
child: contents |
|||
); |
|||
} |
|||
|
|||
Widget _buildHeadingCell( |
|||
BuildContext context = null, |
|||
EdgeInsetsGeometry padding = null, |
|||
Widget label = null, |
|||
string tooltip = null, |
|||
bool? numeric = null, |
|||
VoidCallback onSort = null, |
|||
bool? sorted = null, |
|||
bool? ascending = null |
|||
) { |
|||
List<Widget> arrowWithPadding() { |
|||
return onSort == null |
|||
? new List<Widget>() |
|||
: new List< |
|||
Widget>() { |
|||
new _SortArrow( |
|||
visible: sorted, |
|||
down: sorted ?? false ? ascending : null, |
|||
duration: _sortArrowAnimationDuration |
|||
), |
|||
|
|||
new SizedBox(width: _sortArrowPadding) |
|||
}; |
|||
} |
|||
|
|||
var rowChild = new List<Widget>(); |
|||
rowChild.Add(label); |
|||
rowChild.AddRange(arrowWithPadding()); |
|||
label = new Row( |
|||
textDirection: numeric ?? false ? TextDirection.rtl : (TextDirection?) null, |
|||
children: rowChild |
|||
); |
|||
label = new Container( |
|||
// TODO: udpate to EdgetInsets
|
|||
padding: (EdgeInsets) padding, |
|||
height: headingRowHeight, |
|||
alignment: numeric ?? false |
|||
? Alignment.centerRight |
|||
: (AlignmentGeometry) AlignmentDirectional.centerStart, |
|||
child: new AnimatedDefaultTextStyle( |
|||
style: new TextStyle( |
|||
fontWeight: FontWeight.w500, |
|||
fontSize: _headingFontSize, |
|||
height: Mathf.Min(1.0f, headingRowHeight / _headingFontSize), |
|||
color: (Theme.of(context).brightness == Brightness.light) |
|||
? ((onSort != null && (sorted ?? false)) ? Colors.black87 : Colors.black54) |
|||
: ((onSort != null && (sorted ?? false)) ? Colors.white : Colors.white70) |
|||
), |
|||
softWrap: false, |
|||
duration: _sortArrowAnimationDuration, |
|||
child: label |
|||
) |
|||
); |
|||
if (tooltip != null) { |
|||
label = new Tooltip( |
|||
message: tooltip, |
|||
child: label |
|||
); |
|||
} |
|||
|
|||
// TODO(dkwingsmt): Only wrap Inkwell if onSort != null. Blocked by
|
|||
// https://github.com/flutter/flutter/issues/51152
|
|||
label = new InkWell( |
|||
onTap: () => onSort(), |
|||
child: label |
|||
); |
|||
return label; |
|||
} |
|||
|
|||
Widget _buildDataCell( |
|||
BuildContext context, |
|||
EdgeInsetsGeometry padding, |
|||
Widget label, |
|||
bool numeric, |
|||
bool placeholder, |
|||
bool showEditIcon, |
|||
VoidCallback onTap, |
|||
VoidCallback onSelectChanged |
|||
) { |
|||
bool isLightTheme = Theme.of(context).brightness == Brightness.light; |
|||
if (showEditIcon) { |
|||
Widget icon = new Icon(Icons.edit, size: 18.0f); |
|||
label = new Expanded(child: label); |
|||
label = new Row( |
|||
textDirection: numeric ? TextDirection.rtl : (TextDirection?) null, |
|||
children: new List<Widget> {label, icon} |
|||
); |
|||
} |
|||
|
|||
label = new Container( |
|||
padding: (EdgeInsets) padding, |
|||
height: dataRowHeight, |
|||
alignment: numeric ? Alignment.centerRight : (AlignmentGeometry) AlignmentDirectional.centerStart, |
|||
child: new DefaultTextStyle( |
|||
style: new TextStyle( |
|||
// TODO(ianh): font family should be Roboto; see https://github.com/flutter/flutter/issues/3116
|
|||
fontSize: 13.0f, |
|||
color: isLightTheme |
|||
? (placeholder ? Colors.black38 : Colors.black87) |
|||
: (placeholder ? Colors.white38 : Colors.white70) |
|||
), |
|||
child: IconTheme.merge( |
|||
data: new IconThemeData( |
|||
color: isLightTheme ? Colors.black54 : Colors.white70 |
|||
), |
|||
child: new DropdownButtonHideUnderline(child: label) |
|||
) |
|||
) |
|||
); |
|||
if (onTap != null) { |
|||
label = new InkWell( |
|||
onTap: () => onTap(), |
|||
child: label |
|||
); |
|||
} |
|||
else if (onSelectChanged != null) { |
|||
label = new TableRowInkWell( |
|||
onTap: () => onSelectChanged(), |
|||
child: label |
|||
); |
|||
} |
|||
|
|||
return label; |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
D.assert(!_debugInteractive || material_.debugCheckHasMaterial(context)); |
|||
|
|||
ThemeData theme = Theme.of(context); |
|||
BoxDecoration _kSelectedDecoration = new BoxDecoration( |
|||
border: new Border(bottom: Divider.createBorderSide(context, width: dividerThickness)), |
|||
// The backgroundColor has to be transparent so you can see the ink on the material
|
|||
color: (Theme.of(context).brightness == Brightness.light) ? _grey100Opacity : _grey300Opacity |
|||
); |
|||
BoxDecoration _kUnselectedDecoration = new BoxDecoration( |
|||
border: new Border(bottom: Divider.createBorderSide(context, width: dividerThickness)) |
|||
); |
|||
|
|||
bool displayCheckboxColumn = |
|||
showCheckboxColumn && rows.Any((DataRow row) => row.onSelectChanged != null); |
|||
bool allChecked = displayCheckboxColumn && |
|||
!rows.Any((DataRow row) => row.onSelectChanged != null && !row.selected); |
|||
|
|||
List<TableColumnWidth> tableColumns = |
|||
new List<TableColumnWidth>(columns.Count + (displayCheckboxColumn ? 1 : 0)); |
|||
List<TableRow> tableRows = Enumerable.Range(1, 10).Select((index) => { |
|||
return new TableRow( |
|||
key: index == 0 ? _headingRowKey : rows[index - 1].key, |
|||
decoration: index > 0 && rows[index - 1].selected |
|||
? _kSelectedDecoration |
|||
: _kUnselectedDecoration, |
|||
children: new List<Widget>(tableColumns.Count) |
|||
); |
|||
}).ToList(); |
|||
// List<TableRow> tableRows = List<TableRow>.gegenerate(
|
|||
// rows.length + 1, // the +1 is for the header row
|
|||
// (int index) {
|
|||
// return TableRow(
|
|||
// key: index == 0 ? _headingRowKey : rows[index - 1].key,
|
|||
// decoration: index > 0 && rows[index - 1].selected
|
|||
// ? _kSelectedDecoration
|
|||
// : _kUnselectedDecoration,
|
|||
// children: List<Widget>(tableColumns.length),
|
|||
// );
|
|||
// },
|
|||
// );
|
|||
|
|||
int rowIndex; |
|||
|
|||
int displayColumnIndex = 0; |
|||
if (displayCheckboxColumn) { |
|||
tableColumns[0] = new FixedColumnWidth(horizontalMargin + Checkbox.width + horizontalMargin / 2.0f); |
|||
tableRows[0].children[0] = _buildCheckbox( |
|||
color: theme.accentColor, |
|||
isChecked: allChecked, |
|||
onCheckboxChanged: _check => _handleSelectAll(_check ?? false) |
|||
); |
|||
rowIndex = 1; |
|||
foreach (DataRow row in rows) { |
|||
tableRows[rowIndex].children[0] = _buildCheckbox( |
|||
color: theme.accentColor, |
|||
isChecked: row.selected, |
|||
onRowTap: () => { |
|||
if (row.onSelectChanged != null) { |
|||
row.onSelectChanged(!row.selected); |
|||
} |
|||
}, |
|||
onCheckboxChanged: _select => row.onSelectChanged(_select ?? false) |
|||
); |
|||
rowIndex += 1; |
|||
} |
|||
|
|||
displayColumnIndex += 1; |
|||
} |
|||
|
|||
for (int dataColumnIndex = 0; dataColumnIndex < columns.Count; dataColumnIndex += 1) { |
|||
DataColumn column = columns[dataColumnIndex]; |
|||
|
|||
float paddingStart; |
|||
if (dataColumnIndex == 0 && displayCheckboxColumn) { |
|||
paddingStart = horizontalMargin / 2.0f; |
|||
} |
|||
else if (dataColumnIndex == 0 && !displayCheckboxColumn) { |
|||
paddingStart = horizontalMargin; |
|||
} |
|||
else { |
|||
paddingStart = columnSpacing / 2.0f; |
|||
} |
|||
|
|||
float paddingEnd; |
|||
if (dataColumnIndex == columns.Count - 1) { |
|||
paddingEnd = horizontalMargin; |
|||
} |
|||
else { |
|||
paddingEnd = columnSpacing / 2.0f; |
|||
} |
|||
|
|||
EdgeInsetsDirectional padding = EdgeInsetsDirectional.only( |
|||
start: paddingStart, |
|||
end: paddingEnd |
|||
); |
|||
if (dataColumnIndex == _onlyTextColumn) { |
|||
tableColumns[displayColumnIndex] = new IntrinsicColumnWidth(flex: 1.0f); |
|||
} |
|||
else { |
|||
tableColumns[displayColumnIndex] = new IntrinsicColumnWidth(); |
|||
} |
|||
|
|||
tableRows[0].children[displayColumnIndex] = _buildHeadingCell( |
|||
context: context, |
|||
padding: padding, |
|||
label: column.label, |
|||
tooltip: column.tooltip, |
|||
numeric: column.numeric, |
|||
onSort: column.onSort != null |
|||
? () => column.onSort(dataColumnIndex, sortColumnIndex != dataColumnIndex || !sortAscending) |
|||
: (VoidCallback) null, |
|||
sorted: dataColumnIndex == sortColumnIndex, |
|||
ascending: sortAscending |
|||
); |
|||
rowIndex = 1; |
|||
foreach (DataRow row in rows) { |
|||
DataCell cell = row.cells[dataColumnIndex]; |
|||
tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell( |
|||
context: context, |
|||
padding: padding, |
|||
label: cell.child, |
|||
numeric: column.numeric, |
|||
placeholder: cell.placeholder, |
|||
showEditIcon: cell.showEditIcon, |
|||
onTap: cell.onTap, |
|||
onSelectChanged: () => { |
|||
if (row.onSelectChanged != null) { |
|||
row.onSelectChanged(!row.selected); |
|||
} |
|||
}); |
|||
rowIndex += 1; |
|||
} |
|||
|
|||
displayColumnIndex += 1; |
|||
} |
|||
|
|||
return new Table( |
|||
columnWidths: tableColumns.Select((x, i) => new {x, i}) |
|||
.ToDictionary(a => a.i, a => a.x), |
|||
children: tableRows |
|||
); |
|||
} |
|||
} |
|||
|
|||
public class TableRowInkWell : InkResponse { |
|||
public TableRowInkWell( |
|||
Key key = null, |
|||
Widget child = null, |
|||
GestureTapCallback onTap = null, |
|||
GestureTapCallback onDoubleTap = null, |
|||
GestureLongPressCallback onLongPress = null, |
|||
ValueChanged<bool> onHighlightChanged = null |
|||
) : base( |
|||
key: key, |
|||
child: child, |
|||
onTap: onTap, |
|||
onDoubleTap: onDoubleTap, |
|||
onLongPress: onLongPress, |
|||
onHighlightChanged: onHighlightChanged, |
|||
containedInkWell: true, |
|||
highlightShape: BoxShape.rectangle |
|||
) { |
|||
} |
|||
|
|||
public override RectCallback getRectCallback(RenderBox referenceBox) { |
|||
return () => { |
|||
RenderObject cell = referenceBox; |
|||
AbstractNodeMixinDiagnosticableTree table = cell.parent; |
|||
Matrix4 transform = Matrix4.identity(); |
|||
while (table is RenderObject && !(table is RenderTable)) { |
|||
RenderObject parentBox = table as RenderObject; |
|||
parentBox.applyPaintTransform(cell, transform); |
|||
D.assert(table == cell.parent); |
|||
cell = parentBox; |
|||
table = table.parent; |
|||
} |
|||
|
|||
if (table is RenderTable renderTable) { |
|||
TableCellParentData cellParentData = cell.parentData as TableCellParentData; |
|||
D.assert(cellParentData.y != null); |
|||
Rect rect = renderTable.getRowBox(cellParentData.y); |
|||
// The rect is in the table's coordinate space. We need to change it to the
|
|||
// TableRowInkWell's coordinate space.
|
|||
renderTable.applyPaintTransform(cell, transform); |
|||
Offset offset = MatrixUtils.getAsTranslation(transform); |
|||
if (offset != null) |
|||
return rect.shift(-offset); |
|||
} |
|||
|
|||
return Rect.zero; |
|||
} |
|||
; |
|||
} |
|||
|
|||
public override bool debugCheckContext(BuildContext context) { |
|||
D.assert(WidgetsD.debugCheckHasTable(context)); |
|||
return base.debugCheckContext(context); |
|||
} |
|||
} |
|||
|
|||
internal class _SortArrow : StatefulWidget { |
|||
internal _SortArrow( |
|||
Key key = null, |
|||
bool? visible = null, |
|||
bool? down = null, |
|||
TimeSpan? duration = null |
|||
) : base(key: key) { |
|||
this.visible = visible; |
|||
this.down = down; |
|||
this.duration = duration; |
|||
} |
|||
|
|||
public readonly bool? visible; |
|||
public readonly bool? down; |
|||
public readonly TimeSpan? duration; |
|||
|
|||
public override State createState() => new _SortArrowState(); |
|||
} |
|||
|
|||
class _SortArrowState : TickerProviderStateMixin<_SortArrow> { |
|||
AnimationController _opacityController; |
|||
Animation<float> _opacityAnimation; |
|||
AnimationController _orientationController; |
|||
Animation<float> _orientationAnimation; |
|||
float _orientationOffset = 0.0f; |
|||
bool _down; |
|||
|
|||
static readonly Animatable<float> _turnTween = |
|||
new Tween<float>(begin: 0.0f, end: Mathf.PI).chain(new CurveTween(curve: Curves.easeIn)); |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
_opacityAnimation = new CurvedAnimation( |
|||
parent: _opacityController = new AnimationController( |
|||
duration: widget.duration, |
|||
vsync: this |
|||
), |
|||
curve: Curves.fastOutSlowIn |
|||
); |
|||
_opacityAnimation.addListener(_rebuild); |
|||
_opacityController.setValue(widget.visible ?? false ? 1.0f : 0.0f); |
|||
_orientationController = new AnimationController( |
|||
duration: widget.duration, |
|||
vsync: this |
|||
); |
|||
_orientationAnimation = _orientationController.drive(_turnTween); |
|||
_orientationAnimation.addListener(_rebuild); |
|||
_orientationAnimation.addStatusListener(_resetOrientationAnimation); |
|||
if (widget.visible ?? false) { |
|||
_orientationOffset = widget.down ?? false ? 0.0f : Mathf.PI; |
|||
} |
|||
} |
|||
|
|||
void _rebuild() { |
|||
setState(() => { |
|||
// The animations changed, so we need to rebuild.
|
|||
}); |
|||
} |
|||
|
|||
void _resetOrientationAnimation(AnimationStatus status) { |
|||
if (status == AnimationStatus.completed) { |
|||
D.assert(_orientationAnimation.value == Mathf.PI); |
|||
_orientationOffset += Mathf.PI; |
|||
_orientationController.setValue(0.0f); // TODO(ianh): This triggers a pointless rebuild.
|
|||
} |
|||
} |
|||
|
|||
public override void didUpdateWidget(StatefulWidget oldStatefullWidget) { |
|||
var oldWidget = oldStatefullWidget as _SortArrow; |
|||
if (oldWidget == null) { |
|||
return; |
|||
} |
|||
|
|||
base.didUpdateWidget(oldWidget); |
|||
|
|||
bool skipArrow = false; |
|||
bool newDown = widget.down ?? _down; |
|||
if (oldWidget.visible != widget.visible) { |
|||
if ((widget.visible ?? false) && (_opacityController.status == AnimationStatus.dismissed)) { |
|||
_orientationController.stop(); |
|||
_orientationController.setValue(0.0f); |
|||
_orientationOffset = newDown ? 0.0f : Mathf.PI; |
|||
skipArrow = true; |
|||
} |
|||
|
|||
if (widget.visible ?? false) { |
|||
_opacityController.forward(); |
|||
} |
|||
else { |
|||
_opacityController.reverse(); |
|||
} |
|||
} |
|||
|
|||
if ((_down != newDown) && !skipArrow) { |
|||
if (_orientationController.status == AnimationStatus.dismissed) { |
|||
_orientationController.forward(); |
|||
} |
|||
else { |
|||
_orientationController.reverse(); |
|||
} |
|||
} |
|||
|
|||
_down = newDown; |
|||
} |
|||
|
|||
public override void dispose() { |
|||
_opacityController.dispose(); |
|||
_orientationController.dispose(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
const float _arrowIconBaselineOffset = -1.5f; |
|||
const float _arrowIconSize = 16.0f; |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
var transform = Matrix4.rotationZ(_orientationOffset + _orientationAnimation.value); |
|||
transform.setTranslationRaw(0.0f, _arrowIconBaselineOffset, 0.0f); |
|||
return new Opacity( |
|||
opacity: _opacityAnimation.value, |
|||
child: new widgets.Transform( |
|||
transform: transform, |
|||
alignment: Alignment.center, |
|||
child: new Icon( |
|||
Icons.arrow_downward, |
|||
size: _arrowIconSize, |
|||
color: (Theme.of(context).brightness == Brightness.light) ? Colors.black87 : Colors.white70 |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: c56625425d4442ca967966944751b3b9 |
|||
timeCreated: 1611566744 |
|
|||
using Unity.UIWidgets.foundation; |
|||
|
|||
namespace Unity.UIWidgets.material { |
|||
public abstract class DataTableSource : ChangeNotifier { |
|||
public abstract DataRow getRow(int index); |
|||
|
|||
public int rowCount { get; } |
|||
|
|||
public bool isRowCountApproximate { get; } |
|||
|
|||
public int selectedRowCount { get; } |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 29a8053dc21e4bba8f4062327f37e024 |
|||
timeCreated: 1611566587 |
|
|||
fileFormatVersion: 2 |
|||
guid: d676f1e5f07545f3aec98219fb5f30d5 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: df99d16a985a4763bee0382d2024b012 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: b15d251925f84c85bc8e13131dc97609 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 446480ad0cb1b56478ecd4fedbc65cc9 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 64b05ec1279ec1d46957644817d86215 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: a06341367d93d52418dcdcafe6786cb5 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using com.unity.uiwidgets.Runtime.rendering; |
|||
using uiwidgets; |
|||
using Unity.UIWidgets.animation; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.async2; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.gestures; |
|||
using Unity.UIWidgets.painting; |
|||
using Unity.UIWidgets.rendering; |
|||
using Unity.UIWidgets.service; |
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.widgets; |
|||
using UnityEngine; |
|||
using Color = Unity.UIWidgets.ui.Color; |
|||
using TextStyle = Unity.UIWidgets.painting.TextStyle; |
|||
|
|||
/* |
|||
* Differences between Dart & C# |
|||
* Duration => TimeSpan |
|||
* -1 % 4 = 3 => -1 % 4 = -1 |
|||
* [Dart] [DateTime.weekday] provides a 1-based index (Start with Monday) |
|||
* [C#] [DateTime.DayOfWeek] provides a 0-based index (Start with Sunday) |
|||
* @IIzzaya |
|||
*/ |
|||
namespace Unity.UIWidgets.material { |
|||
public class DatePickerUtils { |
|||
public const float _kDatePickerHeaderPortraitHeight = 100.0f; |
|||
public const float _kDatePickerHeaderLandscapeWidth = 168.0f; |
|||
public static readonly TimeSpan _kMonthScrollDuration = new TimeSpan(0, 0, 0, 0, 200); |
|||
public const float _kDayPickerRowHeight = 42.0f; |
|||
public const int _kMaxDayPickerRowCount = 6; |
|||
|
|||
public const float _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2); |
|||
public const float _kMonthPickerPortraitWidth = 330.0f; |
|||
public const float _kMonthPickerLandscapeWidth = 344.0f; |
|||
public const float _kDialogActionBarHeight = 52.0f; |
|||
public const float _kDatePickerLandscapeHeight = _kMaxDayPickerHeight + _kDialogActionBarHeight; |
|||
|
|||
internal static readonly _DayPickerGridDelegate _kDayPickerGridDelegate = new _DayPickerGridDelegate(); |
|||
|
|||
public static Future<object> showDatePicker( |
|||
BuildContext context, |
|||
DateTime initialDate, |
|||
DateTime firstDate, |
|||
DateTime lastDate, |
|||
SelectableDayPredicate selectableDayPredicate = null, |
|||
DatePickerMode initialDatePickerMode = DatePickerMode.day, |
|||
Locale locale = null, |
|||
TransitionBuilder builder = null |
|||
) { |
|||
D.assert(initialDate >= firstDate, () => "initialDate must be on or after firstDate"); |
|||
D.assert(initialDate <= lastDate, () => "initialDate must be on or before lastDate"); |
|||
D.assert(firstDate <= lastDate, () => "lastDate must be on or after firstDate"); |
|||
D.assert( |
|||
selectableDayPredicate == null || selectableDayPredicate(initialDate), |
|||
() => "Provided initialDate must satisfy provided selectableDayPredicate" |
|||
); |
|||
D.assert(context != null); |
|||
D.assert(material_.debugCheckHasMaterialLocalizations(context)); |
|||
|
|||
Widget child = new _DatePickerDialog( |
|||
initialDate: initialDate, |
|||
firstDate: firstDate, |
|||
lastDate: lastDate, |
|||
selectableDayPredicate: selectableDayPredicate, |
|||
initialDatePickerMode: initialDatePickerMode |
|||
); |
|||
|
|||
if (locale != null) { |
|||
child = Localizations.overrides( |
|||
context: context, |
|||
locale: locale, |
|||
child: child |
|||
); |
|||
} |
|||
|
|||
return material_.showDialog<object>( |
|||
context: context, |
|||
builder: (BuildContext _context) => { return builder == null ? child : builder(_context, child); } |
|||
); |
|||
} |
|||
} |
|||
|
|||
public enum DatePickerMode { |
|||
day, |
|||
year |
|||
} |
|||
|
|||
class _DatePickerHeader : StatelessWidget { |
|||
public _DatePickerHeader( |
|||
DateTime selectedDate, |
|||
DatePickerMode mode, |
|||
ValueChanged<DatePickerMode> onModeChanged, |
|||
Orientation orientation, |
|||
Key key = null |
|||
) : base(key: key) { |
|||
this.selectedDate = selectedDate; |
|||
this.mode = mode; |
|||
this.onModeChanged = onModeChanged; |
|||
this.orientation = orientation; |
|||
} |
|||
|
|||
public readonly DateTime selectedDate; |
|||
public readonly DatePickerMode mode; |
|||
public readonly ValueChanged<DatePickerMode> onModeChanged; |
|||
public readonly Orientation orientation; |
|||
|
|||
void _handleChangeMode(DatePickerMode value) { |
|||
if (value != mode) { |
|||
onModeChanged(value); |
|||
} |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
MaterialLocalizations localizations = MaterialLocalizations.of(context); |
|||
ThemeData themeData = Theme.of(context); |
|||
TextTheme headerTextTheme = themeData.primaryTextTheme; |
|||
Color dayColor = null; |
|||
Color yearColor = null; |
|||
switch (themeData.primaryColorBrightness) { |
|||
case Brightness.light: |
|||
dayColor = mode == DatePickerMode.day ? Colors.black87 : Colors.black54; |
|||
yearColor = mode == DatePickerMode.year ? Colors.black87 : Colors.black54; |
|||
break; |
|||
case Brightness.dark: |
|||
dayColor = mode == DatePickerMode.day ? Colors.white : Colors.white70; |
|||
yearColor = mode == DatePickerMode.year ? Colors.white : Colors.white70; |
|||
break; |
|||
} |
|||
|
|||
TextStyle dayStyle = headerTextTheme.display1.copyWith(color: dayColor, height: 1.4f); |
|||
TextStyle yearStyle = headerTextTheme.subhead.copyWith(color: yearColor, height: 1.4f); |
|||
Color backgroundColor = null; |
|||
switch (themeData.brightness) { |
|||
case Brightness.light: |
|||
backgroundColor = themeData.primaryColor; |
|||
break; |
|||
case Brightness.dark: |
|||
backgroundColor = themeData.backgroundColor; |
|||
break; |
|||
} |
|||
|
|||
float width = 0f; |
|||
float height = 0f; |
|||
EdgeInsets padding = null; |
|||
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.center; |
|||
switch (orientation) { |
|||
case Orientation.portrait: |
|||
height = DatePickerUtils._kDatePickerHeaderPortraitHeight; |
|||
padding = EdgeInsets.symmetric(horizontal: 16.0f); |
|||
mainAxisAlignment = MainAxisAlignment.center; |
|||
break; |
|||
case Orientation.landscape: |
|||
width = DatePickerUtils._kDatePickerHeaderLandscapeWidth; |
|||
padding = EdgeInsets.all(8.0f); |
|||
mainAxisAlignment = MainAxisAlignment.start; |
|||
break; |
|||
} |
|||
|
|||
Widget yearButton = new IgnorePointer( |
|||
ignoring: mode != DatePickerMode.day, |
|||
child: new _DateHeaderButton( |
|||
color: backgroundColor, |
|||
onTap: Feedback.wrapForTap(() => _handleChangeMode(DatePickerMode.year), context), |
|||
child: new Text(localizations.formatYear(selectedDate), style: yearStyle) |
|||
) |
|||
); |
|||
Widget dayButton = new IgnorePointer( |
|||
ignoring: mode == DatePickerMode.day, |
|||
child: new _DateHeaderButton( |
|||
color: backgroundColor, |
|||
onTap: Feedback.wrapForTap(() => _handleChangeMode(DatePickerMode.day), context), |
|||
child: new Text(localizations.formatMediumDate(selectedDate), style: dayStyle) |
|||
) |
|||
); |
|||
return new Container( |
|||
width: width, |
|||
height: height, |
|||
padding: padding, |
|||
color: backgroundColor, |
|||
child: new Column( |
|||
mainAxisAlignment: mainAxisAlignment, |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: new List<Widget> {yearButton, dayButton} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _DateHeaderButton : StatelessWidget { |
|||
public _DateHeaderButton( |
|||
GestureTapCallback onTap, |
|||
Color color, |
|||
Widget child, |
|||
Key key = null |
|||
) : base(key: key) { |
|||
this.onTap = onTap; |
|||
this.color = color; |
|||
this.child = child; |
|||
} |
|||
|
|||
public readonly GestureTapCallback onTap; |
|||
public readonly Color color; |
|||
public readonly Widget child; |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
ThemeData theme = Theme.of(context); |
|||
return new Material( |
|||
type: MaterialType.button, |
|||
color: color, |
|||
child: new InkWell( |
|||
borderRadius: MaterialConstantsUtils.kMaterialEdges[MaterialType.button], |
|||
highlightColor: theme.highlightColor, |
|||
splashColor: theme.splashColor, |
|||
onTap: onTap, |
|||
child: new Container( |
|||
padding: EdgeInsets.symmetric(horizontal: 8.0f), |
|||
child: child |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _DayPickerGridDelegate : SliverGridDelegate { |
|||
public _DayPickerGridDelegate() { } |
|||
|
|||
public override SliverGridLayout getLayout(SliverConstraints constraints) { |
|||
const int columnCount = 7; // DateTime.daysPerWeek = 7
|
|||
float tileWidth = constraints.crossAxisExtent / columnCount; |
|||
float tileHeight = Mathf.Min( |
|||
DatePickerUtils._kDayPickerRowHeight, |
|||
constraints.viewportMainAxisExtent / (DatePickerUtils._kMaxDayPickerRowCount + 1) |
|||
); |
|||
return new SliverGridRegularTileLayout( |
|||
crossAxisCount: columnCount, |
|||
mainAxisStride: tileHeight, |
|||
crossAxisStride: tileWidth, |
|||
childMainAxisExtent: tileHeight, |
|||
childCrossAxisExtent: tileWidth, |
|||
reverseCrossAxis: AxisUtils.axisDirectionIsReversed(constraints.crossAxisDirection) |
|||
); |
|||
} |
|||
|
|||
public override bool shouldRelayout(SliverGridDelegate oldDelegate) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public class DayPicker : StatelessWidget { |
|||
public DayPicker( |
|||
DateTime selectedDate, |
|||
DateTime currentDate, |
|||
ValueChanged<DateTime> onChanged, |
|||
DateTime firstDate, |
|||
DateTime lastDate, |
|||
DateTime displayedMonth, |
|||
SelectableDayPredicate selectableDayPredicate = null, |
|||
DragStartBehavior dragStartBehavior = DragStartBehavior.start, |
|||
Key key = null |
|||
) : base(key: key) { |
|||
D.assert(onChanged != null); |
|||
D.assert(firstDate <= lastDate); |
|||
D.assert(selectedDate >= firstDate); |
|||
this.selectedDate = selectedDate; |
|||
this.currentDate = currentDate; |
|||
this.onChanged = onChanged; |
|||
this.firstDate = firstDate; |
|||
this.lastDate = lastDate; |
|||
this.displayedMonth = displayedMonth; |
|||
this.selectableDayPredicate = selectableDayPredicate; |
|||
this.dragStartBehavior = dragStartBehavior; |
|||
} |
|||
|
|||
public readonly DateTime selectedDate; |
|||
public readonly DateTime currentDate; |
|||
public readonly ValueChanged<DateTime> onChanged; |
|||
public readonly DateTime firstDate; |
|||
public readonly DateTime lastDate; |
|||
public readonly DateTime displayedMonth; |
|||
public readonly SelectableDayPredicate selectableDayPredicate; |
|||
public readonly DragStartBehavior dragStartBehavior; |
|||
|
|||
List<Widget> _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) { |
|||
List<Widget> result = new List<Widget>(); |
|||
for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { |
|||
string weekday = localizations.narrowWeekdays[i]; |
|||
result.Add(new Center(child: new Text(weekday, style: headerStyle))); |
|||
if (i == (localizations.firstDayOfWeekIndex + 6) % 7) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
static readonly List<int> _daysInMonth = new List<int> {31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
|||
|
|||
static int getDaysInMonth(int year, int month) { |
|||
if (month == 2) { |
|||
bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0); |
|||
if (isLeapYear) { |
|||
return 29; |
|||
} |
|||
|
|||
return 28; |
|||
} |
|||
|
|||
return _daysInMonth[month - 1]; |
|||
} |
|||
|
|||
int _computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) { |
|||
int weekdayFromMonday = new DateTime(year, month, 1).DayOfWeek.GetHashCode(); |
|||
if (weekdayFromMonday == 0) { |
|||
weekdayFromMonday = 7; |
|||
} |
|||
|
|||
weekdayFromMonday--; |
|||
|
|||
int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex; |
|||
int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7; |
|||
return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7; |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
ThemeData themeData = Theme.of(context); |
|||
MaterialLocalizations localizations = MaterialLocalizations.of(context); |
|||
int year = displayedMonth.Year; |
|||
int month = displayedMonth.Month; |
|||
int daysInMonth = getDaysInMonth(year, month); |
|||
int firstDayOffset = _computeFirstDayOffset(year, month, localizations); |
|||
List<Widget> labels = new List<Widget>(); |
|||
labels.AddRange(_getDayHeaders(themeData.textTheme.caption, localizations)); |
|||
for (int i = 0; true; i += 1) { |
|||
int day = i - firstDayOffset + 1; |
|||
if (day > daysInMonth) { |
|||
break; |
|||
} |
|||
|
|||
if (day < 1) { |
|||
labels.Add(new Container()); |
|||
} |
|||
else { |
|||
DateTime dayToBuild = new DateTime(year, month, day); |
|||
bool disabled = dayToBuild > lastDate |
|||
|| dayToBuild < firstDate |
|||
|| (selectableDayPredicate != null && |
|||
!selectableDayPredicate(dayToBuild)); |
|||
BoxDecoration decoration = null; |
|||
TextStyle itemStyle = themeData.textTheme.body1; |
|||
bool isSelectedDay = selectedDate.Year == year && selectedDate.Month == month && |
|||
selectedDate.Day == day; |
|||
if (isSelectedDay) { |
|||
itemStyle = themeData.accentTextTheme.body2; |
|||
decoration = new BoxDecoration( |
|||
color: themeData.accentColor, |
|||
shape: BoxShape.circle |
|||
); |
|||
} |
|||
else if (disabled) { |
|||
itemStyle = themeData.textTheme.body1.copyWith(color: themeData.disabledColor); |
|||
} |
|||
else if (currentDate.Year == year && currentDate.Month == month && |
|||
currentDate.Day == day) { |
|||
itemStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor); |
|||
} |
|||
|
|||
Widget dayWidget = new Container( |
|||
decoration: decoration, |
|||
child: new Center( |
|||
child: new Text(localizations.formatDecimal(day), style: itemStyle) |
|||
) |
|||
); |
|||
if (!disabled) { |
|||
dayWidget = new GestureDetector( |
|||
behavior: HitTestBehavior.opaque, |
|||
onTap: () => { onChanged(dayToBuild); }, |
|||
child: dayWidget, |
|||
dragStartBehavior: dragStartBehavior |
|||
); |
|||
} |
|||
|
|||
labels.Add(dayWidget); |
|||
} |
|||
} |
|||
|
|||
return new Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 8.0f), |
|||
child: new Column( |
|||
children: new List<Widget> { |
|||
new Container( |
|||
height: DatePickerUtils._kDayPickerRowHeight, |
|||
child: new Center( |
|||
child: new Text( |
|||
localizations.formatMonthYear(displayedMonth), |
|||
style: themeData.textTheme.subhead |
|||
) |
|||
) |
|||
), |
|||
new Flexible( |
|||
child: GridView.custom( |
|||
gridDelegate: DatePickerUtils._kDayPickerGridDelegate, |
|||
childrenDelegate: new SliverChildListDelegate(labels, addRepaintBoundaries: false) |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
class MonthPicker : StatefulWidget { |
|||
public MonthPicker( |
|||
DateTime selectedDate, |
|||
ValueChanged<DateTime> onChanged, |
|||
DateTime firstDate, |
|||
DateTime lastDate, |
|||
SelectableDayPredicate selectableDayPredicate, |
|||
DragStartBehavior dragStartBehavior = DragStartBehavior.start, |
|||
Key key = null |
|||
) : base(key: key) { |
|||
D.assert(selectedDate != null); |
|||
D.assert(onChanged != null); |
|||
D.assert(firstDate <= lastDate); |
|||
D.assert(selectedDate >= firstDate); |
|||
this.selectedDate = selectedDate; |
|||
this.onChanged = onChanged; |
|||
this.firstDate = firstDate; |
|||
this.lastDate = lastDate; |
|||
this.selectableDayPredicate = selectableDayPredicate; |
|||
this.dragStartBehavior = dragStartBehavior; |
|||
} |
|||
|
|||
public readonly DateTime selectedDate; |
|||
public readonly ValueChanged<DateTime> onChanged; |
|||
public readonly DateTime firstDate; |
|||
public readonly DateTime lastDate; |
|||
public readonly SelectableDayPredicate selectableDayPredicate; |
|||
public readonly DragStartBehavior dragStartBehavior; |
|||
|
|||
public override State createState() { |
|||
return new _MonthPickerState(); |
|||
} |
|||
} |
|||
|
|||
class _MonthPickerState : SingleTickerProviderStateMixin<MonthPicker> { |
|||
static Animatable<float> __chevronOpacityTween; |
|||
|
|||
static Animatable<float> _chevronOpacityTween { |
|||
get { |
|||
if (__chevronOpacityTween == null) { |
|||
__chevronOpacityTween = new FloatTween(begin: 1.0f, end: 0.0f) { }; |
|||
__chevronOpacityTween.chain(new CurveTween(curve: Curves.easeInOut)); |
|||
} |
|||
|
|||
return __chevronOpacityTween; |
|||
} |
|||
} |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
int monthPage = _monthDelta(widget.firstDate, widget.selectedDate); |
|||
_dayPickerController = new PageController(initialPage: monthPage); |
|||
_handleMonthPageChanged(monthPage); |
|||
_updateCurrentDate(); |
|||
_chevronOpacityController = new AnimationController( |
|||
duration: new TimeSpan(0, 0, 0, 0, 250), |
|||
vsync: this |
|||
); |
|||
_chevronOpacityAnimation = _chevronOpacityController.drive(_chevronOpacityTween); |
|||
} |
|||
|
|||
public override void didUpdateWidget(StatefulWidget oldWidget) { |
|||
base.didUpdateWidget(oldWidget); |
|||
if (widget.selectedDate != ((MonthPicker) oldWidget).selectedDate) { |
|||
int monthPage = _monthDelta(widget.firstDate, widget.selectedDate); |
|||
_dayPickerController = new PageController(initialPage: monthPage); |
|||
_handleMonthPageChanged(monthPage); |
|||
} |
|||
} |
|||
|
|||
MaterialLocalizations localizations; |
|||
|
|||
public override void didChangeDependencies() { |
|||
base.didChangeDependencies(); |
|||
localizations = MaterialLocalizations.of(context); |
|||
} |
|||
|
|||
DateTime _todayDate; |
|||
DateTime _currentDisplayedMonthDate; |
|||
Timer _timer; |
|||
PageController _dayPickerController; |
|||
AnimationController _chevronOpacityController; |
|||
Animation<float> _chevronOpacityAnimation; |
|||
|
|||
void _updateCurrentDate() { |
|||
_todayDate = DateTime.Now; |
|||
DateTime tomorrow = _todayDate.AddDays(1); |
|||
TimeSpan timeUntilTomorrow = tomorrow.TimeOfDay - _todayDate.TimeOfDay; |
|||
_timer?.cancel(); |
|||
_timer = Timer.create(timeUntilTomorrow, |
|||
() => { setState(() => { _updateCurrentDate(); }); }); |
|||
} |
|||
|
|||
static int _monthDelta(DateTime startDate, DateTime endDate) { |
|||
return (endDate.Year - startDate.Year) * 12 + endDate.Month - startDate.Month; |
|||
} |
|||
|
|||
DateTime _addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) { |
|||
return monthDate.AddMonths(monthsToAdd); |
|||
} |
|||
|
|||
Widget _buildItems(BuildContext context, int index) { |
|||
DateTime month = _addMonthsToMonthDate(widget.firstDate, index); |
|||
return new DayPicker( |
|||
key: new ValueKey<DateTime>(month), |
|||
selectedDate: widget.selectedDate, |
|||
currentDate: _todayDate, |
|||
onChanged: widget.onChanged, |
|||
firstDate: widget.firstDate, |
|||
lastDate: widget.lastDate, |
|||
displayedMonth: month, |
|||
selectableDayPredicate: widget.selectableDayPredicate, |
|||
dragStartBehavior: widget.dragStartBehavior |
|||
); |
|||
} |
|||
|
|||
void _handleNextMonth() { |
|||
if (!_isDisplayingLastMonth) { |
|||
_dayPickerController.nextPage(duration: DatePickerUtils._kMonthScrollDuration, curve: Curves.ease); |
|||
} |
|||
} |
|||
|
|||
void _handlePreviousMonth() { |
|||
if (!_isDisplayingFirstMonth) { |
|||
_dayPickerController.previousPage(duration: DatePickerUtils._kMonthScrollDuration, |
|||
curve: Curves.ease); |
|||
} |
|||
} |
|||
|
|||
bool _isDisplayingFirstMonth { |
|||
get { |
|||
return _currentDisplayedMonthDate <= |
|||
new DateTime(widget.firstDate.Year, widget.firstDate.Month, 1); |
|||
} |
|||
} |
|||
|
|||
bool _isDisplayingLastMonth { |
|||
get { |
|||
return _currentDisplayedMonthDate >= |
|||
new DateTime(widget.lastDate.Year, widget.lastDate.Month, 1); |
|||
} |
|||
} |
|||
|
|||
DateTime _previousMonthDate; |
|||
DateTime _nextMonthDate; |
|||
|
|||
void _handleMonthPageChanged(int monthPage) { |
|||
setState(() => { |
|||
_previousMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage - 1); |
|||
_currentDisplayedMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage); |
|||
_nextMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage + 1); |
|||
}); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
return new SizedBox( |
|||
width: DatePickerUtils._kMonthPickerPortraitWidth, |
|||
height: DatePickerUtils._kMaxDayPickerHeight, |
|||
child: new Stack( |
|||
children: new List<Widget> { |
|||
new NotificationListener<ScrollStartNotification>( |
|||
onNotification: (_) => { |
|||
_chevronOpacityController.forward(); |
|||
return false; |
|||
}, |
|||
child: new NotificationListener<ScrollEndNotification>( |
|||
onNotification: (_) => { |
|||
_chevronOpacityController.reverse(); |
|||
return false; |
|||
}, |
|||
child: PageView.builder( |
|||
dragStartBehavior: widget.dragStartBehavior, |
|||
key: new ValueKey<DateTime>(widget.selectedDate), |
|||
controller: _dayPickerController, |
|||
scrollDirection: Axis.horizontal, |
|||
itemCount: _monthDelta(widget.firstDate, widget.lastDate) + 1, |
|||
itemBuilder: _buildItems, |
|||
onPageChanged: _handleMonthPageChanged |
|||
) |
|||
) |
|||
), |
|||
new Positioned( |
|||
top: 0.0f, |
|||
left: 8.0f, |
|||
child: new FadeTransition( |
|||
opacity: _chevronOpacityAnimation, |
|||
child: new IconButton( |
|||
icon: new Icon(Icons.chevron_left), |
|||
tooltip: _isDisplayingFirstMonth |
|||
? null |
|||
: $"{localizations.previousMonthTooltip} {localizations.formatMonthYear(_previousMonthDate)}", |
|||
onPressed: _isDisplayingFirstMonth |
|||
? (VoidCallback) null |
|||
: _handlePreviousMonth |
|||
) |
|||
) |
|||
), |
|||
new Positioned( |
|||
top: 0.0f, |
|||
right: 8.0f, |
|||
child: new FadeTransition( |
|||
opacity: _chevronOpacityAnimation, |
|||
child: new IconButton( |
|||
icon: new Icon(Icons.chevron_right), |
|||
tooltip: _isDisplayingLastMonth |
|||
? null |
|||
: $"{localizations.nextMonthTooltip} {localizations.formatMonthYear(_nextMonthDate)}", |
|||
onPressed: _isDisplayingLastMonth |
|||
? (VoidCallback) null |
|||
: _handleNextMonth |
|||
) |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
} |
|||
|
|||
public override void dispose() { |
|||
_timer?.cancel(); |
|||
_dayPickerController?.dispose(); |
|||
base.dispose(); |
|||
} |
|||
} |
|||
|
|||
class _MonthPickerSortKey : Diagnosticable { |
|||
public _MonthPickerSortKey(float order) { } |
|||
public static readonly _MonthPickerSortKey previousMonth = new _MonthPickerSortKey(1.0f); |
|||
public static readonly _MonthPickerSortKey nextMonth = new _MonthPickerSortKey(2.0f); |
|||
public static readonly _MonthPickerSortKey calendar = new _MonthPickerSortKey(3.0f); |
|||
} |
|||
|
|||
public class YearPicker : StatefulWidget { |
|||
public YearPicker( |
|||
DateTime selectedDate, |
|||
ValueChanged<DateTime> onChanged, |
|||
DateTime firstDate, |
|||
DateTime lastDate, |
|||
DragStartBehavior dragStartBehavior = DragStartBehavior.start, |
|||
Key key = null |
|||
) : base(key: key) { |
|||
D.assert(selectedDate != null); |
|||
D.assert(onChanged != null); |
|||
D.assert(firstDate <= lastDate); |
|||
this.selectedDate = selectedDate; |
|||
this.onChanged = onChanged; |
|||
this.firstDate = firstDate; |
|||
this.lastDate = lastDate; |
|||
this.dragStartBehavior = dragStartBehavior; |
|||
} |
|||
|
|||
public readonly DateTime selectedDate; |
|||
public readonly ValueChanged<DateTime> onChanged; |
|||
public readonly DateTime firstDate; |
|||
public readonly DateTime lastDate; |
|||
public readonly DragStartBehavior dragStartBehavior; |
|||
|
|||
public override State createState() { |
|||
return new _YearPickerState(); |
|||
} |
|||
} |
|||
|
|||
class _YearPickerState : State<YearPicker> { |
|||
const float _itemExtent = 50.0f; |
|||
ScrollController scrollController; |
|||
|
|||
public override void initState() { |
|||
base.initState(); |
|||
scrollController = new ScrollController( |
|||
initialScrollOffset: (widget.selectedDate.Year - widget.firstDate.Year) * _itemExtent |
|||
); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
D.assert(material_.debugCheckHasMaterial(context)); |
|||
ThemeData themeData = Theme.of(context); |
|||
TextStyle style = themeData.textTheme.body1; |
|||
return ListView.builder( |
|||
dragStartBehavior: widget.dragStartBehavior, |
|||
controller: scrollController, |
|||
itemExtent: _itemExtent, |
|||
itemCount: widget.lastDate.Year - widget.firstDate.Year + 1, |
|||
itemBuilder: (BuildContext _context, int index) => { |
|||
int year = widget.firstDate.Year + index; |
|||
bool isSelected = year == widget.selectedDate.Year; |
|||
TextStyle itemStyle = isSelected |
|||
? themeData.textTheme.headline.copyWith(color: themeData.accentColor) |
|||
: style; |
|||
return new InkWell( |
|||
key: new ValueKey<int>(year), |
|||
onTap: () => { |
|||
widget.onChanged(new DateTime(year, widget.selectedDate.Month, |
|||
widget.selectedDate.Day)); |
|||
}, |
|||
child: new Center( |
|||
child: new Text(year.ToString(), style: itemStyle) |
|||
) |
|||
); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
class _DatePickerDialog : StatefulWidget { |
|||
public _DatePickerDialog( |
|||
DateTime initialDate, |
|||
DateTime firstDate, |
|||
DateTime lastDate, |
|||
SelectableDayPredicate selectableDayPredicate, |
|||
DatePickerMode initialDatePickerMode, |
|||
Key key = null |
|||
) : base(key: key) { |
|||
this.initialDate = initialDate; |
|||
this.firstDate = firstDate; |
|||
this.lastDate = lastDate; |
|||
this.selectableDayPredicate = selectableDayPredicate; |
|||
this.initialDatePickerMode = initialDatePickerMode; |
|||
} |
|||
|
|||
public readonly DateTime initialDate; |
|||
public readonly DateTime firstDate; |
|||
public readonly DateTime lastDate; |
|||
public readonly SelectableDayPredicate selectableDayPredicate; |
|||
public readonly DatePickerMode initialDatePickerMode; |
|||
|
|||
public override State createState() { |
|||
return new _DatePickerDialogState(); |
|||
} |
|||
} |
|||
|
|||
class _DatePickerDialogState : State<_DatePickerDialog> { |
|||
public override void initState() { |
|||
base.initState(); |
|||
_selectedDate = widget.initialDate; |
|||
_mode = widget.initialDatePickerMode; |
|||
} |
|||
|
|||
bool _announcedInitialDate = false; |
|||
public MaterialLocalizations localizations; |
|||
|
|||
public override void didChangeDependencies() { |
|||
base.didChangeDependencies(); |
|||
localizations = MaterialLocalizations.of(context); |
|||
if (!_announcedInitialDate) { |
|||
_announcedInitialDate = true; |
|||
} |
|||
} |
|||
|
|||
DateTime _selectedDate; |
|||
|
|||
DatePickerMode _mode; |
|||
GlobalKey _pickerKey = GlobalKey.key(); |
|||
|
|||
void _vibrate() { |
|||
switch (Theme.of(context).platform) { |
|||
case RuntimePlatform.Android: |
|||
// case RuntimePlatform.fuchsia:
|
|||
// HapticFeedback.vibrate();
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
void _handleModeChanged(DatePickerMode mode) { |
|||
_vibrate(); |
|||
setState(() => { |
|||
_mode = mode; |
|||
if (_mode == DatePickerMode.day) { |
|||
// SemanticsService.announce(localizations.formatMonthYear(_selectedDate), textDirection);
|
|||
} |
|||
else { |
|||
// SemanticsService.announce(localizations.formatYear(_selectedDate), textDirection);
|
|||
} |
|||
}); |
|||
} |
|||
|
|||
void _handleYearChanged(DateTime value) { |
|||
if (value < widget.firstDate) { |
|||
value = widget.firstDate; |
|||
} |
|||
else if (value > widget.lastDate) { |
|||
value = widget.lastDate; |
|||
} |
|||
|
|||
if (value == _selectedDate) { |
|||
return; |
|||
} |
|||
|
|||
_vibrate(); |
|||
setState(() => { |
|||
_mode = DatePickerMode.day; |
|||
_selectedDate = value; |
|||
}); |
|||
} |
|||
|
|||
void _handleDayChanged(DateTime value) { |
|||
_vibrate(); |
|||
setState(() => { _selectedDate = value; }); |
|||
} |
|||
|
|||
void _handleCancel() { |
|||
Navigator.pop<object>(context); |
|||
} |
|||
|
|||
void _handleOk() { |
|||
Navigator.pop(context, _selectedDate); |
|||
} |
|||
|
|||
Widget _buildPicker() { |
|||
switch (_mode) { |
|||
case DatePickerMode.day: |
|||
return new MonthPicker( |
|||
key: _pickerKey, |
|||
selectedDate: _selectedDate, |
|||
onChanged: _handleDayChanged, |
|||
firstDate: widget.firstDate, |
|||
lastDate: widget.lastDate, |
|||
selectableDayPredicate: widget.selectableDayPredicate |
|||
); |
|||
case DatePickerMode.year: |
|||
return new YearPicker( |
|||
key: _pickerKey, |
|||
selectedDate: _selectedDate, |
|||
onChanged: _handleYearChanged, |
|||
firstDate: widget.firstDate, |
|||
lastDate: widget.lastDate |
|||
); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) { |
|||
ThemeData theme = Theme.of(context); |
|||
Widget picker = new Flexible( |
|||
child: new SizedBox( |
|||
height: DatePickerUtils._kMaxDayPickerHeight, |
|||
child: _buildPicker() |
|||
) |
|||
); |
|||
Widget actions = ButtonTheme.bar( |
|||
child: new ButtonBar( |
|||
children: new List<Widget> { |
|||
new FlatButton( |
|||
child: new Text(localizations.cancelButtonLabel), |
|||
onPressed: _handleCancel |
|||
), |
|||
new FlatButton( |
|||
child: new Text(localizations.okButtonLabel), |
|||
onPressed: _handleOk |
|||
) |
|||
} |
|||
) |
|||
); |
|||
Dialog dialog = new Dialog( |
|||
child: new OrientationBuilder( |
|||
builder: (BuildContext _context, Orientation orientation) => { |
|||
Widget header = new _DatePickerHeader( |
|||
selectedDate: _selectedDate, |
|||
mode: _mode, |
|||
onModeChanged: _handleModeChanged, |
|||
orientation: orientation |
|||
); |
|||
|
|||
switch (orientation) { |
|||
case Orientation.portrait: |
|||
return new SizedBox( |
|||
width: DatePickerUtils._kMonthPickerPortraitWidth, |
|||
child: new Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|||
children: new List<Widget> { |
|||
header, |
|||
new Container( |
|||
color: theme.dialogBackgroundColor, |
|||
child: new Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|||
children: new List<Widget> { |
|||
picker, |
|||
actions, |
|||
} |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
case Orientation.landscape: |
|||
return new SizedBox( |
|||
height: DatePickerUtils._kDatePickerLandscapeHeight, |
|||
child: new Row( |
|||
mainAxisSize: MainAxisSize.min, |
|||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|||
children: new List<Widget> { |
|||
header, |
|||
new Flexible( |
|||
child: new Container( |
|||
width: DatePickerUtils._kMonthPickerLandscapeWidth, |
|||
color: theme.dialogBackgroundColor, |
|||
child: new Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
crossAxisAlignment: CrossAxisAlignment.stretch, |
|||
children: new List<Widget> {picker, actions} |
|||
) |
|||
) |
|||
) |
|||
} |
|||
) |
|||
); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
) |
|||
); |
|||
|
|||
return new Theme( |
|||
data: theme.copyWith( |
|||
dialogBackgroundColor: Colors.transparent |
|||
), |
|||
child: dialog |
|||
); |
|||
} |
|||
} |
|||
|
|||
public delegate bool SelectableDayPredicate(DateTime day); |
|||
} |
撰写
预览
正在加载...
取消
保存
Reference in new issue