rotorgames / Rg.Plugins.Popup

Xamarin Forms popup plugin
MIT License
1.15k stars 337 forks source link

Soft-keyboard not always dismissed correctly after/when closing popup #673

Open renelaerke opened 3 years ago

renelaerke commented 3 years ago

🐛 Bug Report

I'm working on an application targetting scanner devices (Android based Scanners like Honeywell CK65 and Zebra TC20) with HW numeric keyboards. And came across some odd behavior.

I realize this is not the normal case and that it may be very hard for you to test-/debug - i will try to supply as much info as possible to make it happen

When having a popup with in entry closing the Soft-keyboard is not always done "correctly" leaving the Soft-keyboard in a state that causes problems outside the popup after it has been closed.

Expected behavior

When the popup is dismissed any Control with Soft-keyboard enabled will have the Soft-keyboard dismissed correctly - so the state of the Soft-keyboard is correct after leaving / closing the popup.

Reproduction steps

I have a popup with anEntry and two buttons. The Entry has a Completed event that will close the popup and transfer the Text of Entry to the caller., The OK-button has an Click event doing the same, The Cancel-button transfers string.Empty to the caller.

In my MainActivity i've overridden the OnKeyDown and OnKeyUp events - resulting in any keypress - including those from the hardware keys (basically works as if a Bluetooth or USB-keyboard is connected).

Out side the popup i have a form with a custom control inheriting from Entry - but with the extra behavior that the Software keyboard is never shown (hidden by code in the custom renderer acting on FocusChange)

It should behave as when i press a HW key i send the keystroke (after validation/filtering) directly to the CustomEntryControl - without having to set "Focus" to the Control.

This all works EXACTLY as it's supposed to UNTIL i've been via the popup and "dismissed" it via one of the Buttons a bit to fast (threashold is 200ms - as revealed in your source code)

In that case the Popup-entry is not dismissing the software keyboard - which somehow is still connected to the now not displayed View and SWALLOWING the initial OnKeyDown events after leaving the popup.

The funny thing is get some Android Warnings instead of the first OnKeyDown event - AND somehow Android/Xamarin.Forms manages to set focus to my CustomEntry (with no keyboard), show the Softkeyboard - but NOT capturing the first key-down.

The second time i press a HW key the event is captured, focus is apparently transferred to the control (not by my code) resulting in the Soft-keyboard to be dismissed AND the Entry.Text to contain the value of the 2. keystoke.

Configuration

Version: 2.0.0.7 and 2.0.0.11

Platform:

Workaround Looking inside the source of the PopupPageRenderer.cs for Android the override of DispatchTouchEvent appently introduces this bug.

        public override bool DispatchTouchEvent(MotionEvent e)
        {
            if (e.Action == MotionEventActions.Down)
            {
                _downTime = DateTime.UtcNow;
                _downPosition = new Point(e.RawX, e.RawY);
            }
            if (e.Action != MotionEventActions.Up)
                return base.DispatchTouchEvent(e);

            if (_disposed)
                return false;

            View? currentFocus1 = ((Activity?)Context)?.CurrentFocus;

            if (currentFocus1 is EditText)
            {
                View? currentFocus2 = ((Activity?)Context)?.CurrentFocus;
                if (currentFocus1 == currentFocus2 && _downPosition.Distance(new Point(e.RawX, e.RawY)) <= Context.ToPixels(20.0) && !(DateTime.UtcNow - _downTime > TimeSpan.FromMilliseconds(200.0))) // <== *** Note the  ! > 200 ms
                {
                    var location = new int[2];
                    currentFocus1.GetLocationOnScreen(location);
                    var num1 = e.RawX + currentFocus1.Left - location[0];
                    var num2 = e.RawY + currentFocus1.Top - location[1];
                    if (!new Rectangle(currentFocus1.Left, currentFocus1.Top, currentFocus1.Width, currentFocus1.Height).Contains(num1, num2))
                    {
                        Context.HideKeyboard(currentFocus1);
                        currentFocus1.ClearFocus();
                    }
                }
            }

            if (_disposed)
                return false;

            var flag = base.DispatchTouchEvent(e);

            return flag;
        }

I think one of the reasons it was a hard error to find is the > 200 ms criteria. If I "press" either button "slow-ly" my example seems to work as expected - but if I "tap" either button the different code path results in a weird Soft-keyboard state.

Which leads me to my question - as I tried a workaround alltogether removing the DispatchTouchEvent from PopupPageRenderer.cs - everything seems to work EXACTLY as expected ????

So could you please elaborate as to WHY the DispatchTouchEvent is needed in the first place? Which scenario ?

Sorry for the all to looong issue report

renelaerke commented 3 years ago

Hello again,

i managed to build a small example that will let you trigger the bug.

Its a slightly modified Xamarin.Forms Sample app that shows the problem. RgPopupBug.zip

The demo has one page (MainPage) with a label, entry and a button The button triggers a popup (Popup) and awaits the result which is transferred to the label

The popup has an entry and two buttons (OK and Cancel) - the entry shares the completed event with the ok-clicked and the popup's background dismiss shares the cancel-clicked event.

In MainActivity the OnKeyDown and OnKeyUp are being overridden. When OnKeyDown is triggered the keystroke is transferred to the MainPage.OnKeyDown method which sets the MainPages entry.text to the value. This will implicit trigger the entry to get focussed and while the entry has focus it will grab any further keystrokes without going thru the OnKey events in MainActivity.

Reproduce the problem

  1. attach a bluetooth keyboard to any android device

  2. start the application

  3. press the "show popup" button

  4. enter "test" in the entry and tap the "OK" button fast (later do a second test but try pressing the button slowly)

  5. the text "test" is trasferred to the label on MainPage Screen1

  6. now press the "1" key on the bluetooth keyboard and notice

    1. the VS Output window shows warnings Output1

    2. the MainActivity only gets the OnKeyUp event Output2

    3. that the standard "TEXT" Soft-keyboard is popped eventough entrys keyboard is set to "Numeric" AND since the OnKeyDown was never called the entry's text is still "0" Screen2

  7. now press the "1" key on the bluetooth keyboard and notice

    1. both the MainActivity event OnKeyDown and OnKeyUp are called Output3

    2. the Soft-keyboard is now changed and since the OnKeyDown was called the text is now changed to "1" Screen3

If you experiment further by "slow"-pressing the "OK" button inside the popup you will see that "pressing" works, but "tapping" doesn't.

As described in the workaround i removed the PopupPageRenderes DispatchTouchEvent completely - which also solves the problem - but I'm afraid as to IF this is a viable solution. I'm mean the code is there for a reason - allthough I don't have problems in all my uses-cases????