From 51e019ce26a9832d1299cbd3d4e34a4ab225755b Mon Sep 17 00:00:00 2001 From: Li Date: Sat, 7 Sep 2024 10:40:43 +1000 Subject: [PATCH] Fix iOS long press not firing I was lazily using the oncontextmenu event to do long press, however this does not fire on iOS. This commit switches to using a custom long press based on timers and pointer events (similar to what we do elsewhere). As part of this I noticed that the dialog will be cancelled immediately on long press in iOS due to it firing some pointer events when the user hasn't moved enough. To fix this, I just disabled scrim cancellation in those dialogs which are triggered by long press. Fixes #263 --- LiftLog.Ui/Pages/History/HistoryPage.razor | 4 +- LiftLog.Ui/Shared/Presentation/Card.razor | 55 +++++++++++++++++-- LiftLog.Ui/Shared/Presentation/CardList.razor | 7 ++- .../Presentation/ConfirmationDialog.razor | 4 +- LiftLog.Ui/Shared/Presentation/Dialog.razor | 4 +- .../Presentation/VirtualizedCardList.razor | 2 +- .../Shared/Smart/SessionComponent.razor | 2 +- .../Smart/Tips/HoldingRepCounterTip.razor | 2 +- LiftLog.Ui/wwwroot/app-utils.js | 8 ++- 9 files changed, 70 insertions(+), 18 deletions(-) diff --git a/LiftLog.Ui/Pages/History/HistoryPage.razor b/LiftLog.Ui/Pages/History/HistoryPage.razor index 2d0c01dc..9db317f8 100644 --- a/LiftLog.Ui/Pages/History/HistoryPage.razor +++ b/LiftLog.Ui/Pages/History/HistoryPage.razor @@ -34,7 +34,7 @@ else OnDateSelect=CreateSessionAtDate OnSessionLongPress=@((session)=>{_selectedSession = session; _deleteDialog?.Open();}) /> - {_selectedSession = item; _deleteDialog?.Open();}) > + {_selectedSession = item; _deleteDialog?.Open();}) > @@ -56,7 +56,7 @@ else } } - + Delete Session? The session named @_selectedSession.Blueprint.Name, diff --git a/LiftLog.Ui/Shared/Presentation/Card.razor b/LiftLog.Ui/Shared/Presentation/Card.razor index f7304af4..09f9d9ea 100644 --- a/LiftLog.Ui/Shared/Presentation/Card.razor +++ b/LiftLog.Ui/Shared/Presentation/Card.razor @@ -1,9 +1,13 @@ @inject IJSRuntime JsRuntime +@inject IHapticFeedbackService HapticFeedback
@if (OnClick != null) @@ -21,9 +25,9 @@ [Parameter] public RenderFragment ChildContent { get; set; } = null!; - [Parameter] public Action? OnClick { get; set; } + [Parameter] public Action? OnClick { get; set; } - [Parameter] public EventCallback OnContextMenu { get; set; } + [Parameter] public EventCallback OnLongPress { get; set; } [Parameter] public bool IsHighlighted { get; set; } @@ -40,9 +44,12 @@ _ => "", }; - private void HandleOnClick(MouseEventArgs args) + private DateTime? _lastPointerDownTime; + private DateTime? _holdTime; + + private async Task HandleOnClick() { - OnClick?.Invoke(args); + OnClick?.Invoke(); } public enum CardType @@ -52,4 +59,40 @@ Elevated } + + async Task HandleLongPress() + { + await HapticFeedback.PerformAsync(HapticFeedbackType.LongPress); + await OnLongPress.InvokeAsync(); + } + + private async void OnPointerDown(PointerEventArgs args) + { + _lastPointerDownTime = DateTime.Now; + await Task.Delay(400); + if (_lastPointerDownTime is null || _lastPointerDownTime.Value.AddMilliseconds(400) > DateTime.Now) return; + _holdTime = DateTime.Now; + _lastPointerDownTime = null; + await HandleLongPress(); + _holdTime = null; + } + + + private void OnPointerLeave(PointerEventArgs args) + { + _holdTime = null; + _lastPointerDownTime = null; + } + + private async void OnPointerUp(MouseEventArgs args) + { + // On desktop holding down opens a right click menu - so this does not fire + // This results in needing to click the repcount twice after cycling. Not an issue on mobile + if (_holdTime is null) + { + await HandleOnClick(); + } + _holdTime = null; + _lastPointerDownTime = null; + } } diff --git a/LiftLog.Ui/Shared/Presentation/CardList.razor b/LiftLog.Ui/Shared/Presentation/CardList.razor index 40c8a041..bbbd0a57 100644 --- a/LiftLog.Ui/Shared/Presentation/CardList.razor +++ b/LiftLog.Ui/Shared/Presentation/CardList.razor @@ -1,13 +1,14 @@ @typeparam TItem +
@foreach (var item in Items) { OnContextMenu.InvokeAsync(item) : default!) + OnLongPress=@(OnLongPress.HasDelegate ? ()=>OnLongPress.InvokeAsync(item) : default!) IsHighlighted="ShouldHighlight?.Invoke(item) ?? false" - OnClick=@(OnClick != null ? _ => OnClick(item) : null)> + OnClick=@(OnClick != null ? () => OnClick(item) : null)> @ChildContent?.Invoke(item) } @@ -21,7 +22,7 @@ [Parameter] public Action? OnClick { get; set; } - [Parameter] public EventCallback OnContextMenu { get; set; } + [Parameter] public EventCallback OnLongPress { get; set; } [Parameter] public Func? ShouldHighlight { get; set; } diff --git a/LiftLog.Ui/Shared/Presentation/ConfirmationDialog.razor b/LiftLog.Ui/Shared/Presentation/ConfirmationDialog.razor index afb31479..f4858b7b 100644 --- a/LiftLog.Ui/Shared/Presentation/ConfirmationDialog.razor +++ b/LiftLog.Ui/Shared/Presentation/ConfirmationDialog.razor @@ -1,5 +1,5 @@ - + @Headline @TextContent @@ -32,6 +32,8 @@ [Parameter] public string SectionName { get; set; } = "Dialog"; + [Parameter] public bool PreventCancel { get; set; } = false; + private Dialog? dialog; public void Open() diff --git a/LiftLog.Ui/Shared/Presentation/Dialog.razor b/LiftLog.Ui/Shared/Presentation/Dialog.razor index f8d78253..541fb5be 100644 --- a/LiftLog.Ui/Shared/Presentation/Dialog.razor +++ b/LiftLog.Ui/Shared/Presentation/Dialog.razor @@ -30,12 +30,14 @@ [Parameter] public string SectionName { get; set; } = "Dialog"; + [Parameter] public bool PreventCancel { get; set; } = false; + public async void Open() { opened = true; StateHasChanged(); await Task.Yield(); - await JSRuntime.InvokeVoidAsync("AppUtils.onCloseMdPopup", dialog); + await JSRuntime.InvokeVoidAsync("AppUtils.onCloseMdPopup", dialog, PreventCancel); await JSRuntime.InvokeVoidAsync("AppUtils.showMdPopup", dialog); } diff --git a/LiftLog.Ui/Shared/Presentation/VirtualizedCardList.razor b/LiftLog.Ui/Shared/Presentation/VirtualizedCardList.razor index dc39f956..5d2be625 100644 --- a/LiftLog.Ui/Shared/Presentation/VirtualizedCardList.razor +++ b/LiftLog.Ui/Shared/Presentation/VirtualizedCardList.razor @@ -25,7 +25,7 @@ private RenderFragment RenderCard(TItem item) { - return @ + return @ @ChildContent?.Invoke(item) ; } diff --git a/LiftLog.Ui/Shared/Smart/SessionComponent.razor b/LiftLog.Ui/Shared/Smart/SessionComponent.razor index 6ff3e670..8ff064a5 100644 --- a/LiftLog.Ui/Shared/Smart/SessionComponent.razor +++ b/LiftLog.Ui/Shared/Smart/SessionComponent.razor @@ -89,7 +89,7 @@ else - + @if(setAdditionalActions != null) { var exercise = Session.RecordedExercises[setAdditionalActions.Value.ExerciseIndex]; diff --git a/LiftLog.Ui/Shared/Smart/Tips/HoldingRepCounterTip.razor b/LiftLog.Ui/Shared/Smart/Tips/HoldingRepCounterTip.razor index 22cf2a1a..44c6c196 100644 --- a/LiftLog.Ui/Shared/Smart/Tips/HoldingRepCounterTip.razor +++ b/LiftLog.Ui/Shared/Smart/Tips/HoldingRepCounterTip.razor @@ -17,7 +17,7 @@
- + Select Reps { if (element?.shadowRoot) { const scrim = element.shadowRoot.querySelector(".scrim"); @@ -36,7 +36,11 @@ AppUtils.onCloseMdPopup = function (element) { }) ); }); - element?.addEventListener("cancel", () => { + element?.addEventListener("cancel", (event) => { + if (preventCancel) { + event.preventDefault(); + return; + } element?.dispatchEvent( new Event("dialog-cancel", { bubbles: true,