From 5e44a5ba606f0a04b36d478a1952e949a8685c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Fri, 5 Mar 2021 13:18:48 +0100 Subject: [PATCH 01/12] PickerHandlers --- .../src/Android/AppCompat/PickerRenderer.cs | 2 + .../Core/src/iOS/Renderers/PickerRenderer.cs | 7 + .../samples/Controls.Sample/Pages/MainPage.cs | 19 ++ .../src/Core/HandlerImpl/Picker.Impl.cs | 7 + src/Controls/src/Core/Picker.cs | 2 +- src/Core/src/Core/IPicker.cs | 14 ++ .../Handlers/Picker/PickerHandler.Android.cs | 114 +++++++++ .../Handlers/Picker/PickerHandler.Standard.cs | 12 + src/Core/src/Handlers/Picker/PickerHandler.cs | 21 ++ .../src/Handlers/Picker/PickerHandler.iOS.cs | 223 ++++++++++++++++++ .../src/Hosting/AppHostBuilderExtensions.cs | 1 + src/Core/src/Platform/Android/NativePicker.cs | 52 ++++ .../src/Platform/Android/PickerExtensions.cs | 46 ++++ .../src/Platform/Android/PickerManager.cs | 66 ++++++ .../src/Platform/Standard/PickerExtensions.cs | 15 ++ src/Core/src/Platform/iOS/NativePicker.cs | 41 ++++ src/Core/src/Platform/iOS/PickerExtensions.cs | 75 ++++++ .../Picker/PickerHandlerTests.Android.cs | 13 + .../Handlers/Picker/PickerHandlerTests.cs | 25 ++ .../Handlers/Picker/PickerHandlerTests.iOS.cs | 40 ++++ .../tests/DeviceTests/Stubs/PickerStub.cs | 18 ++ 21 files changed, 812 insertions(+), 1 deletion(-) create mode 100644 src/Controls/src/Core/HandlerImpl/Picker.Impl.cs create mode 100644 src/Core/src/Core/IPicker.cs create mode 100644 src/Core/src/Handlers/Picker/PickerHandler.Android.cs create mode 100644 src/Core/src/Handlers/Picker/PickerHandler.Standard.cs create mode 100644 src/Core/src/Handlers/Picker/PickerHandler.cs create mode 100644 src/Core/src/Handlers/Picker/PickerHandler.iOS.cs create mode 100644 src/Core/src/Platform/Android/NativePicker.cs create mode 100644 src/Core/src/Platform/Android/PickerExtensions.cs create mode 100644 src/Core/src/Platform/Android/PickerManager.cs create mode 100644 src/Core/src/Platform/Standard/PickerExtensions.cs create mode 100644 src/Core/src/Platform/iOS/NativePicker.cs create mode 100644 src/Core/src/Platform/iOS/PickerExtensions.cs create mode 100644 src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs create mode 100644 src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs create mode 100644 src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs create mode 100644 src/Core/tests/DeviceTests/Stubs/PickerStub.cs diff --git a/src/Compatibility/Core/src/Android/AppCompat/PickerRenderer.cs b/src/Compatibility/Core/src/Android/AppCompat/PickerRenderer.cs index ae7d4ce55c72..88cf7de238b1 100644 --- a/src/Compatibility/Core/src/Android/AppCompat/PickerRenderer.cs +++ b/src/Compatibility/Core/src/Android/AppCompat/PickerRenderer.cs @@ -109,6 +109,7 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu } } + [PortHandler("Partially ported, still missing code related to TitleColor, etc.")] void IPickerRenderer.OnClick() { Picker model = Element; @@ -168,6 +169,7 @@ protected void UpdateCharacterSpacing() } } + [PortHandler("Partially ported, still missing code related to TitleColor, etc.")] void UpdatePicker() { UpdatePlaceHolderText(); diff --git a/src/Compatibility/Core/src/iOS/Renderers/PickerRenderer.cs b/src/Compatibility/Core/src/iOS/Renderers/PickerRenderer.cs index cf16f9f2a0c7..4d75e7467778 100644 --- a/src/Compatibility/Core/src/iOS/Renderers/PickerRenderer.cs +++ b/src/Compatibility/Core/src/iOS/Renderers/PickerRenderer.cs @@ -11,6 +11,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS { + [PortHandler] internal class ReadOnlyField : NoCaretField { readonly HashSet enableActions; @@ -32,6 +33,7 @@ public PickerRenderer() } + [PortHandler] protected override UITextField CreateNativeControl() { return new ReadOnlyField { BorderStyle = UITextBorderStyle.RoundedRect }; @@ -56,6 +58,8 @@ public PickerRendererBase() } protected abstract override TControl CreateNativeControl(); + + [PortHandler("Partially ported, still missing code related to TitleColor, etc.")] protected override void OnElementChanged(ElementChangedEventArgs e) { if (e.OldElement != null) @@ -231,6 +235,7 @@ protected internal virtual void UpdatePlaceholder() protected virtual void UpdateAttributedPlaceholder(NSAttributedString nsAttributedString) => Control.AttributedPlaceholder = nsAttributedString; + [PortHandler] void UpdatePicker() { var selectedIndex = Element.SelectedIndex; @@ -266,6 +271,7 @@ void UpdatePickerNativeSize(string oldText) ((IVisualElementController)Element).NativeSizeChanged(); } + [PortHandler] void UpdatePickerSelectedIndex(int formsIndex) { var source = (PickerSource)_picker.Model; @@ -334,6 +340,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + [PortHandler] class PickerSource : UIPickerViewModel { PickerRendererBase _renderer; diff --git a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs index e4765150b4b0..fc02ad767d46 100644 --- a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs +++ b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using Maui.Controls.Sample.ViewModel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui; @@ -113,6 +115,23 @@ void SetupMauiLayout() placeholderSearchBar.Placeholder = "Placeholder"; verticalStack.Add(placeholderSearchBar); + + var monkeyList = new List + { + "Baboon", + "Capuchin Monkey", + "Blue Monkey", + "Squirrel Monkey", + "Golden Lion Tamarin", + "Howler Monkey", + "Japanese Macaque" + }; + + var picker = new Picker { Title = "Select a monkey" }; + + picker.ItemsSource = monkeyList; + verticalStack.Add(picker); + verticalStack.Add(new Slider()); verticalStack.Add(new Stepper()); diff --git a/src/Controls/src/Core/HandlerImpl/Picker.Impl.cs b/src/Controls/src/Core/HandlerImpl/Picker.Impl.cs new file mode 100644 index 000000000000..b98e185c368d --- /dev/null +++ b/src/Controls/src/Core/HandlerImpl/Picker.Impl.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Maui.Controls +{ + public partial class Picker : IPicker + { + + } +} \ No newline at end of file diff --git a/src/Controls/src/Core/Picker.cs b/src/Controls/src/Core/Picker.cs index a946602fab61..c50dedbf1a2b 100644 --- a/src/Controls/src/Core/Picker.cs +++ b/src/Controls/src/Core/Picker.cs @@ -9,7 +9,7 @@ namespace Microsoft.Maui.Controls { - public class Picker : View, IFontElement, ITextElement, ITextAlignmentElement, IElementConfiguration + public partial class Picker : View, IFontElement, ITextElement, ITextAlignmentElement, IElementConfiguration { public static readonly BindableProperty TextColorProperty = TextElement.TextColorProperty; diff --git a/src/Core/src/Core/IPicker.cs b/src/Core/src/Core/IPicker.cs new file mode 100644 index 000000000000..51552689368f --- /dev/null +++ b/src/Core/src/Core/IPicker.cs @@ -0,0 +1,14 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Maui +{ + public interface IPicker : IView + { + string Title { get; } + IList Items { get; } + IList ItemsSource { get; } + int SelectedIndex { get; set; } + object? SelectedItem { get; set; } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs new file mode 100644 index 000000000000..6293a682426b --- /dev/null +++ b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Specialized; +using System.Linq; +using Android.App; +using Android.Text; +using Android.Text.Style; +using AResource = Android.Resource; + +namespace Microsoft.Maui.Handlers +{ + public partial class PickerHandler : AbstractViewHandler + { + AlertDialog? _dialog; + + protected override NativePicker CreateNativeView() => + new NativePicker(Context); + + protected override void ConnectHandler(NativePicker nativeView) + { + nativeView.FocusChange += OnFocusChange; + nativeView.Click += OnClick; + + if (VirtualView != null && VirtualView.Items is INotifyCollectionChanged notifyCollection) + notifyCollection.CollectionChanged += OnCollectionChanged; + + base.ConnectHandler(nativeView); + } + + protected override void DisconnectHandler(NativePicker nativeView) + { + nativeView.FocusChange -= OnFocusChange; + nativeView.Click -= OnClick; + + if (VirtualView != null && VirtualView.Items is INotifyCollectionChanged notifyCollection) + notifyCollection.CollectionChanged -= OnCollectionChanged; + + base.DisconnectHandler(nativeView); + } + public static void MapTitle(PickerHandler handler, IPicker picker) + { + handler.TypedNativeView?.UpdateTitle(picker); + } + + public static void MapSelectedIndex(PickerHandler handler, IPicker picker) + { + handler.TypedNativeView?.UpdateSelectedIndex(picker); + } + + void OnFocusChange(object? sender, global::Android.Views.View.FocusChangeEventArgs e) + { + if (TypedNativeView == null) + return; + + if (e.HasFocus) + { + if (TypedNativeView.Clickable) + TypedNativeView.CallOnClick(); + else + OnClick(TypedNativeView, EventArgs.Empty); + } + else if (_dialog != null) + { + _dialog.Hide(); + TypedNativeView.ClearFocus(); + _dialog = null; + } + } + + void OnClick(object? sender, EventArgs e) + { + if (VirtualView != null && _dialog == null) + { + using (var builder = new AlertDialog.Builder(Context)) + { + builder.SetTitle(VirtualView.Title ?? string.Empty); + + string[] items = VirtualView.Items.ToArray(); + + builder.SetItems(items, (s, e) => + { + var selectedIndex = e.Which; + VirtualView.SelectedIndex = selectedIndex; + TypedNativeView?.UpdatePicker(VirtualView); + }); + + builder.SetNegativeButton(AResource.String.Cancel, (o, args) => { }); + + _dialog = builder.Create(); + } + + if (_dialog == null) + return; + + _dialog.SetCanceledOnTouchOutside(true); + + _dialog.DismissEvent += (sender, args) => + { + _dialog.Dispose(); + _dialog = null; + }; + + _dialog.Show(); + } + } + + void OnCollectionChanged(object sender, EventArgs e) + { + if (VirtualView == null || TypedNativeView == null) + return; + + TypedNativeView.UpdatePicker(VirtualView); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Standard.cs b/src/Core/src/Handlers/Picker/PickerHandler.Standard.cs new file mode 100644 index 000000000000..77a0294c3787 --- /dev/null +++ b/src/Core/src/Handlers/Picker/PickerHandler.Standard.cs @@ -0,0 +1,12 @@ +using System; + +namespace Microsoft.Maui.Handlers +{ + public partial class PickerHandler : AbstractViewHandler + { + protected override object CreateNativeView() => throw new NotImplementedException(); + + public static void MapTitle(PickerHandler handler, IPicker view) { } + public static void MapSelectedIndex(PickerHandler handler, IPicker view) { } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/Picker/PickerHandler.cs b/src/Core/src/Handlers/Picker/PickerHandler.cs new file mode 100644 index 000000000000..2504f4a30c28 --- /dev/null +++ b/src/Core/src/Handlers/Picker/PickerHandler.cs @@ -0,0 +1,21 @@ +namespace Microsoft.Maui.Handlers +{ + public partial class PickerHandler + { + public static PropertyMapper PickerMapper = new PropertyMapper(ViewHandler.ViewMapper) + { + [nameof(IPicker.Title)] = MapTitle, + [nameof(IPicker.SelectedIndex)] = MapSelectedIndex + }; + + public PickerHandler() : base(PickerMapper) + { + + } + + public PickerHandler(PropertyMapper mapper) : base(mapper) + { + + } + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs new file mode 100644 index 000000000000..5c204b9e57b6 --- /dev/null +++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Specialized; +using UIKit; +using RectangleF = CoreGraphics.CGRect; + +namespace Microsoft.Maui.Handlers +{ + public partial class PickerHandler : AbstractViewHandler + { + UIPickerView? _pickerView; + + protected override NativePicker CreateNativeView() + { + _pickerView = new UIPickerView(); + + var nativePicker = new NativePicker(_pickerView) { BorderStyle = UITextBorderStyle.RoundedRect }; + + var width = UIScreen.MainScreen.Bounds.Width; + var toolbar = new UIToolbar(new RectangleF(0, 0, width, 44)) { BarStyle = UIBarStyle.Default, Translucent = true }; + var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace); + + var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) => + { + var pickerSource = (PickerSource)_pickerView.Model; + + if (pickerSource.SelectedIndex == -1 && VirtualView?.Items != null && VirtualView.Items.Count > 0) + UpdatePickerSelectedIndex(0); + + if (VirtualView?.SelectedIndex == -1 && VirtualView.Items != null && VirtualView.Items.Count > 0) + { + TypedNativeView?.SetSelectedIndex(VirtualView, 0); + } + + UpdatePickerFromPickerSource(pickerSource); + nativePicker.ResignFirstResponder(); + }); + + toolbar.SetItems(new[] { spacer, doneButton }, false); + + nativePicker.InputView = _pickerView; + nativePicker.InputAccessoryView = toolbar; + + nativePicker.InputView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight; + nativePicker.InputAccessoryView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight; + + if (UIDevice.CurrentDevice.CheckSystemVersion(9, 0)) + { + nativePicker.InputAssistantItem.LeadingBarButtonGroups = null; + nativePicker.InputAssistantItem.TrailingBarButtonGroups = null; + } + + nativePicker.AccessibilityTraits = UIAccessibilityTrait.Button; + + _pickerView.Model = new PickerSource(VirtualView); + + return nativePicker; + } + + protected override void ConnectHandler(NativePicker nativeView) + { + nativeView.EditingDidBegin += OnStarted; + nativeView.EditingDidEnd += OnEnded; + nativeView.EditingChanged += OnEditing; + + if (VirtualView != null) + ((INotifyCollectionChanged)VirtualView.Items).CollectionChanged += OnCollectionChanged; + + base.ConnectHandler(nativeView); + } + + protected override void DisconnectHandler(NativePicker nativeView) + { + nativeView.EditingDidBegin -= OnStarted; + nativeView.EditingDidEnd -= OnEnded; + nativeView.EditingChanged -= OnEditing; + + if (VirtualView != null) + ((INotifyCollectionChanged) VirtualView.Items).CollectionChanged -= OnCollectionChanged; + + if (_pickerView != null) + { + if (_pickerView.Model != null) + { + _pickerView.Model.Dispose(); + _pickerView.Model = null; + } + + _pickerView.RemoveFromSuperview(); + _pickerView.Dispose(); + _pickerView = null; + } + + base.DisconnectHandler(nativeView); + } + + public static void MapTitle(PickerHandler handler, IPicker picker) + { + handler.TypedNativeView?.UpdateTitle(picker); + } + + public static void MapSelectedIndex(PickerHandler handler, IPicker picker) + { + handler.TypedNativeView?.UpdateSelectedIndex(picker); + } + + void OnCollectionChanged(object sender, EventArgs e) + { + if (VirtualView == null || TypedNativeView == null) + return; + + TypedNativeView.UpdatePicker(VirtualView); + } + + void OnStarted(object? sender, EventArgs eventArgs) + { + + } + + void OnEnded(object? sender, EventArgs eventArgs) + { + if (_pickerView == null) + return; + + PickerSource? model = (PickerSource)_pickerView.Model; + + if (model.SelectedIndex != -1 && model.SelectedIndex != _pickerView.SelectedRowInComponent(0)) + { + _pickerView.Select(model.SelectedIndex, 0, false); + } + } + + void OnEditing(object? sender, EventArgs eventArgs) + { + if (VirtualView == null || TypedNativeView == null) + return; + + // Reset the TextField's Text so it appears as if typing with a keyboard does not work. + var selectedIndex = VirtualView.SelectedIndex; + var items = VirtualView.Items; + TypedNativeView.Text = selectedIndex == -1 || items == null ? string.Empty : items[selectedIndex]; + + // Also clears the undo stack (undo/redo possible on iPads) + TypedNativeView.UndoManager.RemoveAllActions(); + } + + void UpdatePickerFromPickerSource(PickerSource pickerSource) + { + if (VirtualView == null || TypedNativeView == null) + return; + + TypedNativeView.Text = pickerSource.SelectedItem; + VirtualView.SelectedIndex = pickerSource.SelectedIndex; + } + + void UpdatePickerSelectedIndex(int formsIndex) + { + if (VirtualView == null || _pickerView == null) + return; + + var source = (PickerSource)_pickerView.Model; + source.SelectedIndex = formsIndex; + source.SelectedItem = formsIndex >= 0 ? VirtualView.Items[formsIndex] : null; + _pickerView.Select(Math.Max(formsIndex, 0), 0, true); + } + } + + public class PickerSource : UIPickerViewModel + { + IPicker? _virtualView; + bool _disposed; + + public PickerSource(IPicker? virtualView) + { + _virtualView = virtualView; + } + + public int SelectedIndex { get; internal set; } + + public string? SelectedItem { get; internal set; } + + public override nint GetComponentCount(UIPickerView picker) + { + return 1; + } + + public override nint GetRowsInComponent(UIPickerView pickerView, nint component) + { + return _virtualView?.Items != null ? _virtualView.Items.Count : 0; + } + + public override string GetTitle(UIPickerView picker, nint row, nint component) + { + return _virtualView != null ? _virtualView.Items[(int)row] : string.Empty; + } + + public override void Selected(UIPickerView picker, nint row, nint component) + { + if (_virtualView?.Items.Count == 0) + { + SelectedItem = null; + SelectedIndex = -1; + } + else + { + SelectedItem = _virtualView?.Items[(int)row]; + SelectedIndex = (int)row; + } + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + _virtualView = null; + + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Hosting/AppHostBuilderExtensions.cs b/src/Core/src/Hosting/AppHostBuilderExtensions.cs index b4868bbaa4f2..80048436d7d6 100644 --- a/src/Core/src/Hosting/AppHostBuilderExtensions.cs +++ b/src/Core/src/Hosting/AppHostBuilderExtensions.cs @@ -42,6 +42,7 @@ public static IAppHostBuilder UseMauiHandlers(this IAppHostBuilder builder) { typeof(IEntry), typeof(EntryHandler) }, { typeof(ILayout), typeof(LayoutHandler) }, { typeof(ILabel), typeof(LabelHandler) }, + { typeof(IPicker), typeof(PickerHandler) }, { typeof(IProgress), typeof(ProgressBarHandler) }, { typeof(ISlider), typeof(SliderHandler) }, { typeof(IStepper), typeof(StepperHandler) }, diff --git a/src/Core/src/Platform/Android/NativePicker.cs b/src/Core/src/Platform/Android/NativePicker.cs new file mode 100644 index 000000000000..fefff71a106b --- /dev/null +++ b/src/Core/src/Platform/Android/NativePicker.cs @@ -0,0 +1,52 @@ +using Android.Content; +using Android.Views; +using Android.Widget; +using Android.Runtime; +using ARect = Android.Graphics.Rect; + +#if __ANDROID_29__ +using AndroidX.Core.Graphics.Drawable; +#else +using Android.Support.V4.Graphics.Drawable; +#endif + +namespace Microsoft.Maui +{ + public class NativePicker : NativePickerBase + { + public bool ShowPopupOnFocus { get; set; } + + public NativePicker(Context? context) : base(context) + { + PickerManager.Init(this); + } + + public override bool OnTouchEvent(MotionEvent? e) + { + PickerManager.OnTouchEvent(this, e); + return base.OnTouchEvent(e); // Raises the OnClick event if focus is already received + } + + protected override void OnFocusChanged(bool gainFocus, [GeneratedEnum] FocusSearchDirection direction, ARect? previouslyFocusedRect) + { + base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect); + PickerManager.OnFocusChanged(gainFocus, this); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + PickerManager.Dispose(this); + + base.Dispose(disposing); + } + } + + public class NativePickerBase : EditText + { + public NativePickerBase(Context? context) : base(context) + { + DrawableCompat.Wrap(Background); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Android/PickerExtensions.cs b/src/Core/src/Platform/Android/PickerExtensions.cs new file mode 100644 index 000000000000..e710c4b34f0f --- /dev/null +++ b/src/Core/src/Platform/Android/PickerExtensions.cs @@ -0,0 +1,46 @@ +namespace Microsoft.Maui +{ + public static class PickerExtensions + { + public static void UpdateTitle(this NativePicker nativePicker, IPicker picker) => + UpdatePicker(nativePicker, picker); + + public static void UpdateSelectedIndex(this NativePicker nativePicker, IPicker picker) => + UpdatePicker(nativePicker, picker); + + internal static void UpdatePicker(this NativePicker nativePicker, IPicker picker) + { + nativePicker.Hint = picker.Title; + + if (picker.SelectedIndex == -1 || picker.Items == null || picker.SelectedIndex >= picker.Items.Count) + nativePicker.Text = null; + else + nativePicker.Text = picker.Items[picker.SelectedIndex]; + + nativePicker.SetSelectedItem(picker); + } + + internal static void SetSelectedItem(this NativePicker nativePicker, IPicker picker) + { + if (picker == null || nativePicker == null) + return; + + int index = picker.SelectedIndex; + + if (index == -1) + { + picker.SelectedItem = null; + return; + } + + if (picker.ItemsSource != null) + { + picker.SelectedItem = picker.ItemsSource[index]; + return; + } + + if (picker.Items != null) + picker.SelectedItem = picker.Items[index]; + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Android/PickerManager.cs b/src/Core/src/Platform/Android/PickerManager.cs new file mode 100644 index 000000000000..e1e40f32607c --- /dev/null +++ b/src/Core/src/Platform/Android/PickerManager.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Android.Text; +using Android.Text.Style; +using Android.Views; +using Android.Widget; +using AView = Android.Views.View; + +namespace Microsoft.Maui +{ + internal static class PickerManager + { + readonly static HashSet AvailableKeys = new HashSet(new[] { + Keycode.Tab, Keycode.Forward, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp + }); + + public static void Init(EditText editText) + { + editText.Focusable = true; + editText.Clickable = true; + editText.InputType = InputTypes.Null; + + editText.KeyPress += OnKeyPress; + } + + public static void OnTouchEvent(EditText sender, MotionEvent? e) + { + if (e != null && e.Action == MotionEventActions.Up && !sender.IsFocused) + { + sender.RequestFocus(); + } + } + + public static void OnFocusChanged(bool gainFocus, EditText sender) + { + if (gainFocus) + sender.CallOnClick(); + } + + static void OnKeyPress(object sender, AView.KeyEventArgs e) + { + if (!AvailableKeys.Contains(e.KeyCode)) + { + e.Handled = false; + return; + } + e.Handled = true; + (sender as AView)?.CallOnClick(); + } + + public static void Dispose(EditText editText) + { + editText.KeyPress -= OnKeyPress; + editText.SetOnClickListener(null); + } + + public static Java.Lang.ICharSequence GetTitle(Color titleColor, string title) + { + if (titleColor == Color.Default) + return new Java.Lang.String(title); + + var spannableTitle = new SpannableString(title ?? string.Empty); + spannableTitle.SetSpan(new ForegroundColorSpan(titleColor.ToNative()), 0, spannableTitle.Length(), SpanTypes.ExclusiveExclusive); + return spannableTitle; + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Standard/PickerExtensions.cs b/src/Core/src/Platform/Standard/PickerExtensions.cs new file mode 100644 index 000000000000..753779c1a179 --- /dev/null +++ b/src/Core/src/Platform/Standard/PickerExtensions.cs @@ -0,0 +1,15 @@ +namespace Microsoft.Maui +{ + public static class PickerExtensions + { + public static void UpdateTitle(this object nothing, IPicker picker) + { + + } + + public static void UpdateSelectedIndex(this object nothing, IPicker picker) + { + + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/NativePicker.cs b/src/Core/src/Platform/iOS/NativePicker.cs new file mode 100644 index 000000000000..6730b8e3aa29 --- /dev/null +++ b/src/Core/src/Platform/iOS/NativePicker.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Foundation; +using UIKit; +using ObjCRuntime; +using RectangleF = CoreGraphics.CGRect; + +namespace Microsoft.Maui +{ + public class NativePicker : NoCaretField + { + readonly HashSet _enableActions; + + public NativePicker(UIPickerView? uIPickerView) + { + UIPickerView = uIPickerView; + + string[] actions = { "copy:", "select:", "selectAll:" }; + _enableActions = new HashSet(actions); + } + + public UIPickerView? UIPickerView { get; set; } + + public override bool CanPerform(Selector action, NSObject? withSender) + => _enableActions.Contains(action.Name); + } + + public class NoCaretField : UITextField + { + public NoCaretField() : base(new RectangleF()) + { + SpellCheckingType = UITextSpellCheckingType.No; + AutocorrectionType = UITextAutocorrectionType.No; + AutocapitalizationType = UITextAutocapitalizationType.None; + } + + public override RectangleF GetCaretRectForPosition(UITextPosition? position) + { + return new RectangleF(); + } + } +} diff --git a/src/Core/src/Platform/iOS/PickerExtensions.cs b/src/Core/src/Platform/iOS/PickerExtensions.cs new file mode 100644 index 000000000000..df7259b8362c --- /dev/null +++ b/src/Core/src/Platform/iOS/PickerExtensions.cs @@ -0,0 +1,75 @@ +using System; +using Foundation; +using UIKit; +using Microsoft.Maui.Handlers; + +namespace Microsoft.Maui +{ + public static class PickerExtensions + { + public static void UpdateTitle(this NativePicker nativePicker, IPicker picker) => + nativePicker.UpdatePicker(picker); + + public static void UpdateSelectedIndex(this NativePicker nativePicker, IPicker picker) => + nativePicker.SetSelectedIndex(picker, picker.SelectedIndex); + + internal static void UpdatePicker(this NativePicker nativePicker, IPicker picker) + { + var selectedIndex = picker.SelectedIndex; + var items = picker.Items; + + nativePicker.Text = selectedIndex == -1 || items == null || selectedIndex >= items.Count ? string.Empty : items[selectedIndex]; + + var pickerView = nativePicker.UIPickerView; + pickerView?.ReloadAllComponents(); + + if (items == null || items.Count == 0) + return; + + nativePicker.SetSelectedIndex(picker, selectedIndex); + nativePicker.SetSelectedItem(picker); + } + + internal static void SetSelectedIndex(this NativePicker nativePicker, IPicker picker, int selectedIndex = 0) + { + picker.SelectedIndex = selectedIndex; + + var pickerView = nativePicker.UIPickerView; + + if (pickerView?.Model is PickerSource source) + { + source.SelectedIndex = selectedIndex; + source.SelectedItem = selectedIndex >= 0 ? picker.Items[selectedIndex] : null; + } + + pickerView?.Select(Math.Max(selectedIndex, 0), 0, true); + } + + internal static void SetSelectedItem(this NativePicker nativePicker, IPicker picker) + { + if (nativePicker == null) + return; + + int index = picker.SelectedIndex; + + if (index == -1) + { + picker.SelectedItem = null; + return; + } + + if (picker.ItemsSource != null) + { + picker.SelectedItem = picker.ItemsSource[index]; + return; + } + + picker.SelectedItem = picker.Items[index]; + } + + internal static void UpdateAttributedPlaceholder(this NativePicker nativePicker, NSAttributedString nsAttributedString) + { + nativePicker.AttributedPlaceholder = nsAttributedString; + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs new file mode 100644 index 000000000000..d44b1a681b6f --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs @@ -0,0 +1,13 @@ +using Microsoft.Maui.Handlers; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class PickerHandlerTests + { + NativePicker GetNativePicker(PickerHandler pickerHandler) => + (NativePicker)pickerHandler.View; + + string GetNativeTitle(PickerHandler pickerHandler) => + GetNativePicker(pickerHandler).Hint; + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs new file mode 100644 index 000000000000..72a880c75b94 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.Handlers; + +namespace Microsoft.Maui.DeviceTests +{ + [Category("PickerHandler")] + public partial class PickerHandlerTests : HandlerTestBase + { + public PickerHandlerTests(HandlerTestFixture fixture) : base(fixture) + { + } + + [Fact(DisplayName = "[PickerHandler] Title Initializes Correctly")] + public async Task TitleInitializesCorrectly() + { + var picker = new PickerStub + { + Title = "Select an Item" + }; + + await ValidatePropertyInitValue(picker, () => picker.Title, GetNativeTitle, picker.Title); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs new file mode 100644 index 000000000000..1db684bcc1a2 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using Microsoft.Maui.Handlers; +using Xunit; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class PickerHandlerTests + { + NativePicker GetNativePicker(PickerHandler pickerHandler) => + (NativePicker)pickerHandler.View; + + string GetNativeTitle(PickerHandler pickerHandler) => + GetNativePicker(pickerHandler).Text; + + string GetNativeText(PickerHandler pickerHandler) => + GetNativePicker(pickerHandler).Text; + + async Task ValidateNativeItemsSource(IPicker picker, int itemsCount) + { + var expected = await GetValueAsync(picker, handler => + { + var pickerView = GetNativePicker(handler).UIPickerView; + var model = (PickerSource)pickerView.Model; + return model.GetRowsInComponent(pickerView, 0); + }); + Assert.Equal(expected, itemsCount); + } + + async Task ValidateNativeSelectedIndex(IPicker slider, int selectedIndex) + { + var expected = await GetValueAsync(slider, handler => + { + var pickerView = GetNativePicker(handler).UIPickerView; + var model = (PickerSource)pickerView.Model; + return model.SelectedIndex; + }); + Assert.Equal(expected, selectedIndex); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Stubs/PickerStub.cs b/src/Core/tests/DeviceTests/Stubs/PickerStub.cs new file mode 100644 index 000000000000..d8dccb5e9b6f --- /dev/null +++ b/src/Core/tests/DeviceTests/Stubs/PickerStub.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Maui.DeviceTests.Stubs +{ + public partial class PickerStub : StubBase, IPicker + { + public string Title { get; set; } + + public IList Items { get; set; } = new List(); + + public IList ItemsSource { get; set; } + + public int SelectedIndex { get; set; } = -1; + + public object SelectedItem { get; set; } + } +} \ No newline at end of file From 91134e374c35ad3ae10ccc0102bf85e221cc79c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 8 Mar 2021 14:31:10 +0100 Subject: [PATCH 02/12] Nullability fixes --- src/Core/src/Handlers/Picker/PickerHandler.Android.cs | 2 +- src/Core/src/Handlers/Picker/PickerHandler.iOS.cs | 9 +-------- src/Core/src/Platform/Android/PickerManager.cs | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs index 6293a682426b..5a4a3e4030f6 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs @@ -103,7 +103,7 @@ void OnClick(object? sender, EventArgs e) } } - void OnCollectionChanged(object sender, EventArgs e) + void OnCollectionChanged(object? sender, EventArgs e) { if (VirtualView == null || TypedNativeView == null) return; diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs index 5c204b9e57b6..8991abe4eedb 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs @@ -58,7 +58,6 @@ protected override NativePicker CreateNativeView() protected override void ConnectHandler(NativePicker nativeView) { - nativeView.EditingDidBegin += OnStarted; nativeView.EditingDidEnd += OnEnded; nativeView.EditingChanged += OnEditing; @@ -70,7 +69,6 @@ protected override void ConnectHandler(NativePicker nativeView) protected override void DisconnectHandler(NativePicker nativeView) { - nativeView.EditingDidBegin -= OnStarted; nativeView.EditingDidEnd -= OnEnded; nativeView.EditingChanged -= OnEditing; @@ -103,7 +101,7 @@ public static void MapSelectedIndex(PickerHandler handler, IPicker picker) handler.TypedNativeView?.UpdateSelectedIndex(picker); } - void OnCollectionChanged(object sender, EventArgs e) + void OnCollectionChanged(object? sender, EventArgs e) { if (VirtualView == null || TypedNativeView == null) return; @@ -111,11 +109,6 @@ void OnCollectionChanged(object sender, EventArgs e) TypedNativeView.UpdatePicker(VirtualView); } - void OnStarted(object? sender, EventArgs eventArgs) - { - - } - void OnEnded(object? sender, EventArgs eventArgs) { if (_pickerView == null) diff --git a/src/Core/src/Platform/Android/PickerManager.cs b/src/Core/src/Platform/Android/PickerManager.cs index e1e40f32607c..d8c2765837a8 100644 --- a/src/Core/src/Platform/Android/PickerManager.cs +++ b/src/Core/src/Platform/Android/PickerManager.cs @@ -36,7 +36,7 @@ public static void OnFocusChanged(bool gainFocus, EditText sender) sender.CallOnClick(); } - static void OnKeyPress(object sender, AView.KeyEventArgs e) + static void OnKeyPress(object? sender, AView.KeyEventArgs e) { if (!AvailableKeys.Contains(e.KeyCode)) { From 2f388dabd881a240f2dbb580cddc0583585ea96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 8 Mar 2021 15:59:49 +0100 Subject: [PATCH 03/12] Updated Picker device tests --- .../tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs | 6 ++++-- src/Core/tests/DeviceTests/TestCategory.cs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs index 72a880c75b94..b0d7a850bf83 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs @@ -4,14 +4,15 @@ namespace Microsoft.Maui.DeviceTests { - [Category("PickerHandler")] + [Category(TestCategory.Picker)] public partial class PickerHandlerTests : HandlerTestBase { public PickerHandlerTests(HandlerTestFixture fixture) : base(fixture) { } - [Fact(DisplayName = "[PickerHandler] Title Initializes Correctly")] +#if MONOANDROID + [Fact(DisplayName = "Title Initializes Correctly")] public async Task TitleInitializesCorrectly() { var picker = new PickerStub @@ -21,5 +22,6 @@ public async Task TitleInitializesCorrectly() await ValidatePropertyInitValue(picker, () => picker.Title, GetNativeTitle, picker.Title); } +#endif } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/TestCategory.cs b/src/Core/tests/DeviceTests/TestCategory.cs index 5494b608e9bb..2ed3e3928aad 100644 --- a/src/Core/tests/DeviceTests/TestCategory.cs +++ b/src/Core/tests/DeviceTests/TestCategory.cs @@ -8,6 +8,7 @@ public static class TestCategory public const string Entry = "Entry"; public const string Label = "Label"; public const string Layout = "Layout"; + public const string Picker = "Picker"; public const string SearchBar = "SearchBar"; public const string Slider = "Slider"; public const string Stepper = "Stepper"; From 701d88014acc44ed566c3385facfe811da317b36 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Sat, 13 Mar 2021 03:33:05 +0200 Subject: [PATCH 04/12] New things in the tests! --- .../tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs index b0d7a850bf83..28cf4d3d2586 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs @@ -5,7 +5,7 @@ namespace Microsoft.Maui.DeviceTests { [Category(TestCategory.Picker)] - public partial class PickerHandlerTests : HandlerTestBase + public partial class PickerHandlerTests : HandlerTestBase { public PickerHandlerTests(HandlerTestFixture fixture) : base(fixture) { From a2552f205c8fc7f945f96ca5f4fad67953dbd95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 15 Mar 2021 11:25:18 +0100 Subject: [PATCH 05/12] Removed unnecessary Android Api level validation --- src/Core/src/Platform/Android/NativePicker.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Core/src/Platform/Android/NativePicker.cs b/src/Core/src/Platform/Android/NativePicker.cs index fefff71a106b..21a62a57b76a 100644 --- a/src/Core/src/Platform/Android/NativePicker.cs +++ b/src/Core/src/Platform/Android/NativePicker.cs @@ -2,13 +2,8 @@ using Android.Views; using Android.Widget; using Android.Runtime; -using ARect = Android.Graphics.Rect; - -#if __ANDROID_29__ using AndroidX.Core.Graphics.Drawable; -#else -using Android.Support.V4.Graphics.Drawable; -#endif +using ARect = Android.Graphics.Rect; namespace Microsoft.Maui { From 33c1a5c961d11c3d24e75f65a0efffbd4f7d22a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 15 Mar 2021 11:26:22 +0100 Subject: [PATCH 06/12] Added Picker Items null validation in iOS PickerHandler --- src/Core/src/Handlers/Picker/PickerHandler.iOS.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs index 8991abe4eedb..8cd49ee8db8e 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs @@ -61,7 +61,7 @@ protected override void ConnectHandler(NativePicker nativeView) nativeView.EditingDidEnd += OnEnded; nativeView.EditingChanged += OnEditing; - if (VirtualView != null) + if (VirtualView != null && VirtualView.Items != null) ((INotifyCollectionChanged)VirtualView.Items).CollectionChanged += OnCollectionChanged; base.ConnectHandler(nativeView); @@ -71,9 +71,9 @@ protected override void DisconnectHandler(NativePicker nativeView) { nativeView.EditingDidEnd -= OnEnded; nativeView.EditingChanged -= OnEditing; - - if (VirtualView != null) - ((INotifyCollectionChanged) VirtualView.Items).CollectionChanged -= OnCollectionChanged; + + if (VirtualView != null && VirtualView.Items != null) + ((INotifyCollectionChanged)VirtualView.Items).CollectionChanged -= OnCollectionChanged; if (_pickerView != null) { @@ -156,7 +156,7 @@ void UpdatePickerSelectedIndex(int formsIndex) _pickerView.Select(Math.Max(formsIndex, 0), 0, true); } } - + public class PickerSource : UIPickerViewModel { IPicker? _virtualView; From 94474ec830d8e4730756aee55c9556e2b17961ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 15 Mar 2021 11:30:22 +0100 Subject: [PATCH 07/12] Moved Picker Handler tests between different classes --- .../Picker/PickerHandlerTests.Android.cs | 15 ++++++++++++++- .../Handlers/Picker/PickerHandlerTests.cs | 16 +--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs index d44b1a681b6f..20df31f7c423 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs @@ -1,9 +1,22 @@ -using Microsoft.Maui.Handlers; +using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.Handlers; namespace Microsoft.Maui.DeviceTests { public partial class PickerHandlerTests { + [Fact(DisplayName = "Title Initializes Correctly")] + public async Task TitleInitializesCorrectly() + { + var picker = new PickerStub + { + Title = "Select an Item" + }; + + await ValidatePropertyInitValue(picker, () => picker.Title, GetNativeTitle, picker.Title); + } + NativePicker GetNativePicker(PickerHandler pickerHandler) => (NativePicker)pickerHandler.View; diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs index 28cf4d3d2586..9a898609461f 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Handlers; namespace Microsoft.Maui.DeviceTests @@ -10,18 +9,5 @@ public partial class PickerHandlerTests : HandlerTestBase picker.Title, GetNativeTitle, picker.Title); - } -#endif } } \ No newline at end of file From aea38d37910f063c141a071c573db32a3991625f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 15 Mar 2021 11:59:10 +0100 Subject: [PATCH 08/12] Renamed NativePicker to MauiPicker --- .../Handlers/Picker/PickerHandler.Android.cs | 12 +++--- .../src/Handlers/Picker/PickerHandler.iOS.cs | 10 ++--- .../{NativePicker.cs => MauiPicker.cs} | 8 ++-- .../src/Platform/Android/PickerExtensions.cs | 8 ++-- src/Core/src/Platform/iOS/MauiPicker.cs | 25 +++++++++++ src/Core/src/Platform/iOS/NativePicker.cs | 41 ------------------- src/Core/src/Platform/iOS/NoCaretField.cs | 20 +++++++++ src/Core/src/Platform/iOS/PickerExtensions.cs | 12 +++--- 8 files changed, 69 insertions(+), 67 deletions(-) rename src/Core/src/Platform/Android/{NativePicker.cs => MauiPicker.cs} (81%) create mode 100644 src/Core/src/Platform/iOS/MauiPicker.cs delete mode 100644 src/Core/src/Platform/iOS/NativePicker.cs create mode 100644 src/Core/src/Platform/iOS/NoCaretField.cs diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs index 5a4a3e4030f6..bf578b493149 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs @@ -2,20 +2,18 @@ using System.Collections.Specialized; using System.Linq; using Android.App; -using Android.Text; -using Android.Text.Style; using AResource = Android.Resource; namespace Microsoft.Maui.Handlers { - public partial class PickerHandler : AbstractViewHandler + public partial class PickerHandler : AbstractViewHandler { AlertDialog? _dialog; - protected override NativePicker CreateNativeView() => - new NativePicker(Context); + protected override MauiPicker CreateNativeView() => + new MauiPicker(Context); - protected override void ConnectHandler(NativePicker nativeView) + protected override void ConnectHandler(MauiPicker nativeView) { nativeView.FocusChange += OnFocusChange; nativeView.Click += OnClick; @@ -26,7 +24,7 @@ protected override void ConnectHandler(NativePicker nativeView) base.ConnectHandler(nativeView); } - protected override void DisconnectHandler(NativePicker nativeView) + protected override void DisconnectHandler(MauiPicker nativeView) { nativeView.FocusChange -= OnFocusChange; nativeView.Click -= OnClick; diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs index 8cd49ee8db8e..3fb41f574042 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs @@ -5,15 +5,15 @@ namespace Microsoft.Maui.Handlers { - public partial class PickerHandler : AbstractViewHandler + public partial class PickerHandler : AbstractViewHandler { UIPickerView? _pickerView; - protected override NativePicker CreateNativeView() + protected override MauiPicker CreateNativeView() { _pickerView = new UIPickerView(); - var nativePicker = new NativePicker(_pickerView) { BorderStyle = UITextBorderStyle.RoundedRect }; + var nativePicker = new MauiPicker(_pickerView) { BorderStyle = UITextBorderStyle.RoundedRect }; var width = UIScreen.MainScreen.Bounds.Width; var toolbar = new UIToolbar(new RectangleF(0, 0, width, 44)) { BarStyle = UIBarStyle.Default, Translucent = true }; @@ -56,7 +56,7 @@ protected override NativePicker CreateNativeView() return nativePicker; } - protected override void ConnectHandler(NativePicker nativeView) + protected override void ConnectHandler(MauiPicker nativeView) { nativeView.EditingDidEnd += OnEnded; nativeView.EditingChanged += OnEditing; @@ -67,7 +67,7 @@ protected override void ConnectHandler(NativePicker nativeView) base.ConnectHandler(nativeView); } - protected override void DisconnectHandler(NativePicker nativeView) + protected override void DisconnectHandler(MauiPicker nativeView) { nativeView.EditingDidEnd -= OnEnded; nativeView.EditingChanged -= OnEditing; diff --git a/src/Core/src/Platform/Android/NativePicker.cs b/src/Core/src/Platform/Android/MauiPicker.cs similarity index 81% rename from src/Core/src/Platform/Android/NativePicker.cs rename to src/Core/src/Platform/Android/MauiPicker.cs index 21a62a57b76a..392ad2c620dd 100644 --- a/src/Core/src/Platform/Android/NativePicker.cs +++ b/src/Core/src/Platform/Android/MauiPicker.cs @@ -7,11 +7,11 @@ namespace Microsoft.Maui { - public class NativePicker : NativePickerBase + public class MauiPicker : MauiPickerBase { public bool ShowPopupOnFocus { get; set; } - public NativePicker(Context? context) : base(context) + public MauiPicker(Context? context) : base(context) { PickerManager.Init(this); } @@ -37,9 +37,9 @@ protected override void Dispose(bool disposing) } } - public class NativePickerBase : EditText + public class MauiPickerBase : EditText { - public NativePickerBase(Context? context) : base(context) + public MauiPickerBase(Context? context) : base(context) { DrawableCompat.Wrap(Background); } diff --git a/src/Core/src/Platform/Android/PickerExtensions.cs b/src/Core/src/Platform/Android/PickerExtensions.cs index e710c4b34f0f..7ce75900e60b 100644 --- a/src/Core/src/Platform/Android/PickerExtensions.cs +++ b/src/Core/src/Platform/Android/PickerExtensions.cs @@ -2,13 +2,13 @@ { public static class PickerExtensions { - public static void UpdateTitle(this NativePicker nativePicker, IPicker picker) => + public static void UpdateTitle(this MauiPicker nativePicker, IPicker picker) => UpdatePicker(nativePicker, picker); - public static void UpdateSelectedIndex(this NativePicker nativePicker, IPicker picker) => + public static void UpdateSelectedIndex(this MauiPicker nativePicker, IPicker picker) => UpdatePicker(nativePicker, picker); - internal static void UpdatePicker(this NativePicker nativePicker, IPicker picker) + internal static void UpdatePicker(this MauiPicker nativePicker, IPicker picker) { nativePicker.Hint = picker.Title; @@ -20,7 +20,7 @@ internal static void UpdatePicker(this NativePicker nativePicker, IPicker picker nativePicker.SetSelectedItem(picker); } - internal static void SetSelectedItem(this NativePicker nativePicker, IPicker picker) + internal static void SetSelectedItem(this MauiPicker nativePicker, IPicker picker) { if (picker == null || nativePicker == null) return; diff --git a/src/Core/src/Platform/iOS/MauiPicker.cs b/src/Core/src/Platform/iOS/MauiPicker.cs new file mode 100644 index 000000000000..40a03a902454 --- /dev/null +++ b/src/Core/src/Platform/iOS/MauiPicker.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Foundation; +using UIKit; +using ObjCRuntime; + +namespace Microsoft.Maui +{ + public class MauiPicker : NoCaretField + { + readonly HashSet _enableActions; + + public MauiPicker(UIPickerView? uIPickerView) + { + UIPickerView = uIPickerView; + + string[] actions = { "copy:", "select:", "selectAll:" }; + _enableActions = new HashSet(actions); + } + + public UIPickerView? UIPickerView { get; set; } + + public override bool CanPerform(Selector action, NSObject? withSender) + => _enableActions.Contains(action.Name); + } +} diff --git a/src/Core/src/Platform/iOS/NativePicker.cs b/src/Core/src/Platform/iOS/NativePicker.cs deleted file mode 100644 index 6730b8e3aa29..000000000000 --- a/src/Core/src/Platform/iOS/NativePicker.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using Foundation; -using UIKit; -using ObjCRuntime; -using RectangleF = CoreGraphics.CGRect; - -namespace Microsoft.Maui -{ - public class NativePicker : NoCaretField - { - readonly HashSet _enableActions; - - public NativePicker(UIPickerView? uIPickerView) - { - UIPickerView = uIPickerView; - - string[] actions = { "copy:", "select:", "selectAll:" }; - _enableActions = new HashSet(actions); - } - - public UIPickerView? UIPickerView { get; set; } - - public override bool CanPerform(Selector action, NSObject? withSender) - => _enableActions.Contains(action.Name); - } - - public class NoCaretField : UITextField - { - public NoCaretField() : base(new RectangleF()) - { - SpellCheckingType = UITextSpellCheckingType.No; - AutocorrectionType = UITextAutocorrectionType.No; - AutocapitalizationType = UITextAutocapitalizationType.None; - } - - public override RectangleF GetCaretRectForPosition(UITextPosition? position) - { - return new RectangleF(); - } - } -} diff --git a/src/Core/src/Platform/iOS/NoCaretField.cs b/src/Core/src/Platform/iOS/NoCaretField.cs new file mode 100644 index 000000000000..5fdcb719eb90 --- /dev/null +++ b/src/Core/src/Platform/iOS/NoCaretField.cs @@ -0,0 +1,20 @@ +using UIKit; +using RectangleF = CoreGraphics.CGRect; + +namespace Microsoft.Maui +{ + public class NoCaretField : UITextField + { + public NoCaretField() : base(new RectangleF()) + { + SpellCheckingType = UITextSpellCheckingType.No; + AutocorrectionType = UITextAutocorrectionType.No; + AutocapitalizationType = UITextAutocapitalizationType.None; + } + + public override RectangleF GetCaretRectForPosition(UITextPosition? position) + { + return new RectangleF(); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/PickerExtensions.cs b/src/Core/src/Platform/iOS/PickerExtensions.cs index df7259b8362c..fa8a758ed6a8 100644 --- a/src/Core/src/Platform/iOS/PickerExtensions.cs +++ b/src/Core/src/Platform/iOS/PickerExtensions.cs @@ -7,13 +7,13 @@ namespace Microsoft.Maui { public static class PickerExtensions { - public static void UpdateTitle(this NativePicker nativePicker, IPicker picker) => + public static void UpdateTitle(this MauiPicker nativePicker, IPicker picker) => nativePicker.UpdatePicker(picker); - public static void UpdateSelectedIndex(this NativePicker nativePicker, IPicker picker) => + public static void UpdateSelectedIndex(this MauiPicker nativePicker, IPicker picker) => nativePicker.SetSelectedIndex(picker, picker.SelectedIndex); - internal static void UpdatePicker(this NativePicker nativePicker, IPicker picker) + internal static void UpdatePicker(this MauiPicker nativePicker, IPicker picker) { var selectedIndex = picker.SelectedIndex; var items = picker.Items; @@ -30,7 +30,7 @@ internal static void UpdatePicker(this NativePicker nativePicker, IPicker picker nativePicker.SetSelectedItem(picker); } - internal static void SetSelectedIndex(this NativePicker nativePicker, IPicker picker, int selectedIndex = 0) + internal static void SetSelectedIndex(this MauiPicker nativePicker, IPicker picker, int selectedIndex = 0) { picker.SelectedIndex = selectedIndex; @@ -45,7 +45,7 @@ internal static void SetSelectedIndex(this NativePicker nativePicker, IPicker pi pickerView?.Select(Math.Max(selectedIndex, 0), 0, true); } - internal static void SetSelectedItem(this NativePicker nativePicker, IPicker picker) + internal static void SetSelectedItem(this MauiPicker nativePicker, IPicker picker) { if (nativePicker == null) return; @@ -67,7 +67,7 @@ internal static void SetSelectedItem(this NativePicker nativePicker, IPicker pic picker.SelectedItem = picker.Items[index]; } - internal static void UpdateAttributedPlaceholder(this NativePicker nativePicker, NSAttributedString nsAttributedString) + internal static void UpdateAttributedPlaceholder(this MauiPicker nativePicker, NSAttributedString nsAttributedString) { nativePicker.AttributedPlaceholder = nsAttributedString; } From 5b35b54cce42eb67864fd10ef857d9233472e775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 15 Mar 2021 12:04:06 +0100 Subject: [PATCH 09/12] Removed unused code from iOS PickerExtensions --- src/Core/src/Handlers/Picker/PickerHandler.Android.cs | 2 +- src/Core/src/Platform/iOS/PickerExtensions.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs index bf578b493149..4d5ca267fe5c 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.Android.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.Android.cs @@ -66,7 +66,7 @@ void OnFocusChange(object? sender, global::Android.Views.View.FocusChangeEventAr void OnClick(object? sender, EventArgs e) { - if (VirtualView != null && _dialog == null) + if (_dialog == null && VirtualView != null) { using (var builder = new AlertDialog.Builder(Context)) { diff --git a/src/Core/src/Platform/iOS/PickerExtensions.cs b/src/Core/src/Platform/iOS/PickerExtensions.cs index fa8a758ed6a8..df2847b7a885 100644 --- a/src/Core/src/Platform/iOS/PickerExtensions.cs +++ b/src/Core/src/Platform/iOS/PickerExtensions.cs @@ -66,10 +66,5 @@ internal static void SetSelectedItem(this MauiPicker nativePicker, IPicker picke picker.SelectedItem = picker.Items[index]; } - - internal static void UpdateAttributedPlaceholder(this MauiPicker nativePicker, NSAttributedString nsAttributedString) - { - nativePicker.AttributedPlaceholder = nsAttributedString; - } } } \ No newline at end of file From 7190c2c055fd8540a678ecc0288900be510d370f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Mon, 15 Mar 2021 19:32:27 +0100 Subject: [PATCH 10/12] Fix build error --- .../DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs | 4 ++-- .../DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs index 20df31f7c423..825a3d7cb714 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs @@ -17,8 +17,8 @@ public async Task TitleInitializesCorrectly() await ValidatePropertyInitValue(picker, () => picker.Title, GetNativeTitle, picker.Title); } - NativePicker GetNativePicker(PickerHandler pickerHandler) => - (NativePicker)pickerHandler.View; + MauiPicker GetNativePicker(PickerHandler pickerHandler) => + (MauiPicker)pickerHandler.View; string GetNativeTitle(PickerHandler pickerHandler) => GetNativePicker(pickerHandler).Hint; diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs index 1db684bcc1a2..b632b3da9f44 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs @@ -6,8 +6,8 @@ namespace Microsoft.Maui.DeviceTests { public partial class PickerHandlerTests { - NativePicker GetNativePicker(PickerHandler pickerHandler) => - (NativePicker)pickerHandler.View; + MauiPicker GetNativePicker(PickerHandler pickerHandler) => + (MauiPicker)pickerHandler.View; string GetNativeTitle(PickerHandler pickerHandler) => GetNativePicker(pickerHandler).Text; From 706ec8a0044e8b21aa97657b30b8e6904fe1e980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Sua=CC=81rez=20Ruiz?= Date: Wed, 17 Mar 2021 09:54:26 +0100 Subject: [PATCH 11/12] Fix build error --- src/Core/src/Handlers/Picker/PickerHandler.iOS.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs index 3fb41f574042..b7790c26e5f2 100644 --- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs +++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs @@ -61,8 +61,8 @@ protected override void ConnectHandler(MauiPicker nativeView) nativeView.EditingDidEnd += OnEnded; nativeView.EditingChanged += OnEditing; - if (VirtualView != null && VirtualView.Items != null) - ((INotifyCollectionChanged)VirtualView.Items).CollectionChanged += OnCollectionChanged; + if (VirtualView != null && VirtualView.Items is INotifyCollectionChanged notifyCollectionChanged) + notifyCollectionChanged.CollectionChanged += OnCollectionChanged; base.ConnectHandler(nativeView); } @@ -72,8 +72,8 @@ protected override void DisconnectHandler(MauiPicker nativeView) nativeView.EditingDidEnd -= OnEnded; nativeView.EditingChanged -= OnEditing; - if (VirtualView != null && VirtualView.Items != null) - ((INotifyCollectionChanged)VirtualView.Items).CollectionChanged -= OnCollectionChanged; + if (VirtualView != null && VirtualView.Items is INotifyCollectionChanged notifyCollectionChanged) + notifyCollectionChanged.CollectionChanged -= OnCollectionChanged; if (_pickerView != null) { From ff80410701ba2be72ef47c589ec7233861359e53 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Wed, 17 Mar 2021 12:17:58 -0600 Subject: [PATCH 12/12] Remove duplicate class --- src/Core/src/Platform/iOS/MauiTimePicker.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Core/src/Platform/iOS/MauiTimePicker.cs b/src/Core/src/Platform/iOS/MauiTimePicker.cs index d6a2c5648f19..8a8a46d1e8dd 100644 --- a/src/Core/src/Platform/iOS/MauiTimePicker.cs +++ b/src/Core/src/Platform/iOS/MauiTimePicker.cs @@ -5,21 +5,6 @@ namespace Microsoft.Maui { - public class NoCaretField : UITextField - { - public NoCaretField() : base(new RectangleF()) - { - SpellCheckingType = UITextSpellCheckingType.No; - AutocorrectionType = UITextAutocorrectionType.No; - AutocapitalizationType = UITextAutocapitalizationType.None; - } - - public override RectangleF GetCaretRectForPosition(UITextPosition? position) - { - return new RectangleF(); - } - } - public class MauiTimePicker : NoCaretField { readonly Action _dateSelected;