Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

[Android] 28+ Make non-visible pickers work again #7289

Merged
merged 11 commits into from
Sep 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
using Xamarin.Forms.Internals;
using Xamarin.Forms.CustomAttributes;
#if UITEST
using Xamarin.Forms.Core.UITests;
using Xamarin.UITest;
using NUnit.Framework;
#endif

namespace Xamarin.Forms.Controls.Issues
{
#if UITEST
[Category(UITestCategories.Picker)]
[Category(UITestCategories.DatePicker)]
[Category(UITestCategories.TimePicker)]
#endif
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 5159, "[Android] Calling Focus on all Pickers running an API 28 devices no longer opens Picker", PlatformAffected.Android)]
public class Issue5159 : TestContentPage
{
const string DatePickerButton = "DatePickerButton";
const string TimePickerButton = "TimePickerButton";
const string PickerButton = "PickerButton";
readonly string[] _pickerValues = { "Foo", "Bar", "42", "1337" };

protected override void Init()
{
var stackLayout = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center
};

// DatePicker
var datePickerButton = new Button
{
Text = "Show DatePicker",
AutomationId = DatePickerButton
};

var datePicker = new DatePicker
{
IsVisible = false
};

datePickerButton.Clicked += (s, a) =>
{
Device.BeginInvokeOnMainThread(() =>
{
if (datePicker.IsFocused)
datePicker.Unfocus();

datePicker.Focus();
});
};

// TimePicker
var timePickerButton = new Button
{
Text = "Show TimePicker",
AutomationId = TimePickerButton
};

var timePicker = new TimePicker
{
IsVisible = false
};

timePickerButton.Clicked += (s, a) =>
{
Device.BeginInvokeOnMainThread(() =>
{
if (timePicker.IsFocused)
timePicker.Unfocus();

timePicker.Focus();
});
};

// Picker
var pickerButton = new Button
{
Text = "Show Picker",
AutomationId = PickerButton
};

var picker = new Picker
{
IsVisible = false,
ItemsSource = _pickerValues
};

pickerButton.Clicked += (s, a) =>
{
Device.BeginInvokeOnMainThread(() =>
{
if (picker.IsFocused)
picker.Unfocus();

picker.Focus();
});
};

stackLayout.Children.Add(datePickerButton);
stackLayout.Children.Add(datePicker);

stackLayout.Children.Add(timePickerButton);
stackLayout.Children.Add(timePicker);

stackLayout.Children.Add(pickerButton);
stackLayout.Children.Add(picker);

Content = stackLayout;
}

#if UITEST && __ANDROID__
[Test]
[UiTest(typeof(DatePicker))]
public void InvisibleDatepickerShowsDialogOnFocus()
{
RunningApp.WaitForElement(DatePickerButton);
RunningApp.Screenshot("Issue 5159 page is showing in all it's glory");
RunningApp.Tap(DatePickerButton);

RunningApp.WaitForElement(x => x.Class("DatePicker"));

RunningApp.Screenshot("DatePicker is shown");
RunningApp.TapCoordinates(5, 100);
}

[Test]
[UiTest(typeof(TimePicker))]
public void InvisibleTimepickerShowsDialogOnFocus()
{
RunningApp.WaitForElement(TimePickerButton);
RunningApp.Screenshot("Issue 5159 page is showing in all it's glory");
RunningApp.Tap(TimePickerButton);

RunningApp.WaitForElement(x => x.Class("timePicker"));

RunningApp.Screenshot("TimePicker is shown");
RunningApp.TapCoordinates(5, 100);
}

[Test]
[UiTest(typeof(Picker))]
public void InvisiblePickerShowsDialogOnFocus()
{
RunningApp.WaitForElement(PickerButton);
RunningApp.Screenshot("Issue 5159 page is showing in all it's glory");
RunningApp.Tap(PickerButton);

RunningApp.WaitForElement("Foo");

RunningApp.Screenshot("Picker is shown");

RunningApp.Tap("Foo");

RunningApp.WaitForNoElement("Foo");

}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;

#if UITEST
using Xamarin.UITest;
using NUnit.Framework;
using Xamarin.UITest.iOS;
#endif

namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 7311, "[Bug] [Android] Error back hardware button with Picker", PlatformAffected.Android)]
public class Issue7311 : TestContentPage
{
const string FirstPickerItem = "Uno";
const string PickerId = "CaptainPickard";
readonly string[] _items = { FirstPickerItem, "Dos", "Tres" };

protected override void Init()
{
var picker = new Picker
{
ItemsSource = _items,
AutomationId = PickerId
};

Content = new StackLayout()
{
Children =
{
new Label()
{
Text = "Open Picker. Click hardware back button to close picker. Click hardware button a second time and it should navigate back to gallery"
},
picker
}
};
}

#if UITEST && __ANDROID__
[Test]
public void OpeningPickerPressingBackButtonTwiceShouldNotOpenPickerAgain()
{
RunningApp.WaitForElement(PickerId);
RunningApp.Tap(PickerId);

RunningApp.WaitForElement(FirstPickerItem);

RunningApp.Back();

RunningApp.WaitForNoElement(FirstPickerItem);

RunningApp.Back();

RunningApp.WaitForNoElement(FirstPickerItem, "Picker is again visible after second back button press", TimeSpan.FromSeconds(10));

RunningApp.Screenshot("Back at the previous page, not showing the picker again");
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,8 @@
<Compile Include="$(MSBuildThisFileDirectory)GitHub6926.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue5503.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ShellTitleView.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue5159.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue7311.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue7053.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue6894.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue6929.cs" />
Expand Down
7 changes: 6 additions & 1 deletion Xamarin.Forms.Platform.Android/AppCompat/PickerRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
base.OnFocusChangeRequested(sender, e);

if (e.Focus)
CallOnClick();
{
if (Clickable)
CallOnClick();
else
((IPickerRenderer)this)?.OnClick();
}
else if (_dialog != null)
{
_dialog.Hide();
Expand Down
6 changes: 3 additions & 3 deletions Xamarin.Forms.Platform.Android/PickerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ namespace Xamarin.Forms.Platform.Android
{
internal static class PickerManager
{
readonly static HashSet<Keycode> availableKeys = new HashSet<Keycode>(new[] {
Keycode.Tab, Keycode.Forward, Keycode.Back, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
readonly static HashSet<Keycode> AvailableKeys = new HashSet<Keycode>(new[] {
Keycode.Tab, Keycode.Forward, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
});

public static void Init(EditText editText)
Expand Down Expand Up @@ -42,7 +42,7 @@ public static void OnFocusChanged(bool gainFocus, EditText sender, IPopupTrigger

static void OnKeyPress(object sender, AView.KeyEventArgs e)
{
if (!availableKeys.Contains(e.KeyCode))
if (!AvailableKeys.Contains(e.KeyCode))
{
e.Handled = false;
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
base.OnFocusChangeRequested(sender, e);

if (e.Focus)
CallOnClick();
{
if (Clickable)
CallOnClick();
else
((IPickerRenderer)this)?.OnClick();
}
else if (_dialog != null)
{
_dialog.Hide();
Expand Down
9 changes: 7 additions & 2 deletions Xamarin.Forms.Platform.Android/Renderers/PickerRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
base.OnFocusChangeRequested(sender, e);

if (e.Focus)
CallOnClick();
{
if (Clickable)
CallOnClick();
else
((IPickerRenderer)this)?.OnClick();
}
else if (_dialog != null)
{
_dialog.Hide();
Expand All @@ -119,7 +124,7 @@ void IPickerRenderer.OnClick()

if (_dialog != null)
return;

var picker = new NumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
Expand Down
28 changes: 27 additions & 1 deletion Xamarin.Forms.Platform.Android/Renderers/TimePickerRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public abstract class TimePickerRendererBase<TControl> : ViewRenderer<TimePicker
{
int _originalHintTextColor;
AlertDialog _dialog;
bool _isDisposed;

bool Is24HourView
{
Expand Down Expand Up @@ -86,7 +87,12 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
base.OnFocusChangeRequested(sender, e);

if (e.Focus)
CallOnClick();
{
if (Clickable)
CallOnClick();
else
((IPickerRenderer)this)?.OnClick();
}
else if (_dialog != null)
{
_dialog.Hide();
Expand All @@ -95,6 +101,7 @@ protected override void OnFocusChangeRequested(object sender, VisualElement.Focu
if (Forms.IsLollipopOrNewer)
_dialog.CancelEvent -= OnCancelButtonClicked;

_dialog?.Dispose();
_dialog = null;
}
}
Expand All @@ -109,6 +116,25 @@ protected virtual TimePickerDialog CreateTimePickerDialog(int hours, int minutes
return dialog;
}

protected override void Dispose(bool disposing)
{
if (_isDisposed)
return;

_isDisposed = true;

if (disposing)
{
if (Forms.IsLollipopOrNewer && _dialog.IsAlive())
_dialog.CancelEvent -= OnCancelButtonClicked;

_dialog?.Dispose();
_dialog = null;
}

base.Dispose(disposing);
}

void IPickerRenderer.OnClick()
{
if (_dialog != null && _dialog.IsShowing)
Expand Down