Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [59.1.0]
- [CollectionView] `RemoveFocusOnScroll` now uses native platform APIs instead of managed Unfocus. Listener is cleaned up on disconnect.
- [ScrollView] `RemoveFocusOnScroll` now uses native platform APIs. Event subscription is removed on disconnect.

## [59.0.0]
- [BarcodeScanner] **BREAKING**: Replaced positional `Start` parameters and `BarcodeScanningSettings` with `BarcodeScanner.Start(BarcodeScannerStartOptions)` so preview, camera failure handling, validation, async callbacks, scan rectangle, and completion behavior are configured in one scanner session contract.
- [BarcodeScanner] Added visible focused scan rectangle overlay controlled by `BarcodeScannerStartOptions.ScanRectangle` and `BarcodeScanRectangleOptions`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace DIPS.Mobile.UI.Components.Lists;

public partial class CollectionViewHandler
{
private KeyboardDismissOnScrollListener? m_keyboardDismissOnScrollListener;

protected override RecyclerView CreatePlatformView()
{
return new MauiRecyclerView(Context, GetItemsLayout, CreateAdapter);
Expand Down Expand Up @@ -53,6 +55,37 @@ private static partial void MapShouldBounce(CollectionViewHandler handler,
collectionView.ShouldBounce ? OverScrollMode.Always : OverScrollMode.Never;
}
}

private static partial void MapRemoveFocusOnScroll(CollectionViewHandler handler,
Microsoft.Maui.Controls.CollectionView virtualView)
{
if (virtualView is not CollectionView collectionView)
return;

// Remove any existing listener first
if (handler.m_keyboardDismissOnScrollListener != null)
{
handler.PlatformView.RemoveOnScrollListener(handler.m_keyboardDismissOnScrollListener);
handler.m_keyboardDismissOnScrollListener = null;
}

if (collectionView.RemoveFocusOnScroll)
{
handler.m_keyboardDismissOnScrollListener = new KeyboardDismissOnScrollListener();
handler.PlatformView.AddOnScrollListener(handler.m_keyboardDismissOnScrollListener);
}
}

protected override void DisconnectHandler(RecyclerView platformView)
{
if (m_keyboardDismissOnScrollListener != null)
{
platformView.RemoveOnScrollListener(m_keyboardDismissOnScrollListener);
m_keyboardDismissOnScrollListener = null;
}

base.DisconnectHandler(platformView);
}

internal partial void ReloadData(CollectionViewHandler handler)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Android.Views.InputMethods;
using AndroidX.RecyclerView.Widget;
using Microsoft.Maui.Platform;

namespace DIPS.Mobile.UI.Components.Lists;

internal class KeyboardDismissOnScrollListener : RecyclerView.OnScrollListener
{
public override void OnScrollStateChanged(RecyclerView recyclerView, int newState)
{
base.OnScrollStateChanged(recyclerView, newState);

if (newState != RecyclerView.ScrollStateDragging)
return;

var context = recyclerView.Context;
if (context == null)
return;

var imm = (InputMethodManager?)context.GetSystemService(global::Android.Content.Context.InputMethodService);
imm?.HideSoftInputFromWindow(recyclerView.WindowToken, HideSoftInputFlags.None);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,22 @@ public bool HasAdditionalSpaceAtTheEnd
set => SetValue(HasAdditionalSizeAtTheEndProperty, value);
}

public static readonly BindableProperty RemoveFocusOnScrollProperty = BindableProperty.Create(
nameof(RemoveFocusOnScroll),
typeof(bool),
typeof(CollectionView),
defaultValue: false);

/// <summary>
/// Determines if input fields should be unfocused when the user scrolls the <see cref="CollectionView"/>. (ScrollBar, Editor etc..)
/// Determines if the keyboard should be dismissed when the user scrolls the <see cref="CollectionView"/>.
/// On iOS this sets UIScrollView.KeyboardDismissMode to OnDrag.
/// On Android this hides the soft input via InputMethodManager on scroll.
/// </summary>
public bool RemoveFocusOnScroll { get; set; }
public bool RemoveFocusOnScroll
{
get => (bool)GetValue(RemoveFocusOnScrollProperty);
set => SetValue(RemoveFocusOnScrollProperty, value);
}

public static readonly BindableProperty ShouldBounceProperty = BindableProperty.Create(
nameof(ShouldBounce),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using System.Collections;
using DIPS.Mobile.UI.Components.Dividers;
using Microsoft.Maui.Platform;
using SearchBar = DIPS.Mobile.UI.Components.Searching.SearchBar;

namespace DIPS.Mobile.UI.Components.Lists;

public partial class CollectionView : Microsoft.Maui.Controls.CollectionView
{
private readonly List<WeakReference<VisualElement>> m_inputFields = [];
private double m_previousHeightDifference;

public CollectionView()
Expand All @@ -25,34 +23,6 @@ protected override void OnHandlerChanging(HandlerChangingEventArgs args)
Dispose();
return;
}


if (!RemoveFocusOnScroll)
return;

var page = this.FindParentOfType<ContentPage>();
RetrieveInputFields(page);
}

private void RetrieveInputFields(IVisualTreeElement? visualTreeElement)
{
foreach (var child in visualTreeElement?.GetVisualTreeDescendants() ?? [])
{
if(Equals(child, visualTreeElement))
continue;

switch (child)
{
case InputView editor:
m_inputFields.Add(new WeakReference<VisualElement>(editor));
break;
case SearchBar searchBar:
m_inputFields.Add(new WeakReference<VisualElement>(searchBar));
break;
}

RetrieveInputFields(child);
}
}

protected override void OnScrolled(ItemsViewScrolledEventArgs e)
Expand All @@ -65,7 +35,6 @@ protected override void OnScrolled(ItemsViewScrolledEventArgs e)
return; //0 is idle
#endif
TryCollapseOrExpandElements(e);
TryRemoveScroll();
}

private void TryCollapseOrExpandElements(ItemsViewScrolledEventArgs e)
Expand Down Expand Up @@ -117,23 +86,6 @@ private void TryCollapseOrExpandElements(ItemsViewScrolledEventArgs e)
CollapsibleElement.TrySetInputTransparent();
}

