Skip to content

Commit

Permalink
TimePicker Handlers (#454)
Browse files Browse the repository at this point in the history
* TimePickerHandlers

* Undo changes in sample

* Add some nullability fixes

* Fix build error

* Changes from PR feedback

* Fixed build error

* Updated tests

* Roll time picker and popup into one control

* Move 24 hour view check to handler

* Pull NSLocale -> CultureInfo code out to common property

Co-authored-by: E.Z. Hart <hartez@users.noreply.github.com>
Co-authored-by: E.Z. Hart <hartez@gmail.com>
  • Loading branch information
3 people committed Mar 17, 2021
1 parent 16ff68a commit b0b16b2
Show file tree
Hide file tree
Showing 23 changed files with 610 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public abstract class TimePickerRendererBase<TControl> : ViewRenderer<TimePicker
AlertDialog _dialog;
bool _disposed;

[PortHandler]
bool Is24HourView
{
get => (DateFormat.Is24HourFormat(Context) && Element.Format == (string)TimePicker.FormatProperty.DefaultValue) || Element.Format?.Contains('H') == true;
Expand Down Expand Up @@ -154,6 +155,7 @@ void OnCancelButtonClicked(object sender, EventArgs e)
Element.Unfocus();
}

[PortHandler]
void SetTime(TimeSpan time)
{
if (String.IsNullOrEmpty(Element.Format))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public TimePickerRenderer()

}

[PortHandler]
protected override UITextField CreateNativeControl()
{
return new NoCaretField { BorderStyle = UITextBorderStyle.RoundedRect };
Expand Down Expand Up @@ -201,6 +202,7 @@ void UpdateCharacterSpacing()
Control.AttributedText = textAttr;
}

[PortHandler]
void UpdateTime()
{
_picker.Date = new DateTime(1, 1, 1).Add(Element.Time).ToNSDate();
Expand Down Expand Up @@ -237,6 +239,7 @@ void UpdateTime()
Element.InvalidateMeasureNonVirtual(Controls.Internals.InvalidationTrigger.MeasureChanged);
}

[PortHandler]
void UpdateElementTime()
{
ElementController.SetValueFromRenderer(TimePicker.TimeProperty, _picker.Date.ToDateTime() - new DateTime(1, 1, 1));
Expand Down
7 changes: 7 additions & 0 deletions src/Controls/src/Core/HandlerImpl/TimePicker.Impl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Maui.Controls
{
public partial class TimePicker : ITimePicker
{

}
}
2 changes: 1 addition & 1 deletion src/Controls/src/Core/TimePicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Microsoft.Maui.Controls
{
public class TimePicker : View, IFontElement, ITextElement, IElementConfiguration<TimePicker>
public partial class TimePicker : View, IFontElement, ITextElement, IElementConfiguration<TimePicker>
{
public static readonly BindableProperty FormatProperty = BindableProperty.Create(nameof(Format), typeof(string), typeof(TimePicker), "t");

Expand Down
10 changes: 10 additions & 0 deletions src/Core/src/Core/ITimePicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Microsoft.Maui
{
public interface ITimePicker : IView
{
string Format { get; }
TimeSpan Time { get; set; }
}
}
84 changes: 84 additions & 0 deletions src/Core/src/Handlers/TimePicker/TimePickerHandler.Android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using Android.App;
using Android.Text.Format;

namespace Microsoft.Maui.Handlers
{
public partial class TimePickerHandler : AbstractViewHandler<ITimePicker, MauiTimePicker>
{
MauiTimePicker? _timePicker;
AlertDialog? _dialog;

protected override MauiTimePicker CreateNativeView()
{
_timePicker = new MauiTimePicker(Context)
{
ShowPicker = ShowPickerDialog,
HidePicker = HidePickerDialog
};

return _timePicker;
}

protected override void DisconnectHandler(MauiTimePicker nativeView)
{
if (_dialog != null)
{
_dialog.Hide();
_dialog = null;
}
}

protected virtual TimePickerDialog CreateTimePickerDialog(int hour, int minute)
{
void onTimeSetCallback(object? obj, TimePickerDialog.TimeSetEventArgs args)
{
if (VirtualView == null || TypedNativeView == null)
return;

VirtualView.Time = new TimeSpan(args.HourOfDay, args.Minute, 0);
}

var dialog = new TimePickerDialog(Context!, onTimeSetCallback, hour, minute, Use24HourView);

return dialog;
}

public static void MapFormat(TimePickerHandler handler, ITimePicker timePicker)
{
handler.TypedNativeView?.UpdateFormat(timePicker);
}

public static void MapTime(TimePickerHandler handler, ITimePicker timePicker)
{
handler.TypedNativeView?.UpdateTime(timePicker);
}

void ShowPickerDialog()
{
if (VirtualView == null)
return;

var time = VirtualView.Time;
ShowPickerDialog(time.Hours, time.Minutes);
}

// This overload is here so we can pass in the current values from the dialog
// on an orientation change (so that orientation changes don't cause the user's date selection progress
// to be lost). Not useful until we have orientation changed events.
void ShowPickerDialog(int hour, int minute)
{
_dialog = CreateTimePickerDialog(hour, minute);
_dialog.Show();
}

void HidePickerDialog()
{
_dialog?.Hide();
_dialog = null;
}

bool Use24HourView => VirtualView != null && (DateFormat.Is24HourFormat(TypedNativeView?.Context)
&& VirtualView.Format == "t" || VirtualView.Format == "HH:mm");
}
}
12 changes: 12 additions & 0 deletions src/Core/src/Handlers/TimePicker/TimePickerHandler.Standard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Microsoft.Maui.Handlers
{
public partial class TimePickerHandler : AbstractViewHandler<ITimePicker, object>
{
protected override object CreateNativeView() => throw new NotImplementedException();

public static void MapFormat(TimePickerHandler handler, ITimePicker view) { }
public static void MapTime(TimePickerHandler handler, ITimePicker view) { }
}
}
21 changes: 21 additions & 0 deletions src/Core/src/Handlers/TimePicker/TimePickerHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Microsoft.Maui.Handlers
{
public partial class TimePickerHandler
{
public static PropertyMapper<ITimePicker, TimePickerHandler> TimePickerMapper = new PropertyMapper<ITimePicker, TimePickerHandler>(ViewHandler.ViewMapper)
{
[nameof(ITimePicker.Format)] = MapFormat,
[nameof(ITimePicker.Time)] = MapTime
};

public TimePickerHandler() : base(TimePickerMapper)
{

}

public TimePickerHandler(PropertyMapper mapper) : base(mapper ?? TimePickerMapper)
{

}
}
}
54 changes: 54 additions & 0 deletions src/Core/src/Handlers/TimePicker/TimePickerHandler.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;

namespace Microsoft.Maui.Handlers
{
public partial class TimePickerHandler : AbstractViewHandler<ITimePicker, MauiTimePicker>
{
protected override MauiTimePicker CreateNativeView()
{
return new MauiTimePicker(() => {
SetVirtualViewTime();
TypedNativeView?.ResignFirstResponder();
});
}

protected override void ConnectHandler(MauiTimePicker nativeView)
{
if (nativeView != null)
nativeView.ValueChanged += OnValueChanged;
}

protected override void DisconnectHandler(MauiTimePicker nativeView)
{
if (nativeView != null)
{
nativeView.RemoveFromSuperview();
nativeView.ValueChanged -= OnValueChanged;
nativeView.Dispose();
}
}

public static void MapFormat(TimePickerHandler handler, ITimePicker timePicker)
{
handler.TypedNativeView?.UpdateFormat(timePicker);
}

public static void MapTime(TimePickerHandler handler, ITimePicker timePicker)
{
handler.TypedNativeView?.UpdateTime(timePicker);
}

void OnValueChanged(object? sender, EventArgs e)
{
SetVirtualViewTime();
}

void SetVirtualViewTime()
{
if (VirtualView == null || TypedNativeView == null)
return;

VirtualView.Time = TypedNativeView.Date.ToDateTime() - new DateTime(1, 1, 1);
}
}
}
3 changes: 2 additions & 1 deletion src/Core/src/Hosting/AppHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public static IAppHostBuilder UseMauiHandlers(this IAppHostBuilder builder)
{ typeof(ILabel), typeof(LabelHandler) },
{ typeof(IProgress), typeof(ProgressBarHandler) },
{ typeof(ISlider), typeof(SliderHandler) },
{ typeof(ISwitch), typeof(SwitchHandler) }
{ typeof(ISwitch), typeof(SwitchHandler) },
{ typeof(ITimePicker), typeof(TimePickerHandler) }
});

return builder;
Expand Down
46 changes: 46 additions & 0 deletions src/Core/src/Platform/Android/MauiTimePicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using Android.Content;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using static Android.Views.View;

namespace Microsoft.Maui
{
public class MauiTimePicker : EditText, IOnClickListener
{
public MauiTimePicker(Context? context) : base(context)
{
Initialize();
}

public MauiTimePicker(Context? context, IAttributeSet attrs) : base(context, attrs)
{
Initialize();
}

public MauiTimePicker(Context? context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
{
Initialize();
}

protected MauiTimePicker(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}

private void Initialize()
{
Focusable = true;
SetOnClickListener(this);
}

public Action? ShowPicker { get; set; }
public Action? HidePicker { get; set; }

public void OnClick(View? v)
{
ShowPicker?.Invoke();
}
}
}
28 changes: 28 additions & 0 deletions src/Core/src/Platform/Android/TimeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;

namespace Microsoft.Maui
{
public static class TimeExtensions
{
public static string ToFormattedString(this ITimePicker timePicker)
{
var time = timePicker.Time;
var format = timePicker.Format;

return time.ToFormattedString(format);
}

public static string ToFormattedString(this TimeSpan time, string format)
{
if (string.IsNullOrEmpty(format))
{
var timeFormat = "t";
return DateTime.Today.Add(time).ToString(timeFormat);
}
else
{
return DateTime.Today.Add(time).ToString(format);
}
}
}
}
23 changes: 23 additions & 0 deletions src/Core/src/Platform/Android/TimePickerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Microsoft.Maui
{
public static class TimePickerExtensions
{
public static void UpdateFormat(this MauiTimePicker mauiTimePicker, ITimePicker view)
{
mauiTimePicker.SetTime(view);
}

public static void UpdateTime(this MauiTimePicker mauiTimePicker, ITimePicker view)
{
mauiTimePicker.SetTime(view);
}

internal static void SetTime(this MauiTimePicker mauiTimePicker, ITimePicker timePicker)
{
var time = timePicker.Time;
var format = timePicker.Format;

mauiTimePicker.Text = time.ToFormattedString(format);
}
}
}
20 changes: 20 additions & 0 deletions src/Core/src/Platform/MaciOS/DateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using Foundation;

namespace Microsoft.Maui
{
public static class DateExtensions
{
internal static DateTime ReferenceDate = new DateTime(2001, 1, 1, 0, 0, 0);

public static DateTime ToDateTime(this NSDate date)
{
return ReferenceDate.AddSeconds(date.SecondsSinceReferenceDate);
}

public static NSDate ToNSDate(this DateTime date)
{
return NSDate.FromTimeIntervalSinceReferenceDate((date - ReferenceDate).TotalSeconds);
}
}
}
Loading

0 comments on commit b0b16b2

Please sign in to comment.