From 228deefa2441df4da702fc3ca0a4462be6aa5d94 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 17 Apr 2025 10:21:02 -0400 Subject: [PATCH 01/71] Create MauiGestureRecognizerEventsBinder.cs --- .../MauiGestureRecognizerEventsBinder.cs | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs diff --git a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs new file mode 100644 index 0000000000..cc2a00e35a --- /dev/null +++ b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs @@ -0,0 +1,210 @@ +namespace Sentry.Maui.Internal; + +/// +/// Binds to +/// +public class MauiGestureRecognizerEventsBinder : IMauiElementEventBinder +{ + private Action _addBreadcrumb = null!; + + /// + /// Searches VisualElement for gesture recognizers to bind to + /// + public void Bind(VisualElement element, Action addBreadcrumb) + { + _addBreadcrumb ??= addBreadcrumb; + if (element is IGestureRecognizers recognizers) + { + foreach (var recognizer in recognizers.GestureRecognizers) + { + SetHooks(recognizer, true); + } + } + } + + + /// + /// Searches VisualElement for gesture recognizers to unbind from + /// + /// + public void UnBind(VisualElement element) + { + if (element is IGestureRecognizers recognizers) + { + foreach (var recognizer in recognizers.GestureRecognizers) + { + SetHooks(recognizer, false); + } + } + } + + + void SetHooks(IGestureRecognizer recognizer, bool add) + { + switch (recognizer) + { + case TapGestureRecognizer tap: + if (add) + { + tap.Tapped += OnTapGesture; + } + else + { + tap.Tapped -= OnTapGesture; + } + break; + + case SwipeGestureRecognizer swipe: + if (add) + { + swipe.Swiped += OnSwipeGesture; + } + else + { + swipe.Swiped -= OnSwipeGesture; + } + break; + + case PinchGestureRecognizer pinch: + if (add) + { + pinch.PinchUpdated += OnPinchGesture; + } + else + { + pinch.PinchUpdated -= OnPinchGesture; + } + break; + + case DragGestureRecognizer drag: + if (add) + { + drag.DragStarting += OnDragStartingGesture; + drag.DropCompleted += OnDropCompletedGesture; + } + else + { + drag.DragStarting -= OnDragStartingGesture; + drag.DropCompleted -= OnDropCompletedGesture; + } + break; + + case PanGestureRecognizer pan: + if (add) + { + pan.PanUpdated += OnPanGesture; + } + else + { + pan.PanUpdated -= OnPanGesture; + } + break; + + case PointerGestureRecognizer pointer: + if (add) + { + pointer.PointerEntered += OnPointerEnteredGesture; + pointer.PointerExited += OnPointerExitedGesture; + pointer.PointerMoved += OnPointerMovedGesture; + pointer.PointerPressed += OnPointerPressedGesture; + pointer.PointerReleased += OnPointerReleasedGesture; + } + else + { + pointer.PointerEntered -= OnPointerEnteredGesture; + pointer.PointerExited -= OnPointerExitedGesture; + pointer.PointerMoved -= OnPointerMovedGesture; + pointer.PointerPressed -= OnPointerPressedGesture; + pointer.PointerReleased -= OnPointerReleasedGesture; + } + break; + } + } + + private void OnPointerReleasedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(PointerGestureRecognizer.PointerReleased), + ToPointerData(e) + )); + + private void OnPointerPressedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(PointerGestureRecognizer.PointerPressed), + ToPointerData(e) + )); + + private void OnPointerMovedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(PointerGestureRecognizer.PointerMoved), + ToPointerData(e) + )); + + private void OnPointerExitedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(PointerGestureRecognizer.PointerExited), + ToPointerData(e) + )); + + private void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(PointerGestureRecognizer.PointerEntered), + ToPointerData(e) + )); + + + static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => + [ + // some of the data here may have some challenges being pulled out + #if ANDROID + ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) + //("MotionEventActionButton", e.PlatformArgs?.MotionEvent.ActionButton.ToString() ?? String.Empty) + #elif IOS + ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? String.Empty) + //("ButtonMask", e.PlatformArgs?.GestureRecognizer.ButtonMask.ToString() ?? String.Empty) + #endif + ]; + + private void OnPanGesture(object? sender, PanUpdatedEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(PanGestureRecognizer.PanUpdated), + [ + ("GestureId", e.GestureId.ToString()), + ("StatusType", e.StatusType.ToString()), + ("TotalX", e.TotalX.ToString()), + ("TotalY", e.TotalY.ToString()) + ] + )); + + private void OnDropCompletedGesture(object? sender, DropCompletedEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(DragGestureRecognizer.DropCompleted) + )); + + private void OnDragStartingGesture(object? sender, DragStartingEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(DragGestureRecognizer.DragStarting) + )); + + + private void OnPinchGesture(object? sender, PinchGestureUpdatedEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(PinchGestureRecognizer.PinchUpdated), + [ + ("GestureStatus", e.Status.ToString()), + ("Scale", e.Scale.ToString()), + ("ScaleOrigin", e.ScaleOrigin.ToString()) + ] + )); + + private void OnSwipeGesture(object? sender, SwipedEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(SwipeGestureRecognizer.Swiped), + [("Direction", e.Direction.ToString())] + )); + + private void OnTapGesture(object? sender, TappedEventArgs e) => _addBreadcrumb.Invoke(new( + sender, + nameof(TapGestureRecognizer.Tapped), + [("ButtonMask", e.Buttons.ToString())] + )); +} From e66308c6aef98fedbb34f70680f9ff20cba99a5a Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 17 Apr 2025 10:28:51 -0400 Subject: [PATCH 02/71] Wireup --- src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index b10eab38d7..87385641d9 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -57,8 +57,8 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, services.AddSingleton(); services.AddSingleton(); - services.TryAddSingleton(); - + services.AddSingleton(); + services.AddSingleton(); services.AddSentry(); builder.RegisterMauiEventsBinder(); From d2cb84fb892b2efbb0eaba5db661a5ed0e10293d Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 17 Apr 2025 14:40:02 +0000 Subject: [PATCH 03/71] Format code --- .../MauiGestureRecognizerEventsBinder.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs index cc2a00e35a..b4677b7198 100644 --- a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs @@ -39,7 +39,7 @@ public void UnBind(VisualElement element) } - void SetHooks(IGestureRecognizer recognizer, bool add) + private void SetHooks(IGestureRecognizer recognizer, bool add) { switch (recognizer) { @@ -152,7 +152,41 @@ private void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _add )); + +<<<<<<< TODO: Unmerged change from project 'Sentry.Maui(net8.0-android34.0)', Before: + static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => + [ + // some of the data here may have some challenges being pulled out + #if ANDROID + ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) +======= + private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => + [ + // some of the data here may have some challenges being pulled out + #if ANDROID + ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? string.Empty) +>>>>>>> After + +<<<<<<< TODO: Unmerged change from project 'Sentry.Maui(net8.0-ios17.0)', Before: static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => + [ + // some of the data here may have some challenges being pulled out + #if ANDROID + ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) + //("MotionEventActionButton", e.PlatformArgs?.MotionEvent.ActionButton.ToString() ?? String.Empty) + #elif IOS + ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? String.Empty) +======= + private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => + [ + // some of the data here may have some challenges being pulled out + #if ANDROID + ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) + //("MotionEventActionButton", e.PlatformArgs?.MotionEvent.ActionButton.ToString() ?? String.Empty) + #elif IOS + ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? string.Empty) +>>>>>>> After + private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => [ // some of the data here may have some challenges being pulled out #if ANDROID From 2eec158e7cc1a02d4cc3342f1f312c2102728c99 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 17 Apr 2025 10:57:20 -0400 Subject: [PATCH 04/71] WIP --- Sentry.sln | 7 ++ SentryMobile.slnf | 3 +- .../CtMvvmMauiElementEventBinder.cs | 78 +++++++++++++++++++ .../MauiAppBuilderExtensions.cs | 32 ++++++++ .../Sentry.Maui.CommunityToolkitMvvm.csproj | 26 +++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs create mode 100644 src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs create mode 100644 src/Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj diff --git a/Sentry.sln b/Sentry.sln index 3ef6fd24aa..b4c2d29661 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -197,6 +197,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.TrimTest", "test\Sen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.MauiTrimTest", "test\Sentry.MauiTrimTest\Sentry.MauiTrimTest.csproj", "{DF92E098-822C-4B94-B96B-56BFB91FBB54}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui.CommunityToolkitMvvm", "src\Sentry.Maui.CommunityToolkitMvvm\Sentry.Maui.CommunityToolkitMvvm.csproj", "{E898F337-E982-46CC-8DA9-F8556AA7DD72}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -532,6 +534,10 @@ Global {DF92E098-822C-4B94-B96B-56BFB91FBB54}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF92E098-822C-4B94-B96B-56BFB91FBB54}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF92E098-822C-4B94-B96B-56BFB91FBB54}.Release|Any CPU.Build.0 = Release|Any CPU + {E898F337-E982-46CC-8DA9-F8556AA7DD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E898F337-E982-46CC-8DA9-F8556AA7DD72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E898F337-E982-46CC-8DA9-F8556AA7DD72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E898F337-E982-46CC-8DA9-F8556AA7DD72}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -619,5 +625,6 @@ Global {D7DF0B26-AD43-4F8B-9BFE-C4471CCC9821} = {21B42F60-5802-404E-90F0-AEBCC56760C0} {6030B748-0000-43B5-B8A8-399AA42F5229} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} {DF92E098-822C-4B94-B96B-56BFB91FBB54} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {E898F337-E982-46CC-8DA9-F8556AA7DD72} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} EndGlobalSection EndGlobal diff --git a/SentryMobile.slnf b/SentryMobile.slnf index c38fa573cc..fe33528eb6 100644 --- a/SentryMobile.slnf +++ b/SentryMobile.slnf @@ -11,6 +11,7 @@ "src\\Sentry.Bindings.Android\\Sentry.Bindings.Android.csproj", "src\\Sentry.Bindings.Cocoa\\Sentry.Bindings.Cocoa.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", + "src\\Sentry.Maui.CommunityToolkitMvvm\\Sentry.Maui.CommunityToolkitMvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", @@ -21,4 +22,4 @@ "test\\Sentry.Tests\\Sentry.Tests.csproj" ] } -} +} \ No newline at end of file diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs new file mode 100644 index 0000000000..5930eda878 --- /dev/null +++ b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs @@ -0,0 +1,78 @@ +using System.Windows.Input; +using CommunityToolkit.Mvvm.Input; + +namespace Sentry.Maui.CommunityToolkitMvvm; + +/// +/// Scans all elements for known commands that are implement +/// +public class CtMvvmMauiElementEventBinder : IMauiElementEventBinder +{ + /// + /// Binds to the element + /// + /// + /// + public void Bind(VisualElement element, Action addBreadcrumb) => Iterate(element, true); + + /// + /// Unbinds from the element + /// + /// + /// + public void UnBind(VisualElement element) => Iterate(element, false); + + + void Iterate(VisualElement element, bool bind) + { + switch (element) + { + case Button button: + TryBindTo(button.Command, bind); + break; + + case ImageButton imageButton: + TryBindTo(imageButton.Command, bind); + break; + + } + + if (element is IGestureRecognizers gestureRecognizers) + { + foreach (var gestureRecognizer in gestureRecognizers.GestureRecognizers) + { + } + } + } + + private static void TryBindTo(ICommand? command, bool bind) + { + if (command is IAsyncRelayCommand relayCommand) + { + if (bind) + { + relayCommand.PropertyChanged += RelayCommandOnPropertyChanged; + } + else + { + relayCommand.PropertyChanged -= RelayCommandOnPropertyChanged; + } + } + } + + private static void RelayCommandOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(IAsyncRelayCommand.IsRunning)) + { + var relay = (IAsyncRelayCommand)sender!; + if (relay.IsRunning) + { + // start span + } + else + { + // finish span + } + } + } +} diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs b/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs new file mode 100644 index 0000000000..4f209e6bf6 --- /dev/null +++ b/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs @@ -0,0 +1,32 @@ +using Sentry.Maui.CommunityToolkitMvvm; + +namespace Sentry.Maui; + +/// +/// Methods to hook into MAUI & CommunityToolkit.Mvvm +/// +public static class MauiAppBuilderExtensions +{ + /// + /// Installs necessary services to auto-instrument CommunityToolkit.Mvvm commands + /// + /// The MauiAppBuilder + /// The MauiAppBuilder + public static MauiAppBuilder UseSentryCommunityToolkitIntegration(this MauiAppBuilder builder) + { + builder.Services.UseSentryCommunityToolkitIntegration(); + return builder; + } + + + /// + /// Installs necessary services to auto-instrument CommunityToolkit.Mvvm commands + /// + /// The Service Collection + /// The Service Collection + public static IServiceCollection UseSentryCommunityToolkitIntegration(this IServiceCollection services) + { + services.AddSingleton(); + return services; + } +} diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj b/src/Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj new file mode 100644 index 0000000000..7613389dcb --- /dev/null +++ b/src/Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj @@ -0,0 +1,26 @@ + + + + MAUI and Community Toolkit integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. + net9.0;net8.0 + + enable + enable + true + + + true + + + + + + + + + + + + + + From 24248daa9f24aab3a061db9d31c4261ad45ea304 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 17 Apr 2025 11:03:42 -0400 Subject: [PATCH 05/71] Cleanup and fix code due stupid bot --- .../MauiGestureRecognizerEventsBinder.cs | 70 ++++--------------- 1 file changed, 13 insertions(+), 57 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs index b4677b7198..ef48ad6ec4 100644 --- a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs @@ -10,41 +10,32 @@ public class MauiGestureRecognizerEventsBinder : IMauiElementEventBinder /// /// Searches VisualElement for gesture recognizers to bind to /// - public void Bind(VisualElement element, Action addBreadcrumb) - { - _addBreadcrumb ??= addBreadcrumb; - if (element is IGestureRecognizers recognizers) - { - foreach (var recognizer in recognizers.GestureRecognizers) - { - SetHooks(recognizer, true); - } - } - } - + public void Bind(VisualElement element, Action addBreadcrumb) => TryBind(element, true); /// /// Searches VisualElement for gesture recognizers to unbind from /// /// - public void UnBind(VisualElement element) + public void UnBind(VisualElement element) => TryBind(element, false); + + private void TryBind(VisualElement element, bool bind) { if (element is IGestureRecognizers recognizers) { foreach (var recognizer in recognizers.GestureRecognizers) { - SetHooks(recognizer, false); + SetHooks(recognizer, bind); } } } - private void SetHooks(IGestureRecognizer recognizer, bool add) + private void SetHooks(IGestureRecognizer recognizer, bool bind) { switch (recognizer) { case TapGestureRecognizer tap: - if (add) + if (bind) { tap.Tapped += OnTapGesture; } @@ -55,7 +46,7 @@ private void SetHooks(IGestureRecognizer recognizer, bool add) break; case SwipeGestureRecognizer swipe: - if (add) + if (bind) { swipe.Swiped += OnSwipeGesture; } @@ -66,7 +57,7 @@ private void SetHooks(IGestureRecognizer recognizer, bool add) break; case PinchGestureRecognizer pinch: - if (add) + if (bind) { pinch.PinchUpdated += OnPinchGesture; } @@ -77,7 +68,7 @@ private void SetHooks(IGestureRecognizer recognizer, bool add) break; case DragGestureRecognizer drag: - if (add) + if (bind) { drag.DragStarting += OnDragStartingGesture; drag.DropCompleted += OnDropCompletedGesture; @@ -90,7 +81,7 @@ private void SetHooks(IGestureRecognizer recognizer, bool add) break; case PanGestureRecognizer pan: - if (add) + if (bind) { pan.PanUpdated += OnPanGesture; } @@ -101,7 +92,7 @@ private void SetHooks(IGestureRecognizer recognizer, bool add) break; case PointerGestureRecognizer pointer: - if (add) + if (bind) { pointer.PointerEntered += OnPointerEnteredGesture; pointer.PointerExited += OnPointerExitedGesture; @@ -151,41 +142,6 @@ private void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _add ToPointerData(e) )); - - -<<<<<<< TODO: Unmerged change from project 'Sentry.Maui(net8.0-android34.0)', Before: - static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => - [ - // some of the data here may have some challenges being pulled out - #if ANDROID - ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) -======= - private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => - [ - // some of the data here may have some challenges being pulled out - #if ANDROID - ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? string.Empty) ->>>>>>> After - -<<<<<<< TODO: Unmerged change from project 'Sentry.Maui(net8.0-ios17.0)', Before: - static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => - [ - // some of the data here may have some challenges being pulled out - #if ANDROID - ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) - //("MotionEventActionButton", e.PlatformArgs?.MotionEvent.ActionButton.ToString() ?? String.Empty) - #elif IOS - ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? String.Empty) -======= - private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => - [ - // some of the data here may have some challenges being pulled out - #if ANDROID - ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) - //("MotionEventActionButton", e.PlatformArgs?.MotionEvent.ActionButton.ToString() ?? String.Empty) - #elif IOS - ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? string.Empty) ->>>>>>> After private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) => [ // some of the data here may have some challenges being pulled out @@ -193,7 +149,7 @@ private void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _add ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) //("MotionEventActionButton", e.PlatformArgs?.MotionEvent.ActionButton.ToString() ?? String.Empty) #elif IOS - ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? String.Empty) + ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? String.Empty), //("ButtonMask", e.PlatformArgs?.GestureRecognizer.ButtonMask.ToString() ?? String.Empty) #endif ]; From 98a97892406c5f481dcdf47a0e9ac4b063e96f9f Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 17 Apr 2025 11:07:05 -0400 Subject: [PATCH 06/71] I'm not an animal - I can save mem --- .../MauiGestureRecognizerEventsBinder.cs | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs index ef48ad6ec4..82a52aed9d 100644 --- a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs @@ -5,20 +5,29 @@ namespace Sentry.Maui.Internal; /// public class MauiGestureRecognizerEventsBinder : IMauiElementEventBinder { - private Action _addBreadcrumb = null!; + private static Action? _addBreadcrumb = null!; /// /// Searches VisualElement for gesture recognizers to bind to /// - public void Bind(VisualElement element, Action addBreadcrumb) => TryBind(element, true); + public void Bind(VisualElement element, Action addBreadcrumb) + { + _addBreadcrumb ??= addBreadcrumb; // this is fine... it's the same callback for everyone and it never changes + TryBind(element, true); + } + /// /// Searches VisualElement for gesture recognizers to unbind from /// /// - public void UnBind(VisualElement element) => TryBind(element, false); + public void UnBind(VisualElement element) + { + _addBreadcrumb = null; + TryBind(element, false); + } - private void TryBind(VisualElement element, bool bind) + private static void TryBind(VisualElement element, bool bind) { if (element is IGestureRecognizers recognizers) { @@ -30,7 +39,7 @@ private void TryBind(VisualElement element, bool bind) } - private void SetHooks(IGestureRecognizer recognizer, bool bind) + private static void SetHooks(IGestureRecognizer recognizer, bool bind) { switch (recognizer) { @@ -112,31 +121,31 @@ private void SetHooks(IGestureRecognizer recognizer, bool bind) } } - private void OnPointerReleasedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnPointerReleasedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(PointerGestureRecognizer.PointerReleased), ToPointerData(e) )); - private void OnPointerPressedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnPointerPressedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(PointerGestureRecognizer.PointerPressed), ToPointerData(e) )); - private void OnPointerMovedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnPointerMovedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(PointerGestureRecognizer.PointerMoved), ToPointerData(e) )); - private void OnPointerExitedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnPointerExitedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(PointerGestureRecognizer.PointerExited), ToPointerData(e) )); - private void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(PointerGestureRecognizer.PointerEntered), ToPointerData(e) @@ -154,7 +163,7 @@ private void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _add #endif ]; - private void OnPanGesture(object? sender, PanUpdatedEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnPanGesture(object? sender, PanUpdatedEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(PanGestureRecognizer.PanUpdated), [ @@ -165,18 +174,18 @@ private void OnPanGesture(object? sender, PanUpdatedEventArgs e) => _addBreadcru ] )); - private void OnDropCompletedGesture(object? sender, DropCompletedEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnDropCompletedGesture(object? sender, DropCompletedEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(DragGestureRecognizer.DropCompleted) )); - private void OnDragStartingGesture(object? sender, DragStartingEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnDragStartingGesture(object? sender, DragStartingEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(DragGestureRecognizer.DragStarting) )); - private void OnPinchGesture(object? sender, PinchGestureUpdatedEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnPinchGesture(object? sender, PinchGestureUpdatedEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(PinchGestureRecognizer.PinchUpdated), [ @@ -186,13 +195,13 @@ private void OnPinchGesture(object? sender, PinchGestureUpdatedEventArgs e) => _ ] )); - private void OnSwipeGesture(object? sender, SwipedEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnSwipeGesture(object? sender, SwipedEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(SwipeGestureRecognizer.Swiped), [("Direction", e.Direction.ToString())] )); - private void OnTapGesture(object? sender, TappedEventArgs e) => _addBreadcrumb.Invoke(new( + private static void OnTapGesture(object? sender, TappedEventArgs e) => _addBreadcrumb?.Invoke(new( sender, nameof(TapGestureRecognizer.Tapped), [("ButtonMask", e.Buttons.ToString())] From 35e769c02fee840f4aec8099e96b8fff44ef594d Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 17 Apr 2025 15:19:24 +0000 Subject: [PATCH 07/71] Format code --- src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs index 82a52aed9d..9a4c654927 100644 --- a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs @@ -155,10 +155,10 @@ private static void OnPointerEnteredGesture(object? sender, PointerEventArgs e) [ // some of the data here may have some challenges being pulled out #if ANDROID - ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? String.Empty) + ("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? string.Empty) //("MotionEventActionButton", e.PlatformArgs?.MotionEvent.ActionButton.ToString() ?? String.Empty) #elif IOS - ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? String.Empty), + ("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? string.Empty), //("ButtonMask", e.PlatformArgs?.GestureRecognizer.ButtonMask.ToString() ?? String.Empty) #endif ]; From 017f7f19236a4fd863144b1580059e3c6158dedb Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 17 Apr 2025 11:21:47 -0400 Subject: [PATCH 08/71] Command instrumentation --- .../CtMvvmMauiElementEventBinder.cs | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs index 5930eda878..3a49712697 100644 --- a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs +++ b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs @@ -23,7 +23,7 @@ public class CtMvvmMauiElementEventBinder : IMauiElementEventBinder public void UnBind(VisualElement element) => Iterate(element, false); - void Iterate(VisualElement element, bool bind) + private static void Iterate(VisualElement element, bool bind) { switch (element) { @@ -34,17 +34,72 @@ void Iterate(VisualElement element, bool bind) case ImageButton imageButton: TryBindTo(imageButton.Command, bind); break; - + + case CarouselView carousel: + TryBindTo(carousel.CurrentItemChangedCommand, bind); + TryBindTo(carousel.RemainingItemsThresholdReachedCommand, bind); + TryBindTo(carousel.PositionChangedCommand, bind); + break; + + case CollectionView collectionView: + TryBindTo(collectionView.RemainingItemsThresholdReachedCommand, bind); + TryBindTo(collectionView.SelectionChangedCommand, bind); + break; + + case Entry entry: + TryBindTo(entry.ReturnCommand, bind); + break; + + case SearchBar searchBar: + TryBindTo(searchBar.SearchCommand, bind); + break; + + default: + TryGestureBinding(element, bind); + break; } + } + private static void TryGestureBinding(VisualElement element, bool bind) + { if (element is IGestureRecognizers gestureRecognizers) { foreach (var gestureRecognizer in gestureRecognizers.GestureRecognizers) { + TryBindTo(gestureRecognizer, bind); } } } + private static void TryBindTo(IGestureRecognizer recognizer, bool bind) + { + switch (recognizer) + { + case TapGestureRecognizer tap: + TryBindTo(tap.Command, bind); + break; + + case SwipeGestureRecognizer swipe: + TryBindTo(swipe.Command, bind); + break; + + // no commands + //case PinchGestureRecognizer pinch + //case PanGestureRecognizer pan + case DragGestureRecognizer drag: + TryBindTo(drag.DragStartingCommand, bind); // unlikely to ever be async + TryBindTo(drag.DropCompletedCommand, bind); + break; + + case PointerGestureRecognizer pointer: + TryBindTo(pointer.PointerPressedCommand, bind); + TryBindTo(pointer.PointerReleasedCommand, bind); + TryBindTo(pointer.PointerEnteredCommand, bind); // unlikely to ever be async + TryBindTo(pointer.PointerExitedCommand, bind); + break; + } + } + private static void TryBindTo(ICommand? command, bool bind) { if (command is IAsyncRelayCommand relayCommand) @@ -67,11 +122,11 @@ private static void RelayCommandOnPropertyChanged(object? sender, PropertyChange var relay = (IAsyncRelayCommand)sender!; if (relay.IsRunning) { - // start span + // TODO: start span (transaction?) } else { - // finish span + // TODO: finish span (transaction?) } } } From 006abec6d0e59fb9d8356b53294db0d8657b9585 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 17 Apr 2025 15:39:13 +0000 Subject: [PATCH 09/71] Format code --- .../CtMvvmMauiElementEventBinder.cs | 2 +- .../MauiAppBuilderExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs index 3a49712697..3cc9bccefb 100644 --- a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs +++ b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs @@ -13,7 +13,7 @@ public class CtMvvmMauiElementEventBinder : IMauiElementEventBinder /// /// /// - public void Bind(VisualElement element, Action addBreadcrumb) => Iterate(element, true); + public void Bind(VisualElement element, Action addBreadcrumb) => Iterate(element, true); /// /// Unbinds from the element diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs b/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs index 4f209e6bf6..f818e244d5 100644 --- a/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs @@ -1,4 +1,4 @@ -using Sentry.Maui.CommunityToolkitMvvm; +using Sentry.Maui.CommunityToolkitMvvm; namespace Sentry.Maui; From f76f1326a7531d6689c8ce0bc4ea3fab165585da Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 24 Apr 2025 13:01:46 -0400 Subject: [PATCH 10/71] Fixes from live usage in our sample app --- .../CtMvvmMauiElementEventBinder.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs index 3cc9bccefb..4b1f0bd47d 100644 --- a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs +++ b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs @@ -50,6 +50,10 @@ private static void Iterate(VisualElement element, bool bind) TryBindTo(entry.ReturnCommand, bind); break; + case RefreshView refresh: + TryBindTo(refresh.Command, bind); + break; + case SearchBar searchBar: TryBindTo(searchBar.SearchCommand, bind); break; @@ -100,33 +104,47 @@ private static void TryBindTo(IGestureRecognizer recognizer, bool bind) } } + static List _refs = []; private static void TryBindTo(ICommand? command, bool bind) { if (command is IAsyncRelayCommand relayCommand) { if (bind) { - relayCommand.PropertyChanged += RelayCommandOnPropertyChanged; + // necessary for collectionview buttons + if (!_refs.Contains(relayCommand)) + { + _refs.Add(relayCommand); + relayCommand.PropertyChanged += RelayCommandOnPropertyChanged; + } } else { + _refs.Remove(relayCommand); relayCommand.PropertyChanged -= RelayCommandOnPropertyChanged; } } } + static ConcurrentDictionary _contexts = new(); + private static void RelayCommandOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(IAsyncRelayCommand.IsRunning)) { var relay = (IAsyncRelayCommand)sender!; + if (relay.IsRunning) { - // TODO: start span (transaction?) + var transaction = SentrySdk.StartTransaction("ctmvvm", "asynccommand"); + var span = transaction.StartChild("run"); + _contexts.TryAdd(relay, (transaction, span)); } - else + else if (_contexts.TryGetValue(relay, out var value)) { - // TODO: finish span (transaction?) + value.Span.Finish(); + value.Transaction.Finish(); + _contexts.TryRemove(relay, out _); } } } From 1943f222d036da128cde7be53337f8e711fcd1cf Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 24 Apr 2025 17:17:30 +0000 Subject: [PATCH 11/71] Format code --- .../CtMvvmMauiElementEventBinder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs index 4b1f0bd47d..d11ff82784 100644 --- a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs +++ b/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs @@ -104,7 +104,7 @@ private static void TryBindTo(IGestureRecognizer recognizer, bool bind) } } - static List _refs = []; + private static List _refs = []; private static void TryBindTo(ICommand? command, bool bind) { if (command is IAsyncRelayCommand relayCommand) @@ -126,7 +126,7 @@ private static void TryBindTo(ICommand? command, bool bind) } } - static ConcurrentDictionary _contexts = new(); + private static ConcurrentDictionary _contexts = new(); private static void RelayCommandOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { From 66879376811711e4a9adce7481fdedb4816ef51a Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 30 Apr 2025 09:49:57 -0400 Subject: [PATCH 12/71] Update according to code review --- Sentry.sln | 2 +- SentryMobile.slnf | 2 +- .../CtMvvmMauiElementEventBinder.cs | 39 +++++++------------ .../MauiAppBuilderExtensions.cs | 0 .../Sentry.Maui.CommunityToolkit.Mvvm.csproj} | 1 + 5 files changed, 18 insertions(+), 26 deletions(-) rename src/{Sentry.Maui.CommunityToolkitMvvm => Sentry.Maui.CommunityToolkit.Mvvm}/CtMvvmMauiElementEventBinder.cs (73%) rename src/{Sentry.Maui.CommunityToolkitMvvm => Sentry.Maui.CommunityToolkit.Mvvm}/MauiAppBuilderExtensions.cs (100%) rename src/{Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj => Sentry.Maui.CommunityToolkit.Mvvm/Sentry.Maui.CommunityToolkit.Mvvm.csproj} (93%) diff --git a/Sentry.sln b/Sentry.sln index b4c2d29661..6b07ab0847 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -197,7 +197,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.TrimTest", "test\Sen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.MauiTrimTest", "test\Sentry.MauiTrimTest\Sentry.MauiTrimTest.csproj", "{DF92E098-822C-4B94-B96B-56BFB91FBB54}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui.CommunityToolkitMvvm", "src\Sentry.Maui.CommunityToolkitMvvm\Sentry.Maui.CommunityToolkitMvvm.csproj", "{E898F337-E982-46CC-8DA9-F8556AA7DD72}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Maui.CommunityToolkit.Mvvm", "src\Sentry.Maui.CommunityToolkit.Mvvm\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "{E898F337-E982-46CC-8DA9-F8556AA7DD72}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/SentryMobile.slnf b/SentryMobile.slnf index fe33528eb6..4ac376a231 100644 --- a/SentryMobile.slnf +++ b/SentryMobile.slnf @@ -11,7 +11,7 @@ "src\\Sentry.Bindings.Android\\Sentry.Bindings.Android.csproj", "src\\Sentry.Bindings.Cocoa\\Sentry.Bindings.Cocoa.csproj", "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", - "src\\Sentry.Maui.CommunityToolkitMvvm\\Sentry.Maui.CommunityToolkitMvvm.csproj", + "src\\Sentry.Maui.CommunityToolkit.Mvvm\\Sentry.Maui.CommunityToolkit.Mvvm.csproj", "src\\Sentry.Maui\\Sentry.Maui.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs b/src/Sentry.Maui.CommunityToolkit.Mvvm/CtMvvmMauiElementEventBinder.cs similarity index 73% rename from src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs rename to src/Sentry.Maui.CommunityToolkit.Mvvm/CtMvvmMauiElementEventBinder.cs index d11ff82784..3d0370fc93 100644 --- a/src/Sentry.Maui.CommunityToolkitMvvm/CtMvvmMauiElementEventBinder.cs +++ b/src/Sentry.Maui.CommunityToolkit.Mvvm/CtMvvmMauiElementEventBinder.cs @@ -6,7 +6,7 @@ namespace Sentry.Maui.CommunityToolkitMvvm; /// /// Scans all elements for known commands that are implement /// -public class CtMvvmMauiElementEventBinder : IMauiElementEventBinder +public class CtMvvmMauiElementEventBinder(IHub hub) : IMauiElementEventBinder { /// /// Binds to the element @@ -23,7 +23,7 @@ public class CtMvvmMauiElementEventBinder : IMauiElementEventBinder public void UnBind(VisualElement element) => Iterate(element, false); - private static void Iterate(VisualElement element, bool bind) + private void Iterate(VisualElement element, bool bind) { switch (element) { @@ -64,7 +64,7 @@ private static void Iterate(VisualElement element, bool bind) } } - private static void TryGestureBinding(VisualElement element, bool bind) + private void TryGestureBinding(VisualElement element, bool bind) { if (element is IGestureRecognizers gestureRecognizers) { @@ -75,7 +75,7 @@ private static void TryGestureBinding(VisualElement element, bool bind) } } - private static void TryBindTo(IGestureRecognizer recognizer, bool bind) + private void TryBindTo(IGestureRecognizer recognizer, bool bind) { switch (recognizer) { @@ -104,31 +104,24 @@ private static void TryBindTo(IGestureRecognizer recognizer, bool bind) } } - private static List _refs = []; - private static void TryBindTo(ICommand? command, bool bind) + private void TryBindTo(ICommand? command, bool bind) { if (command is IAsyncRelayCommand relayCommand) { + // since events can retrigger binding pickups, we want to ensure we unhook any previous event handlers + // instead of storing a ref to know if we've already bound to an event or not + relayCommand.PropertyChanged -= RelayCommandOnPropertyChanged; if (bind) { - // necessary for collectionview buttons - if (!_refs.Contains(relayCommand)) - { - _refs.Add(relayCommand); - relayCommand.PropertyChanged += RelayCommandOnPropertyChanged; - } - } - else - { - _refs.Remove(relayCommand); - relayCommand.PropertyChanged -= RelayCommandOnPropertyChanged; + relayCommand.PropertyChanged += RelayCommandOnPropertyChanged; } } } - private static ConcurrentDictionary _contexts = new(); - private static void RelayCommandOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + private readonly ConcurrentDictionary _contexts = new(); + + private void RelayCommandOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(IAsyncRelayCommand.IsRunning)) { @@ -136,14 +129,12 @@ private static void RelayCommandOnPropertyChanged(object? sender, PropertyChange if (relay.IsRunning) { - var transaction = SentrySdk.StartTransaction("ctmvvm", "asynccommand"); - var span = transaction.StartChild("run"); - _contexts.TryAdd(relay, (transaction, span)); + var transaction = hub.StartTransaction("ctmvvm", "relay.command"); + _contexts.TryAdd(relay, transaction); } else if (_contexts.TryGetValue(relay, out var value)) { - value.Span.Finish(); - value.Transaction.Finish(); + value.Finish(); _contexts.TryRemove(relay, out _); } } diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs b/src/Sentry.Maui.CommunityToolkit.Mvvm/MauiAppBuilderExtensions.cs similarity index 100% rename from src/Sentry.Maui.CommunityToolkitMvvm/MauiAppBuilderExtensions.cs rename to src/Sentry.Maui.CommunityToolkit.Mvvm/MauiAppBuilderExtensions.cs diff --git a/src/Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj b/src/Sentry.Maui.CommunityToolkit.Mvvm/Sentry.Maui.CommunityToolkit.Mvvm.csproj similarity index 93% rename from src/Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj rename to src/Sentry.Maui.CommunityToolkit.Mvvm/Sentry.Maui.CommunityToolkit.Mvvm.csproj index 7613389dcb..3da95b18da 100644 --- a/src/Sentry.Maui.CommunityToolkitMvvm/Sentry.Maui.CommunityToolkitMvvm.csproj +++ b/src/Sentry.Maui.CommunityToolkit.Mvvm/Sentry.Maui.CommunityToolkit.Mvvm.csproj @@ -10,6 +10,7 @@ true + Sentry.Maui.CommunityToolkitMvvm From 8059884cd643bc23d594781d9dff01dae23c8e4e Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 30 Apr 2025 10:47:37 -0400 Subject: [PATCH 13/71] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22b7ee7380..15860866f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - Custom SessionReplay masks in MAUI Android apps ([#4121](https://github.com/getsentry/sentry-dotnet/pull/4121)) +- .NET MAUI integration with CommunityToolkit.Mvvm Async Relay Commands can now be auto spanned with the new package Sentry.Maui.CommunityToolkit.Mvvm ([#4125](https://github.com/getsentry/sentry-dotnet/pull/4125)) ### Fixes From 658605027bab280f186a0c4b42e881683cba44e3 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 30 Apr 2025 10:59:47 -0400 Subject: [PATCH 14/71] Update MauiGestureRecognizerEventsBinder.cs --- .../MauiGestureRecognizerEventsBinder.cs | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs index 9a4c654927..f0f2e53d1f 100644 --- a/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs @@ -1,7 +1,7 @@ namespace Sentry.Maui.Internal; /// -/// Binds to +/// Detects and breadcrumbs any gesture recognizers attached to the visual element /// public class MauiGestureRecognizerEventsBinder : IMauiElementEventBinder { @@ -44,63 +44,59 @@ private static void SetHooks(IGestureRecognizer recognizer, bool bind) switch (recognizer) { case TapGestureRecognizer tap: + tap.Tapped -= OnTapGesture; + if (bind) { tap.Tapped += OnTapGesture; } - else - { - tap.Tapped -= OnTapGesture; - } break; case SwipeGestureRecognizer swipe: + swipe.Swiped -= OnSwipeGesture; + if (bind) { swipe.Swiped += OnSwipeGesture; } - else - { - swipe.Swiped -= OnSwipeGesture; - } break; case PinchGestureRecognizer pinch: + pinch.PinchUpdated -= OnPinchGesture; + if (bind) { pinch.PinchUpdated += OnPinchGesture; } - else - { - pinch.PinchUpdated -= OnPinchGesture; - } break; case DragGestureRecognizer drag: + drag.DragStarting -= OnDragStartingGesture; + drag.DropCompleted -= OnDropCompletedGesture; + if (bind) { drag.DragStarting += OnDragStartingGesture; drag.DropCompleted += OnDropCompletedGesture; } - else - { - drag.DragStarting -= OnDragStartingGesture; - drag.DropCompleted -= OnDropCompletedGesture; - } break; case PanGestureRecognizer pan: + pan.PanUpdated -= OnPanGesture; + if (bind) { pan.PanUpdated += OnPanGesture; } - else - { - pan.PanUpdated -= OnPanGesture; - } break; case PointerGestureRecognizer pointer: + pointer.PointerEntered -= OnPointerEnteredGesture; + pointer.PointerExited -= OnPointerExitedGesture; + pointer.PointerMoved -= OnPointerMovedGesture; + pointer.PointerPressed -= OnPointerPressedGesture; + pointer.PointerReleased -= OnPointerReleasedGesture; + if (bind) { pointer.PointerEntered += OnPointerEnteredGesture; @@ -109,14 +105,6 @@ private static void SetHooks(IGestureRecognizer recognizer, bool bind) pointer.PointerPressed += OnPointerPressedGesture; pointer.PointerReleased += OnPointerReleasedGesture; } - else - { - pointer.PointerEntered -= OnPointerEnteredGesture; - pointer.PointerExited -= OnPointerExitedGesture; - pointer.PointerMoved -= OnPointerMovedGesture; - pointer.PointerPressed -= OnPointerPressedGesture; - pointer.PointerReleased -= OnPointerReleasedGesture; - } break; } } From d1d5089e7a1de5b996b5b45310ec07633be1492f Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 30 Apr 2025 14:57:03 -0400 Subject: [PATCH 15/71] Create MauiEventsBinderTests.GestureRecognizers.cs --- ...auiEventsBinderTests.GestureRecognizers.cs | 284 ++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs new file mode 100644 index 0000000000..30ff56e5a6 --- /dev/null +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs @@ -0,0 +1,284 @@ +using Sentry.Maui.Internal; + +namespace Sentry.Maui.Tests; + +public partial class MauiEventsBinderTests +{ + [Fact] + public void TapGestureRecognizer_LifecycleEvents_AddsBreadcrumb() + { + // Arrange + var image = new Image(); + var gesture = new TapGestureRecognizer(); + image.GestureRecognizers.Add(gesture); + + _fixture.Binder.HandleElementEvents(image); + + // Act + gesture.RaiseEvent(nameof(TapGestureRecognizer.Tapped), EventArgs.Empty); + + // Assert + var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); + Assert.Equal($"{nameof(Window)}.{nameof(TapGestureRecognizer.Tapped)}", crumb.Message); + Assert.Equal(BreadcrumbLevel.Info, crumb.Level); + Assert.Equal(MauiEventsBinder.SystemType, crumb.Type); + Assert.Equal(MauiEventsBinder.LifecycleCategory, crumb.Category); + crumb.Data.Should().Contain($"{nameof(TapGestureRecognizer)}.Name", "tapgesturerecognizer"); + } + + // [Theory] + // [InlineData(nameof(Window.Activated))] + // [InlineData(nameof(Window.Deactivated))] + // [InlineData(nameof(Window.Stopped))] + // [InlineData(nameof(Window.Resumed))] + // [InlineData(nameof(Window.Created))] + // [InlineData(nameof(Window.Destroying))] + // public void Window_UnbindLifecycleEvents_DoesNotAddBreadcrumb(string eventName) + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // + // window.RaiseEvent(eventName, EventArgs.Empty); + // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check + // + // _fixture.Binder.HandleWindowEvents(window, bind: false); + // + // // Act + // window.RaiseEvent(eventName, EventArgs.Empty); + // + // // Assert + // Assert.Single(_fixture.Scope.Breadcrumbs); + // } + // + // [Theory] + // [InlineData(true)] + // [InlineData(false)] + // public void Window_Backgrounding_AddsBreadcrumb(bool includeStateInBreadcrumb) + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // _fixture.Options.IncludeBackgroundingStateInBreadcrumbs = includeStateInBreadcrumb; + // + // var state = new PersistedState + // { + // ["Foo"] = "123", + // ["Bar"] = "", + // ["Baz"] = null + // }; + // + // // Act + // window.RaiseEvent(nameof(Window.Backgrounding), new BackgroundingEventArgs(state)); + // + // // Assert + // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); + // Assert.Equal($"{nameof(Window)}.{nameof(Window.Backgrounding)}", crumb.Message); + // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); + // Assert.Equal(MauiEventsBinder.SystemType, crumb.Type); + // Assert.Equal(MauiEventsBinder.LifecycleCategory, crumb.Category); + // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); + // + // if (includeStateInBreadcrumb) + // { + // crumb.Data.Should().Contain("State.Foo", "123"); + // crumb.Data.Should().Contain("State.Bar", ""); + // crumb.Data.Should().Contain("State.Baz", ""); + // } + // else + // { + // crumb.Data.Should().NotContain(kvp => kvp.Key.StartsWith("State.")); + // } + // } + // + // [Fact] + // public void Window_UnbindBackgrounding_DoesNotAddBreadcrumb() + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // _fixture.Options.IncludeBackgroundingStateInBreadcrumbs = true; + // + // var state = new PersistedState + // { + // ["Foo"] = "123", + // ["Bar"] = "", + // ["Baz"] = null + // }; + // + // window.RaiseEvent(nameof(Window.Backgrounding), new BackgroundingEventArgs(state)); + // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check + // + // _fixture.Binder.HandleWindowEvents(window, bind: false); + // + // // Act + // window.RaiseEvent(nameof(Window.Backgrounding), new BackgroundingEventArgs(state)); + // + // // Assert + // Assert.Single(_fixture.Scope.Breadcrumbs); + // } + // + // [Fact] + // public void Window_DisplayDensityChanged_AddsBreadcrumb() + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // + // // Act + // window.RaiseEvent(nameof(Window.DisplayDensityChanged), new DisplayDensityChangedEventArgs(1.25f)); + // + // // Assert + // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); + // Assert.Equal($"{nameof(Window)}.{nameof(Window.DisplayDensityChanged)}", crumb.Message); + // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); + // Assert.Equal(MauiEventsBinder.SystemType, crumb.Type); + // Assert.Equal(MauiEventsBinder.LifecycleCategory, crumb.Category); + // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); + // crumb.Data.Should().Contain("DisplayDensity", "1.25"); + // } + // + // [Fact] + // public void Window_UnbindDisplayDensityChanged_DoesNotAddBreadcrumb() + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // + // window.RaiseEvent(nameof(Window.DisplayDensityChanged), new DisplayDensityChangedEventArgs(1.25f)); + // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check + // + // _fixture.Binder.HandleWindowEvents(window, bind: false); + // + // // Act + // window.RaiseEvent(nameof(Window.DisplayDensityChanged), new DisplayDensityChangedEventArgs(1.0f)); + // + // // Assert + // Assert.Single(_fixture.Scope.Breadcrumbs); + // } + // + // [Fact] + // public void Window_PopCanceled_AddsBreadcrumb() + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // + // // Act + // window.RaiseEvent(nameof(Window.PopCanceled), EventArgs.Empty); + // + // // Assert + // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); + // Assert.Equal($"{nameof(Window)}.{nameof(Window.PopCanceled)}", crumb.Message); + // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); + // Assert.Equal(MauiEventsBinder.NavigationType, crumb.Type); + // Assert.Equal(MauiEventsBinder.NavigationCategory, crumb.Category); + // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); + // } + // + // [Fact] + // public void Window_UnbindPopCanceled_DoesNotAddBreadcrumb() + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // + // window.RaiseEvent(nameof(Window.PopCanceled), EventArgs.Empty); + // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check + // + // _fixture.Binder.HandleWindowEvents(window, bind: false); + // + // // Act + // window.RaiseEvent(nameof(Window.PopCanceled), EventArgs.Empty); + // + // // Assert + // Assert.Single(_fixture.Scope.Breadcrumbs); + // } + // + // [Theory] + // [MemberData(nameof(WindowModalEventsData))] + // public void Window_ModalEvents_AddsBreadcrumb(string eventName, object eventArgs) + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // + // // Act + // window.RaiseEvent(eventName, eventArgs); + // + // // Assert + // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); + // Assert.Equal($"{nameof(Window)}.{eventName}", crumb.Message); + // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); + // Assert.Equal(MauiEventsBinder.NavigationType, crumb.Type); + // Assert.Equal(MauiEventsBinder.NavigationCategory, crumb.Category); + // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); + // crumb.Data.Should().Contain("Modal", nameof(ContentPage)); + // crumb.Data.Should().Contain("Modal.Name", "TestModalPage"); + // } + // + // [Theory] + // [MemberData(nameof(WindowModalEventsData))] + // public void Window_UnbindModalEvents_DoesNotAddBreadcrumb(string eventName, object eventArgs) + // { + // // Arrange + // var window = new Window + // { + // StyleId = "window" + // }; + // _fixture.Binder.HandleWindowEvents(window); + // + // window.RaiseEvent(eventName, eventArgs); + // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check + // + // _fixture.Binder.HandleWindowEvents(window, bind: false); + // + // // Act + // window.RaiseEvent(eventName, eventArgs); + // + // // Assert + // Assert.Single(_fixture.Scope.Breadcrumbs); + // } + // + // public static IEnumerable WindowModalEventsData + // { + // get + // { + // var modelPage = new ContentPage + // { + // StyleId = "TestModalPage" + // }; + // + // return new List + // { + // // Note, these are distinct from the Application events with the same names. + // new object[] {nameof(Window.ModalPushed), new ModalPushedEventArgs(modelPage)}, + // new object[] {nameof(Window.ModalPopped), new ModalPoppedEventArgs(modelPage)} + // }; + // } + // } +} From efcd6735871450ac0973a819295f39db39032cf3 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 30 Apr 2025 16:16:41 -0400 Subject: [PATCH 16/71] Update MauiEventsBinderTests.GestureRecognizers.cs --- ...auiEventsBinderTests.GestureRecognizers.cs | 334 ++++-------------- 1 file changed, 73 insertions(+), 261 deletions(-) diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs index 30ff56e5a6..48ba343e95 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.GestureRecognizers.cs @@ -7,278 +7,90 @@ public partial class MauiEventsBinderTests [Fact] public void TapGestureRecognizer_LifecycleEvents_AddsBreadcrumb() { - // Arrange - var image = new Image(); var gesture = new TapGestureRecognizer(); + TestGestureRecognizer( + gesture, + nameof(TapGestureRecognizer.Tapped), + new TappedEventArgs(gesture) + ); + } + + + [Fact] + public void SwipeGestureRecognizer_LifecycleEvents_AddsBreadcrumb() + { + var gesture = new SwipeGestureRecognizer(); + TestGestureRecognizer( + gesture, + nameof(SwipeGestureRecognizer.Swiped), + new SwipedEventArgs(gesture, SwipeDirection.Down) + ); + } + + [Fact] + public void PinchGestureRecognizer_LifecycleEvents_AddsBreadcrumb() + { + TestGestureRecognizer( + new PinchGestureRecognizer(), + nameof(PinchGestureRecognizer.PinchUpdated), + new PinchGestureUpdatedEventArgs(GestureStatus.Completed, 0, Point.Zero) + ); + } + + [Theory] + [InlineData(nameof(DragGestureRecognizer.DragStarting))] + [InlineData(nameof(DragGestureRecognizer.DropCompleted))] + public void DragGestureRecognizer_LifecycleEvents_AddsBreadcrumb(string eventName) + { + TestGestureRecognizer( + new DragGestureRecognizer(), + eventName, + DragStartingEventArgs.Empty + ); + } + + [Fact] + public void PanGestureRecognizer_LifecycleEvents_AddsBreadcrumb() + { + TestGestureRecognizer( + new PanGestureRecognizer(), + nameof(PanGestureRecognizer.PanUpdated), + new PanUpdatedEventArgs(GestureStatus.Completed, 1, 0, 0) + ); + } + + [Theory] + [InlineData(nameof(PointerGestureRecognizer.PointerEntered))] + [InlineData(nameof(PointerGestureRecognizer.PointerExited))] + [InlineData(nameof(PointerGestureRecognizer.PointerMoved))] + [InlineData(nameof(PointerGestureRecognizer.PointerPressed))] + [InlineData(nameof(PointerGestureRecognizer.PointerReleased))] + public void PointerGestureRecognizer_LifecycleEvents_AddsBreadcrumb(string eventName) + { + TestGestureRecognizer( + new PointerGestureRecognizer(), + eventName, + PointerEventArgs.Empty + ); + } + + + void TestGestureRecognizer(GestureRecognizer gesture, string eventName, object eventArgs) + { + var image = new Image(); image.GestureRecognizers.Add(gesture); _fixture.Binder.HandleElementEvents(image); // Act - gesture.RaiseEvent(nameof(TapGestureRecognizer.Tapped), EventArgs.Empty); + gesture.RaiseEvent(eventName, eventArgs); // Assert var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); - Assert.Equal($"{nameof(Window)}.{nameof(TapGestureRecognizer.Tapped)}", crumb.Message); + Assert.Equal($"{gesture.GetType().Name}.{eventName}", crumb.Message); Assert.Equal(BreadcrumbLevel.Info, crumb.Level); Assert.Equal(MauiEventsBinder.SystemType, crumb.Type); Assert.Equal(MauiEventsBinder.LifecycleCategory, crumb.Category); - crumb.Data.Should().Contain($"{nameof(TapGestureRecognizer)}.Name", "tapgesturerecognizer"); + crumb.Data.Should().Contain($"{gesture.GetType().Name}.Name", "tapgesturerecognizer"); } - - // [Theory] - // [InlineData(nameof(Window.Activated))] - // [InlineData(nameof(Window.Deactivated))] - // [InlineData(nameof(Window.Stopped))] - // [InlineData(nameof(Window.Resumed))] - // [InlineData(nameof(Window.Created))] - // [InlineData(nameof(Window.Destroying))] - // public void Window_UnbindLifecycleEvents_DoesNotAddBreadcrumb(string eventName) - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // - // window.RaiseEvent(eventName, EventArgs.Empty); - // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check - // - // _fixture.Binder.HandleWindowEvents(window, bind: false); - // - // // Act - // window.RaiseEvent(eventName, EventArgs.Empty); - // - // // Assert - // Assert.Single(_fixture.Scope.Breadcrumbs); - // } - // - // [Theory] - // [InlineData(true)] - // [InlineData(false)] - // public void Window_Backgrounding_AddsBreadcrumb(bool includeStateInBreadcrumb) - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // _fixture.Options.IncludeBackgroundingStateInBreadcrumbs = includeStateInBreadcrumb; - // - // var state = new PersistedState - // { - // ["Foo"] = "123", - // ["Bar"] = "", - // ["Baz"] = null - // }; - // - // // Act - // window.RaiseEvent(nameof(Window.Backgrounding), new BackgroundingEventArgs(state)); - // - // // Assert - // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); - // Assert.Equal($"{nameof(Window)}.{nameof(Window.Backgrounding)}", crumb.Message); - // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); - // Assert.Equal(MauiEventsBinder.SystemType, crumb.Type); - // Assert.Equal(MauiEventsBinder.LifecycleCategory, crumb.Category); - // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); - // - // if (includeStateInBreadcrumb) - // { - // crumb.Data.Should().Contain("State.Foo", "123"); - // crumb.Data.Should().Contain("State.Bar", ""); - // crumb.Data.Should().Contain("State.Baz", ""); - // } - // else - // { - // crumb.Data.Should().NotContain(kvp => kvp.Key.StartsWith("State.")); - // } - // } - // - // [Fact] - // public void Window_UnbindBackgrounding_DoesNotAddBreadcrumb() - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // _fixture.Options.IncludeBackgroundingStateInBreadcrumbs = true; - // - // var state = new PersistedState - // { - // ["Foo"] = "123", - // ["Bar"] = "", - // ["Baz"] = null - // }; - // - // window.RaiseEvent(nameof(Window.Backgrounding), new BackgroundingEventArgs(state)); - // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check - // - // _fixture.Binder.HandleWindowEvents(window, bind: false); - // - // // Act - // window.RaiseEvent(nameof(Window.Backgrounding), new BackgroundingEventArgs(state)); - // - // // Assert - // Assert.Single(_fixture.Scope.Breadcrumbs); - // } - // - // [Fact] - // public void Window_DisplayDensityChanged_AddsBreadcrumb() - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // - // // Act - // window.RaiseEvent(nameof(Window.DisplayDensityChanged), new DisplayDensityChangedEventArgs(1.25f)); - // - // // Assert - // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); - // Assert.Equal($"{nameof(Window)}.{nameof(Window.DisplayDensityChanged)}", crumb.Message); - // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); - // Assert.Equal(MauiEventsBinder.SystemType, crumb.Type); - // Assert.Equal(MauiEventsBinder.LifecycleCategory, crumb.Category); - // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); - // crumb.Data.Should().Contain("DisplayDensity", "1.25"); - // } - // - // [Fact] - // public void Window_UnbindDisplayDensityChanged_DoesNotAddBreadcrumb() - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // - // window.RaiseEvent(nameof(Window.DisplayDensityChanged), new DisplayDensityChangedEventArgs(1.25f)); - // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check - // - // _fixture.Binder.HandleWindowEvents(window, bind: false); - // - // // Act - // window.RaiseEvent(nameof(Window.DisplayDensityChanged), new DisplayDensityChangedEventArgs(1.0f)); - // - // // Assert - // Assert.Single(_fixture.Scope.Breadcrumbs); - // } - // - // [Fact] - // public void Window_PopCanceled_AddsBreadcrumb() - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // - // // Act - // window.RaiseEvent(nameof(Window.PopCanceled), EventArgs.Empty); - // - // // Assert - // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); - // Assert.Equal($"{nameof(Window)}.{nameof(Window.PopCanceled)}", crumb.Message); - // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); - // Assert.Equal(MauiEventsBinder.NavigationType, crumb.Type); - // Assert.Equal(MauiEventsBinder.NavigationCategory, crumb.Category); - // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); - // } - // - // [Fact] - // public void Window_UnbindPopCanceled_DoesNotAddBreadcrumb() - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // - // window.RaiseEvent(nameof(Window.PopCanceled), EventArgs.Empty); - // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check - // - // _fixture.Binder.HandleWindowEvents(window, bind: false); - // - // // Act - // window.RaiseEvent(nameof(Window.PopCanceled), EventArgs.Empty); - // - // // Assert - // Assert.Single(_fixture.Scope.Breadcrumbs); - // } - // - // [Theory] - // [MemberData(nameof(WindowModalEventsData))] - // public void Window_ModalEvents_AddsBreadcrumb(string eventName, object eventArgs) - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // - // // Act - // window.RaiseEvent(eventName, eventArgs); - // - // // Assert - // var crumb = Assert.Single(_fixture.Scope.Breadcrumbs); - // Assert.Equal($"{nameof(Window)}.{eventName}", crumb.Message); - // Assert.Equal(BreadcrumbLevel.Info, crumb.Level); - // Assert.Equal(MauiEventsBinder.NavigationType, crumb.Type); - // Assert.Equal(MauiEventsBinder.NavigationCategory, crumb.Category); - // crumb.Data.Should().Contain($"{nameof(Window)}.Name", "window"); - // crumb.Data.Should().Contain("Modal", nameof(ContentPage)); - // crumb.Data.Should().Contain("Modal.Name", "TestModalPage"); - // } - // - // [Theory] - // [MemberData(nameof(WindowModalEventsData))] - // public void Window_UnbindModalEvents_DoesNotAddBreadcrumb(string eventName, object eventArgs) - // { - // // Arrange - // var window = new Window - // { - // StyleId = "window" - // }; - // _fixture.Binder.HandleWindowEvents(window); - // - // window.RaiseEvent(eventName, eventArgs); - // Assert.Single(_fixture.Scope.Breadcrumbs); // Sanity check - // - // _fixture.Binder.HandleWindowEvents(window, bind: false); - // - // // Act - // window.RaiseEvent(eventName, eventArgs); - // - // // Assert - // Assert.Single(_fixture.Scope.Breadcrumbs); - // } - // - // public static IEnumerable WindowModalEventsData - // { - // get - // { - // var modelPage = new ContentPage - // { - // StyleId = "TestModalPage" - // }; - // - // return new List - // { - // // Note, these are distinct from the Application events with the same names. - // new object[] {nameof(Window.ModalPushed), new ModalPushedEventArgs(modelPage)}, - // new object[] {nameof(Window.ModalPopped), new ModalPoppedEventArgs(modelPage)} - // }; - // } - // } } From 42268065d386ddb6390cc13e3eeb5aee621d26f0 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 1 May 2025 10:59:10 -0400 Subject: [PATCH 17/71] Initial Unit Tests and samples --- samples/Sentry.Samples.Maui/AppShell.xaml.cs | 1 + samples/Sentry.Samples.Maui/CtMvvmPage.xaml | 18 +++++++++ .../Sentry.Samples.Maui/CtMvvmPage.xaml.cs | 16 ++++++++ .../Sentry.Samples.Maui/CtMvvmViewModel.cs | 15 ++++++++ samples/Sentry.Samples.Maui/MainPage.xaml | 7 ++++ samples/Sentry.Samples.Maui/MainPage.xaml.cs | 5 +++ samples/Sentry.Samples.Maui/MauiProgram.cs | 5 +++ .../Sentry.Samples.Maui.csproj | 1 + .../MauiAppBuilderExtensions.cs | 2 +- ...ntsBinderTests.CtMvvmAsyncRelayCommands.cs | 38 +++++++++++++++++++ .../MauiEventsBinderTests.cs | 4 +- .../Sentry.Maui.Tests.csproj | 1 + 12 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 samples/Sentry.Samples.Maui/CtMvvmPage.xaml create mode 100644 samples/Sentry.Samples.Maui/CtMvvmPage.xaml.cs create mode 100644 samples/Sentry.Samples.Maui/CtMvvmViewModel.cs create mode 100644 test/Sentry.Maui.Tests/MauiEventsBinderTests.CtMvvmAsyncRelayCommands.cs diff --git a/samples/Sentry.Samples.Maui/AppShell.xaml.cs b/samples/Sentry.Samples.Maui/AppShell.xaml.cs index 234ec733a9..ee6c2db9fb 100644 --- a/samples/Sentry.Samples.Maui/AppShell.xaml.cs +++ b/samples/Sentry.Samples.Maui/AppShell.xaml.cs @@ -5,5 +5,6 @@ public partial class AppShell public AppShell() { InitializeComponent(); + Routing.RegisterRoute("ctmvvm", typeof(CtMvvmPage)); } } diff --git a/samples/Sentry.Samples.Maui/CtMvvmPage.xaml b/samples/Sentry.Samples.Maui/CtMvvmPage.xaml new file mode 100644 index 0000000000..9e7a483abe --- /dev/null +++ b/samples/Sentry.Samples.Maui/CtMvvmPage.xaml @@ -0,0 +1,18 @@ + + + + + + + + + +