private void TryRemoveScroll()
{
if (!RemoveFocusOnScroll)
return;

foreach (var inputFieldReference in m_inputFields)
{
if (inputFieldReference.TryGetTarget(out var inputField))
{
if(inputField is SearchBar searchBar)
searchBar.Unfocus();
else
inputField.Unfocus();
}
}
}

private bool IsBouncing(ItemsViewScrolledEventArgs e)
{
#if __IOS__
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ public CollectionViewHandler() : base(CollectionViewPropertyMapper)

public static readonly PropertyMapper CollectionViewPropertyMapper = new PropertyMapper<Microsoft.Maui.Controls.CollectionView, CollectionViewHandler>(Mapper)
{
[nameof(CollectionView.ShouldBounce)] = MapShouldBounce
[nameof(CollectionView.ShouldBounce)] = MapShouldBounce,
[nameof(CollectionView.RemoveFocusOnScroll)] = MapRemoveFocusOnScroll
};

private static partial void MapShouldBounce(CollectionViewHandler handler, Microsoft.Maui.Controls.CollectionView virtualView);

private static partial void MapRemoveFocusOnScroll(CollectionViewHandler handler, Microsoft.Maui.Controls.CollectionView virtualView);

internal partial void ReloadData(CollectionViewHandler handler);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ private static partial void MapShouldBounce(CollectionViewHandler handler,
Microsoft.Maui.Controls.CollectionView virtualView) =>
throw new Only_Here_For_UnitTests();

private static partial void MapRemoveFocusOnScroll(CollectionViewHandler handler,
Microsoft.Maui.Controls.CollectionView virtualView) =>
throw new Only_Here_For_UnitTests();

internal partial void ReloadData(CollectionViewHandler handler) => throw new Only_Here_For_UnitTests();

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ private static partial void MapShouldBounce(CollectionViewHandler handler,
}
}
}

private static partial void MapRemoveFocusOnScroll(CollectionViewHandler handler,
Microsoft.Maui.Controls.CollectionView virtualView)
{
if (handler.PlatformView.Subviews[0] is not UICollectionView uiCollectionView)
return;

if (virtualView is CollectionView collectionView)
{
uiCollectionView.KeyboardDismissMode = collectionView.RemoveFocusOnScroll
? UIScrollViewKeyboardDismissMode.OnDrag
: UIScrollViewKeyboardDismissMode.None;
}
}

internal partial void ReloadData(CollectionViewHandler handler)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Android.Views;
using Android.Views.InputMethods;
using Microsoft.Maui.Platform;
using AView = Android.Views.View;

namespace DIPS.Mobile.UI.Components.Lists;

Expand All @@ -12,4 +15,41 @@ private static partial void MapShouldBounce(ScrollViewHandler handler,
handler.PlatformView.OverScrollMode = scrollView.ShouldBounce ? OverScrollMode.Always : OverScrollMode.Never;
}
}

private static partial void MapRemoveFocusOnScroll(ScrollViewHandler handler,
Microsoft.Maui.Controls.ScrollView virtualView)
{
if (virtualView is not ScrollView scrollView)
return;

if (scrollView.RemoveFocusOnScroll)
{
scrollView.Scrolled += OnScrollViewScrolledForKeyboardDismiss;
}
else
{
scrollView.Scrolled -= OnScrollViewScrolledForKeyboardDismiss;
}
}

