diff --git a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickedEventArgs.cs b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickedEventArgs.cs index 1498941d43fb..e90ef24ee017 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickedEventArgs.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickedEventArgs.cs @@ -2,31 +2,13 @@ #pragma warning disable 114 // new keyword hiding namespace Windows.UI.Xaml.Controls { - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || false + #if false || false || false || false || false [global::Uno.NotImplemented] #endif - public partial class DatePickedEventArgs : global::Windows.UI.Xaml.DependencyObject + public partial class DatePickedEventArgs : global::Windows.UI.Xaml.DependencyObject { - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ - [global::Uno.NotImplemented] - public global::System.DateTimeOffset NewDate - { - get - { - throw new global::System.NotImplementedException("The member DateTimeOffset DatePickedEventArgs.NewDate is not implemented in Uno."); - } - } - #endif - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ - [global::Uno.NotImplemented] - public global::System.DateTimeOffset OldDate - { - get - { - throw new global::System.NotImplementedException("The member DateTimeOffset DatePickedEventArgs.OldDate is not implemented in Uno."); - } - } - #endif + // Skipping already declared property NewDate + // Skipping already declared property OldDate // Skipping already declared method Windows.UI.Xaml.Controls.DatePickedEventArgs.DatePickedEventArgs() // Forced skipping of method Windows.UI.Xaml.Controls.DatePickedEventArgs.DatePickedEventArgs() // Forced skipping of method Windows.UI.Xaml.Controls.DatePickedEventArgs.OldDate.get diff --git a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePicker.cs b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePicker.cs index 6bbbc1626283..1ceeab29c1ba 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePicker.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePicker.cs @@ -111,20 +111,7 @@ public string CalendarIdentifier } } #endif - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ - [global::Uno.NotImplemented] - public global::System.DateTimeOffset? SelectedDate - { - get - { - return (global::System.DateTimeOffset?)this.GetValue(SelectedDateProperty); - } - set - { - this.SetValue(SelectedDateProperty, value); - } - } - #endif + // Skipping already declared property SelectedDate #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ [global::Uno.NotImplemented] public static global::Windows.UI.Xaml.DependencyProperty CalendarIdentifierProperty { get; } = @@ -185,16 +172,9 @@ public string CalendarIdentifier "YearFormat", typeof(string), typeof(global::Windows.UI.Xaml.Controls.DatePicker), new FrameworkPropertyMetadata(default(string))); - #endif +#endif // Skipping already declared property YearVisibleProperty - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ - [global::Uno.NotImplemented] - public static global::Windows.UI.Xaml.DependencyProperty SelectedDateProperty { get; } = - Windows.UI.Xaml.DependencyProperty.Register( - "SelectedDate", typeof(global::System.DateTimeOffset?), - typeof(global::Windows.UI.Xaml.Controls.DatePicker), - new FrameworkPropertyMetadata(default(global::System.DateTimeOffset?))); - #endif + // Skipping already declared property SelectedDateProperty // Skipping already declared method Windows.UI.Xaml.Controls.DatePicker.DatePicker() // Forced skipping of method Windows.UI.Xaml.Controls.DatePicker.DatePicker() // Forced skipping of method Windows.UI.Xaml.Controls.DatePicker.Header.get @@ -247,21 +227,6 @@ public string CalendarIdentifier // Forced skipping of method Windows.UI.Xaml.Controls.DatePicker.MaxYearProperty.get // Forced skipping of method Windows.UI.Xaml.Controls.DatePicker.OrientationProperty.get // Skipping already declared event Windows.UI.Xaml.Controls.DatePicker.DateChanged - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ - [global::Uno.NotImplemented] - public event global::Windows.Foundation.TypedEventHandler SelectedDateChanged - { - [global::Uno.NotImplemented] - add - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Controls.DatePicker", "event TypedEventHandler DatePicker.SelectedDateChanged"); - } - [global::Uno.NotImplemented] - remove - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Controls.DatePicker", "event TypedEventHandler DatePicker.SelectedDateChanged"); - } - } - #endif + // Skipping already declared event Windows.UI.Xaml.Controls.DatePicker.SelectedDateChanged } } diff --git a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerFlyout.cs b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerFlyout.cs index 2f7a720ed63c..b68784e236cb 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerFlyout.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerFlyout.cs @@ -148,7 +148,7 @@ public string DayFormat // Forced skipping of method Windows.UI.Xaml.Controls.DatePickerFlyout.YearVisibleProperty.get // Forced skipping of method Windows.UI.Xaml.Controls.DatePickerFlyout.MinYearProperty.get // Forced skipping of method Windows.UI.Xaml.Controls.DatePickerFlyout.MaxYearProperty.get - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ + #if false || false || NET461 || __WASM__ || __MACOS__ [global::Uno.NotImplemented] public event global::Windows.Foundation.TypedEventHandler DatePicked { diff --git a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerSelectedValueChangedEventArgs.cs b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerSelectedValueChangedEventArgs.cs index d0dd56a3cef4..d8f42f35254b 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerSelectedValueChangedEventArgs.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Windows.UI.Xaml.Controls/DatePickerSelectedValueChangedEventArgs.cs @@ -2,31 +2,13 @@ #pragma warning disable 114 // new keyword hiding namespace Windows.UI.Xaml.Controls { - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ + #if false || false || false || false || false [global::Uno.NotImplemented] #endif public partial class DatePickerSelectedValueChangedEventArgs { - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ - [global::Uno.NotImplemented] - public global::System.DateTimeOffset? NewDate - { - get - { - throw new global::System.NotImplementedException("The member DateTimeOffset? DatePickerSelectedValueChangedEventArgs.NewDate is not implemented in Uno."); - } - } - #endif - #if __ANDROID__ || __IOS__ || NET461 || __WASM__ || __MACOS__ - [global::Uno.NotImplemented] - public global::System.DateTimeOffset? OldDate - { - get - { - throw new global::System.NotImplementedException("The member DateTimeOffset? DatePickerSelectedValueChangedEventArgs.OldDate is not implemented in Uno."); - } - } - #endif + // Skipping already declared property NewDate + // Skipping already declared property OldDate // Forced skipping of method Windows.UI.Xaml.Controls.DatePickerSelectedValueChangedEventArgs.OldDate.get // Forced skipping of method Windows.UI.Xaml.Controls.DatePickerSelectedValueChangedEventArgs.NewDate.get } diff --git a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickedEventArgs.cs b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickedEventArgs.cs new file mode 100644 index 000000000000..6592bc6154cb --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Windows.UI.Xaml.Controls +{ + public partial class DatePickedEventArgs + { + public DatePickedEventArgs(DateTimeOffset newDate, DateTimeOffset oldDate) + { + NewDate = newDate; + OldDate = oldDate; + } + + public DateTimeOffset NewDate { get; } + public DateTimeOffset OldDate { get; } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePicker.cs b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePicker.cs index a61bb0a9a46a..d078b9f551b4 100644 --- a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePicker.cs +++ b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePicker.cs @@ -14,6 +14,9 @@ namespace Windows.UI.Xaml.Controls public partial class DatePicker : Control { public event EventHandler DateChanged; + public event EventHandler SelectedDateChanged; + + private static readonly DateTimeOffset UnsetDateValue = DateTimeOffset.MinValue; #region DateProperty @@ -27,11 +30,17 @@ public DateTimeOffset Date //Set initial value of DatePicker to DateTimeOffset.MinValue to avoid 2 way binding issue where the DatePicker reset Date(DateTimeOffset.MinValue) after the initial binding value. //We assume that this is the view model who will set the initial value just the time to fix #18331 public static readonly DependencyProperty DateProperty = - DependencyProperty.Register("Date", typeof(DateTimeOffset), typeof(DatePicker), new PropertyMetadata(DateTimeOffset.MinValue, + DependencyProperty.Register("Date", typeof(DateTimeOffset), typeof(DatePicker), new PropertyMetadata(UnsetDateValue, (s, e) => ((DatePicker)s).OnDatePropertyChanged((DateTimeOffset)e.NewValue, (DateTimeOffset)e.OldValue))); private void OnDatePropertyChanged(DateTimeOffset newValue, DateTimeOffset oldValue) { + if ((SelectedDate != newValue) && + !(newValue == UnsetDateValue && !SelectedDate.HasValue)) + { + SelectedDate = newValue; + } + #if XAMARIN UpdateDisplayedDate(); #endif @@ -43,6 +52,33 @@ private void OnDatePropertyChanged(DateTimeOffset newValue, DateTimeOffset oldVa partial void OnDateChangedPartial(); #endregion + #region Property: SelectedDate + public static readonly DependencyProperty SelectedDateProperty = DependencyProperty.Register( + nameof(SelectedDate), + typeof(DateTimeOffset?), + typeof(DatePicker), + new PropertyMetadata(default(DateTimeOffset?), (s, e) => (s as DatePicker).OnSelectedDateChanged((DateTimeOffset?)e.NewValue, (DateTimeOffset?)e.OldValue))); + + public DateTimeOffset? SelectedDate + { + get => (DateTimeOffset?)GetValue(SelectedDateProperty); + set => SetValue(SelectedDateProperty, value); + } + + private void OnSelectedDateChanged(DateTimeOffset? newValue, DateTimeOffset? oldValue) + { + if (Date != (newValue ?? UnsetDateValue)) + { + Date = newValue ?? UnsetDateValue; + } + + OnSelectedDatePartial(); + SelectedDateChanged?.Invoke(this, new DatePickerSelectedValueChangedEventArgs(newValue, oldValue)); + } + + partial void OnSelectedDatePartial(); + #endregion + #region DayVisibleProperty public bool DayVisible { @@ -171,6 +207,7 @@ public DatePicker() #endregion private Button _flyoutButton; + private DatePickerFlyout _flyout; private TextBlock _dayTextBlock; private TextBlock _monthTextBlock; @@ -237,28 +274,27 @@ private void SetupFlyoutButton() if (_flyoutButton != null) { #if __IOS__ || __ANDROID__ - _flyoutButton.Flyout = new DatePickerFlyout() + _flyoutButton.Flyout = _flyout = new DatePickerFlyout() { -#if __IOS__ - Placement = FlyoutPlacement, -#endif Date = Date, MinYear = MinYear, - MaxYear = MaxYear + MaxYear = MaxYear, }; - BindToFlyout(nameof(Date)); + _flyout.Opened += (s, e) => _flyout.Date = SelectedDate ?? DateTimeOffset.Now; + _flyout.DatePicked += (s, e) => Date = e.NewDate; + BindToFlyout(nameof(MinYear)); BindToFlyout(nameof(MaxYear)); - _flyoutButton.Flyout.BindToEquivalentProperty(this, nameof(LightDismissOverlayMode)); - _flyoutButton.Flyout.BindToEquivalentProperty(this, nameof(LightDismissOverlayBackground)); + _flyout.BindToEquivalentProperty(this, nameof(LightDismissOverlayMode)); + _flyout.BindToEquivalentProperty(this, nameof(LightDismissOverlayBackground)); #endif } } private void BindToFlyout(string propertyName) { - this.Binding(propertyName, propertyName, _flyoutButton.Flyout, BindingMode.TwoWay); + this.Binding(propertyName, propertyName, _flyout, BindingMode.TwoWay); } private void InitializeTextBlocks(IFrameworkElement container) @@ -272,7 +308,7 @@ private void InitializeTextBlocks(IFrameworkElement container) .Select(x => new { Column = x, grid.ColumnDefinitions.ElementAtOrDefault(x)?.Width }) .ToArray(); - // re-position TextBlocks' Grid.Column and their respective ColumnDefinition.Width + // update TextBlocks' Grid.Column and their respective ColumnDefinition.Width foreach (var item in items) { if (grid.ColumnDefinitions.ElementAtOrDefault(oldColumnInfos[item.NewIndex].Column) is ColumnDefinition definition && diff --git a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.Android.cs b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.Android.cs index 8c924cf7edff..cbbdafae433d 100644 --- a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.Android.cs @@ -1,66 +1,75 @@ -#if XAMARIN_ANDROID -using Android.App; -using Java.Util; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using Uno.UI; -using Uno.UI.Common; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; - -namespace Windows.UI.Xaml.Controls -{ - public partial class DatePickerFlyout : PickerFlyoutBase - { - private DatePickerDialog _dialog; - - internal protected override void Open() - { - // Note: Month needs to be -1 since on Android months go from 0-11 - // http://developer.android.com/reference/android/app/DatePickerDialog.OnDateSetListener.html#onDateSet(android.widget.DatePicker, int, int, int) - - _dialog = new DatePickerDialog( - ContextHelper.Current, - OnDateSet, - Date.Year, - Date.Month - 1, - Date.Day - ); - - //Removes title that is unnecessary as it is a duplicate -> http://stackoverflow.com/questions/33486643/remove-title-from-datepickerdialog - _dialog.SetTitle(""); - - var minYearCalendar = Calendar.Instance; - minYearCalendar.Set(MinYear.Year, MinYear.Month - 1, MinYear.Day, MinYear.Hour, MinYear.Minute, MinYear.Second); - _dialog.DatePicker.MinDate = minYearCalendar.TimeInMillis; - - var maxYearCalendar = Calendar.Instance; - maxYearCalendar.Set(MaxYear.Year, MaxYear.Month - 1, MaxYear.Day, MaxYear.Hour, MaxYear.Minute, MaxYear.Second); - _dialog.DatePicker.MaxDate = maxYearCalendar.TimeInMillis; - - _dialog.DismissEvent += OnDismiss; - _dialog.Show(); - } - - private void OnDismiss(object sender, EventArgs e) - { - Hide(canCancel: false); - } - - internal protected override void Close() - { - _dialog?.Dismiss(); - } - - private void OnDateSet(object sender, DatePickerDialog.DateSetEventArgs e) - { -#pragma warning disable CS0618 // Type or member is obsolete - Date = new DateTimeOffset(e.Year, e.MonthOfYear + 1, e.DayOfMonth, Date.Hour, Date.Minute, Date.Second, Date.Millisecond, Date.Offset); -#pragma warning restore CS0618 // Type or member is obsolete - } - } -} - -#endif +#if XAMARIN_ANDROID +using Android.App; +using Java.Util; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Uno.UI; +using Uno.UI.Common; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; + +namespace Windows.UI.Xaml.Controls +{ + public partial class DatePickerFlyout : PickerFlyoutBase + { + public event EventHandler DatePicked; + + private DatePickerDialog _dialog; + + internal protected override void Open() + { + // Note: Month needs to be -1 since on Android months go from 0-11 + // http://developer.android.com/reference/android/app/DatePickerDialog.OnDateSetListener.html#onDateSet(android.widget.DatePicker, int, int, int) + + _dialog = new DatePickerDialog( + ContextHelper.Current, + OnDateSet, + Date.Year, + Date.Month - 1, + Date.Day + ); + + //Removes title that is unnecessary as it is a duplicate -> http://stackoverflow.com/questions/33486643/remove-title-from-datepickerdialog + _dialog.SetTitle(""); + + var minYearCalendar = Calendar.Instance; + minYearCalendar.Set(MinYear.Year, MinYear.Month - 1, MinYear.Day, MinYear.Hour, MinYear.Minute, MinYear.Second); + _dialog.DatePicker.MinDate = minYearCalendar.TimeInMillis; + + var maxYearCalendar = Calendar.Instance; + maxYearCalendar.Set(MaxYear.Year, MaxYear.Month - 1, MaxYear.Day, MaxYear.Hour, MaxYear.Minute, MaxYear.Second); + _dialog.DatePicker.MaxDate = maxYearCalendar.TimeInMillis; + + _dialog.DismissEvent += OnDismiss; + _dialog.Show(); + } + + partial void OnDateChangedPartialNative(DateTimeOffset oldDate, DateTimeOffset newDate) + { + _dialog?.UpdateDate(newDate.Date); + } + + private void OnDismiss(object sender, EventArgs e) + { + Hide(canCancel: false); + } + + internal protected override void Close() + { + _dialog?.Dismiss(); + } + + private void OnDateSet(object sender, DatePickerDialog.DateSetEventArgs e) + { + var newValue = new DateTimeOffset(e.Date + Date.TimeOfDay, Date.Offset); + var oldValue = Date; + + Date = newValue; + DatePicked?.Invoke(this, new DatePickedEventArgs(newValue, oldValue)); + } + } +} + +#endif diff --git a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.iOS.cs index c026e0c1ce1d..c9368a0a1d6b 100644 --- a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerFlyout.iOS.cs @@ -23,6 +23,8 @@ public partial class DatePickerFlyout : PickerFlyoutBase public const string DismissButtonPartName = "DismissButton"; #endregion + public event EventHandler DatePicked; + private readonly SerialDisposable _presenterLoadedDisposable = new SerialDisposable(); private readonly SerialDisposable _presenterUnloadedDisposable = new SerialDisposable(); private bool _isInitialized; @@ -133,12 +135,15 @@ private static void OnContentChanged(object dependencyObject, DependencyProperty private void DatePickerFlyout_Opening(object sender, EventArgs e) { InitializeContent(); + } + partial void OnDateChangedPartialNative(DateTimeOffset oldDate, DateTimeOffset newDate) + { // The date coerced by UIDatePicker doesn't propagate back to DatePickerSelector (#137137) // When the user selected an invalid date, a `ValueChanged` will be raised after coercion to propagate the coerced date. // However, when the `Date` is set below, there no `ValueChanged` to propagate the coerced date. // To address this, we clamp the date between the valid range. - var validDate = Date; + var validDate = newDate; validDate = validDate > MaxYear ? MaxYear : validDate; validDate = validDate < MinYear ? MinYear : validDate; @@ -166,8 +171,9 @@ private void DatePickerFlyout_Closed(object sender, EventArgs e) private void Accept() { _selector.SaveValue(); - Date = _selector.Date; Hide(false); + + DatePicked?.Invoke(this, new DatePickedEventArgs(_selector.Date, Date)); } private void Dismiss() diff --git a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerSelectedValueChangedEventArgs.cs b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerSelectedValueChangedEventArgs.cs new file mode 100644 index 000000000000..918515cbe359 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerSelectedValueChangedEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Windows.UI.Xaml.Controls +{ + public sealed partial class DatePickerSelectedValueChangedEventArgs + { + public DatePickerSelectedValueChangedEventArgs(DateTimeOffset? newDate, DateTimeOffset? oldDate) + { + NewDate = newDate; + OldDate = oldDate; + } + + public DateTimeOffset? NewDate { get; } + public DateTimeOffset? OldDate { get; } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerSelector.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerSelector.iOS.cs index ee00e52d8a63..3f67bf7b3b83 100644 --- a/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerSelector.iOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/DatePicker/DatePickerSelector.iOS.cs @@ -61,7 +61,6 @@ partial void OnDateChangedPartialNative(DateTimeOffset oldDate, DateTimeOffset n // Animate to cover up the small delay in setting the date when the flyout is opened var animated = !UIDevice.CurrentDevice.CheckSystemVersion(10, 0); - // todo: treat default value (min-value) as today if (newDate < MinYear) { Date = MinYear; @@ -71,6 +70,7 @@ partial void OnDateChangedPartialNative(DateTimeOffset oldDate, DateTimeOffset n Date = MaxYear; } + // todo: replace with UpdatePickerValue _picker?.SetDate( DateTime.SpecifyKind(Date.DateTime, DateTimeKind.Local).ToNSDate(), animated: animated @@ -116,7 +116,6 @@ internal void SaveValue() { if (_newValue != null && _newValue != _initialValue) { - // todo: should allow for today to be returned var value = GetValueFromPicker(); if (Date.Year != value.Year || Date.Month != value.Month || Date.Day != value.Day) // fixme: compare date-only {