using System;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using ObjCRuntime;
using UIKit;
namespace Avalonia.iOS.Specific
{
///
/// In order to have properly handle of keyboard event in iOS View should already made some things in the View:
/// 1. Adopt the UIKeyInput protocol - add [Adopts("UIKeyInput")] to your view class
/// 2. Implement all the methods required by UIKeyInput:
/// 2.1 Implement HasText
/// example:
/// [Export("hasText")]
/// bool HasText => _keyboardHelper.HasText()
/// 2.2 Implement InsertText
/// example:
/// [Export("insertText:")]
/// void InsertText(string text) => _keyboardHelper.InsertText(text);
/// 2.3 Implement InsertText
/// example:
/// [Export("deleteBackward")]
/// void DeleteBackward() => _keyboardHelper.DeleteBackward();
/// 3.Let iOS know that this can become a first responder:
/// public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder();
/// or
/// public override bool CanBecomeFirstResponder { get { return true; } }
///
/// 4. To show keyboard:
/// view.BecomeFirstResponder();
/// 5. To hide keyboard
/// view.ResignFirstResponder();
///
/// View that needs keyboard events and show/hide keyboard
internal class KeyboardEventsHelper where TView : UIView, ITopLevelImpl
{
private TView _view;
private IInputElement _lastFocusedElement;
public KeyboardEventsHelper(TView view)
{
_view = view;
var uiKeyInputAttribute = view.GetType().GetCustomAttributes(typeof(AdoptsAttribute), true).OfType().Where(a => a.ProtocolType == "UIKeyInput").FirstOrDefault();
if (uiKeyInputAttribute == null) throw new NotSupportedException($"View class {typeof(TView).Name} should have class attribute - [Adopts(\"UIKeyInput\")] in order to access keyboard events!");
HandleEvents = true;
}
///
/// HandleEvents in order to suspend keyboard notifications or resume it
///
public bool HandleEvents { get; set; }
public bool HasText() => false;
public bool CanBecomeFirstResponder() => true;
public void DeleteBackward()
{
HandleKey(Key.Back, RawKeyEventType.KeyDown);
HandleKey(Key.Back, RawKeyEventType.KeyUp);
}
public void InsertText(string text)
{
var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, text);
_view.Input(rawTextEvent);
}
private void HandleKey(Key key, RawKeyEventType type)
{
var rawKeyEvent = new RawKeyEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, type, key, RawInputModifiers.None);
_view.Input(rawKeyEvent);
}
//currently not found a way to get InputModifiers state
//private static InputModifiers GetModifierKeys(object e)
//{
// var im = InputModifiers.None;
// //if (IsCtrlPressed) rv |= InputModifiers.Control;
// //if (IsShiftPressed) rv |= InputModifiers.Shift;
// return im;
//}
private bool NeedsKeyboard(IInputElement element)
{
//may be some other elements
return element is TextBox;
}
private void TryShowHideKeyboard(IInputElement element, bool value)
{
if (value)
{
_view.BecomeFirstResponder();
}
else
{
_view.ResignFirstResponder();
}
}
public void UpdateKeyboardState(IInputElement element)
{
var focusedElement = element;
bool oldValue = NeedsKeyboard(_lastFocusedElement);
bool newValue = NeedsKeyboard(focusedElement);
if (newValue != oldValue || newValue)
{
TryShowHideKeyboard(focusedElement, newValue);
}
_lastFocusedElement = element;
}
public void ActivateAutoShowKeyboard()
{
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
//just in case we've called more than once the method
kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged;
kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged;
}
private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement))
{
UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement);
}
}
public void Dispose()
{
HandleEvents = false;
}
}
}