protected override void DisconnectHandler(MauiScrollView platformView)
{
if (VirtualView is ScrollView scrollView)
{
scrollView.Scrolled -= OnScrollViewScrolledForKeyboardDismiss;
}
}

private static void OnScrollViewScrolledForKeyboardDismiss(object? sender, ScrolledEventArgs e)
{
if (sender is not ScrollView { Handler: ScrollViewHandler handler } scrollView)
return;

var context = handler.PlatformView.Context;
if (context == null)
return;

var imm = (InputMethodManager?)context.GetSystemService(global::Android.Content.Context.InputMethodService);
imm?.HideSoftInputFromWindow(handler.PlatformView.WindowToken, HideSoftInputFlags.None);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@ public bool ShouldBounce
set => SetValue(ShouldBounceProperty, value);
}

public static readonly BindableProperty RemoveFocusOnScrollProperty = BindableProperty.Create(
nameof(RemoveFocusOnScroll),
typeof(bool),
typeof(ScrollView),
defaultValue: false);

/// <summary>
/// Determines if input fields should be unfocused when the user scrolls the <see cref="ScrollView"/>. (ScrollBar, Editor etc..)
/// Determines if the keyboard should be dismissed when the user scrolls the <see cref="ScrollView"/>.
/// On iOS this sets UIScrollView.KeyboardDismissMode to OnDrag.
/// On Android this hides the soft input via InputMethodManager on scroll.
/// </summary>
public bool RemoveFocusOnScroll { get; set; }
public bool RemoveFocusOnScroll
{
get => (bool)GetValue(RemoveFocusOnScrollProperty);
set => SetValue(RemoveFocusOnScrollProperty, value);
}

public static readonly BindableProperty HasAdditionalSpaceAtTheEndProperty = BindableProperty.Create(
nameof(HasAdditionalSpaceAtTheEnd),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@ namespace DIPS.Mobile.UI.Components.Lists;
public partial class ScrollView : Microsoft.Maui.Controls.ScrollView
{
private bool m_hasAddedSpaceToBottom;

private readonly List<WeakReference<VisualElement>> m_inputFields = [];

public ScrollView()
{
#if __ANDROID__ //Not possible to set padding on scroll view after its rendered
AdjustPadding(AndroidAdditionalSpaceAtEnd);
#endif

this.Scrolled += OnScrolled;
}

#if __IOS__
Expand All @@ -26,56 +22,6 @@ protected override void OnSizeAllocated(double width, double height)
protected override void OnHandlerChanging(HandlerChangingEventArgs args)
{
base.OnHandlerChanging(args);

if (args.NewHandler is null)
{
this.Scrolled -= OnScrolled;
return;
}

if(!RemoveFocusOnScroll)
return;

var page = this.FindParentOfType<ContentPage>();
RetrieveInputFields(page);
}

private void RetrieveInputFields(IVisualTreeElement? visualTreeElement)
{
foreach (var child in visualTreeElement?.GetVisualTreeDescendants() ?? [])
{
if(Equals(child, visualTreeElement))
continue;

switch (child)
{
case InputView editor:
m_inputFields.Add(new WeakReference<VisualElement>(editor));
break;
case Searching.SearchBar searchBar:
m_inputFields.Add(new WeakReference<VisualElement>(searchBar));
break;
}

RetrieveInputFields(child);
}
}

private void OnScrolled(object? sender, ScrolledEventArgs e)
{
if (!RemoveFocusOnScroll)
return;

foreach (var inputFieldReference in m_inputFields)
{
if (inputFieldReference.TryGetTarget(out var inputField))
{
if(inputField is Searching.SearchBar searchBar)
searchBar.Unfocus();
else
inputField.Unfocus();
}
}
}

private void AdjustPadding(double height)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ public ScrollViewHandler() : base(ScrollViewPropertyMapper)

public static readonly PropertyMapper ScrollViewPropertyMapper = new PropertyMapper<Microsoft.Maui.Controls.ScrollView, ScrollViewHandler>(Mapper)
{
[nameof(CollectionView.ShouldBounce)] = MapShouldBounce
[nameof(CollectionView.ShouldBounce)] = MapShouldBounce,
[nameof(ScrollView.RemoveFocusOnScroll)] = MapRemoveFocusOnScroll
};

private static partial void MapShouldBounce(ScrollViewHandler handler, Microsoft.Maui.Controls.ScrollView virtualView);

private static partial void MapRemoveFocusOnScroll(ScrollViewHandler handler, Microsoft.Maui.Controls.ScrollView virtualView);
}
Loading