Skip to content

Commit f04930d

Browse files
aritchiegetsentry-botjamescrosswell
authored
MAUI Gesture Recognizer Auto-Breadcrumbs (#4124)
* Create MauiGestureRecognizerEventsBinder.cs * Wireup * Format code * Cleanup and fix code due stupid bot * I'm not an animal - I can save mem * Format code * Update MauiGestureRecognizerEventsBinder.cs * Create MauiEventsBinderTests.GestureRecognizers.cs * Update MauiEventsBinderTests.GestureRecognizers.cs * Update MauiEventsBinderTests.cs * Update CHANGELOG.md * Format code * WIP * Update CHANGELOG.md * Bring over device tests to improve setup, fix gesture tests * These run every time in the visual runner * If working strictly from slnf - these don't get built and are dependencies for tests * Finally got these to generate * Format code * Update MauiEventsBinderTests.GestureRecognizers.cs * Improve testing setup * Update src/Sentry.Maui/Internal/MauiGestureRecognizerEventsBinder.cs Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com> * Update MauiGestureRecognizerEventsBinder.cs * Simplified conditional compilation of visual tests * Simplified TFMs in Maui.Device.TestApp --------- Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io> Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com>
1 parent f21135e commit f04930d

18 files changed

+418
-21
lines changed

.generated.NoMobile.sln

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ EndProject
104104
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry", "src\Sentry\Sentry.csproj", "{5F253D7F-BF27-46F5-9382-73A66EC247BF}"
105105
EndProject
106106
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6987A1CC-608E-4868-A02C-09D30C8B7B2D}"
107+
ProjectSection(SolutionItems) = preProject
108+
test\Directory.Build.props = test\Directory.Build.props
109+
test\Directory.Build.targets = test\Directory.Build.targets
110+
EndProjectSection
107111
EndProject
108112
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidTestApp", "test\AndroidTestApp\AndroidTestApp.csproj", "{99E2D1A4-1853-49F9-96B6-59C70FA3DE3A}"
109113
EndProject

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
- Redact Authorization headers before sending events to Sentry ([#4164](https://github.com/getsentry/sentry-dotnet/pull/4164))
1212
- Remove Strong Naming from Sentry.Hangfire ([#4099](https://github.com/getsentry/sentry-dotnet/pull/4099))
1313

14+
### Features
15+
16+
- New source generator allows Sentry to see true build variables like PublishAot and PublishTrimmed to properly adapt checks in the Sentry SDK ([#4101](https://github.com/getsentry/sentry-dotnet/pull/4101))
17+
- Auto breadcrumbs now include all .NET MAUI gesture recognizer events ([#4124](https://github.com/getsentry/sentry-dotnet/pull/4124))
18+
1419
### Dependencies
1520

1621
- Bump CLI from v2.43.1 to v2.45.0 ([#4169](https://github.com/getsentry/sentry-dotnet/pull/4169), [#4179](https://github.com/getsentry/sentry-dotnet/pull/4179))

Directory.Build.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@
106106
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" PublicKey="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
107107
</ItemGroup>
108108

109+
<!-- Helpful properties used elsewhere -->
110+
<PropertyGroup>
111+
<TargetFrameworkVersion>$([MSBuild]::GetTargetFrameworkVersion($(TargetFramework)))</TargetFrameworkVersion>
112+
<TargetFrameworkIsNet9OrGreater Condition="$([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), 9.0))">true</TargetFrameworkIsNet9OrGreater>
113+
</PropertyGroup>
109114
</Project>

Sentry.sln

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ EndProject
104104
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry", "src\Sentry\Sentry.csproj", "{5F253D7F-BF27-46F5-9382-73A66EC247BF}"
105105
EndProject
106106
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6987A1CC-608E-4868-A02C-09D30C8B7B2D}"
107+
ProjectSection(SolutionItems) = preProject
108+
test\Directory.Build.props = test\Directory.Build.props
109+
test\Directory.Build.targets = test\Directory.Build.targets
110+
EndProjectSection
107111
EndProject
108112
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidTestApp", "test\AndroidTestApp\AndroidTestApp.csproj", "{99E2D1A4-1853-49F9-96B6-59C70FA3DE3A}"
109113
EndProject

SentryMobile.slnf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
"src\\Sentry.Bindings.Cocoa\\Sentry.Bindings.Cocoa.csproj",
1313
"src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj",
1414
"src\\Sentry.Maui\\Sentry.Maui.csproj",
15+
"src\\Sentry.SourceGenerators\\Sentry.SourceGenerators.csproj",
1516
"src\\Sentry\\Sentry.csproj",
1617
"test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj",
1718
"test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj",
1819
"test\\Sentry.Maui.Device.TestApp\\Sentry.Maui.Device.TestApp.csproj",
1920
"test\\Sentry.Maui.Tests\\Sentry.Maui.Tests.csproj",
21+
"test\\Sentry.Testing.CrashableApp\\Sentry.Testing.CrashableApp.csproj",
2022
"test\\Sentry.Testing\\Sentry.Testing.csproj",
2123
"test\\Sentry.Tests\\Sentry.Tests.csproj"
2224
]

samples/Sentry.Samples.Maui/MainPage.xaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
sentry:SessionReplay.Mask="Unmask"
1616
SemanticProperties.Description="Cute dot net bot waving hi to you!"
1717
HeightRequest="200"
18-
HorizontalOptions="Center" />
18+
HorizontalOptions="Center">
19+
<Image.GestureRecognizers>
20+
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped" />
21+
</Image.GestureRecognizers>
22+
</Image>
1923

2024
<Label
2125
Text="Hello, World!"

samples/Sentry.Samples.Maui/MainPage.xaml.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,8 @@ private async void OnFeedbackClicked(object sender, EventArgs e)
100100
{
101101
await Navigation.PushModalAsync(new SubmitFeedback());
102102
}
103+
104+
private void TapGestureRecognizer_OnTapped(object sender, TappedEventArgs e)
105+
{
106+
}
103107
}

scripts/generate-solution-filters-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,13 @@ filterConfigs:
169169
- "samples/**/*Maui.csproj"
170170
- "src/**/Sentry.csproj"
171171
- "src/**/Sentry.Analyzers.csproj"
172+
- "src/**/Sentry.SourceGenerators.csproj"
172173
- "src/**/*Android*.csproj"
173174
- "src/**/*Bindings.Android.csproj"
174175
- "src/**/*Bindings.Cocoa.csproj"
175176
- "src/**/Sentry.Extensions.Logging.csproj"
176177
- "src/**/Sentry.Maui.csproj"
178+
- "test/**/Sentry.Testing.CrashableApp.csproj"
177179
- "test/**/Sentry.Android.AssemblyReader.Tests.csproj"
178180
- "test/**/Sentry.Extensions.Logging.Tests.csproj"
179181
- "test/**/Sentry.Maui.Device.TestApp.csproj"
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
namespace Sentry.Maui.Internal;
2+
3+
/// <summary>
4+
/// Detects and adds breadcrumbs for any gesture recognizers attached to the visual element
5+
/// </summary>
6+
public class MauiGestureRecognizerEventsBinder : IMauiElementEventBinder
7+
{
8+
private static Action<BreadcrumbEvent>? _addBreadcrumb = null!;
9+
10+
/// <summary>
11+
/// Searches VisualElement for gesture recognizers to bind to
12+
/// </summary>
13+
public void Bind(VisualElement element, Action<BreadcrumbEvent> addBreadcrumb)
14+
{
15+
_addBreadcrumb ??= addBreadcrumb; // this is fine... it's the same callback for everyone and it never changes
16+
TryBind(element, true);
17+
}
18+
19+
20+
/// <summary>
21+
/// Searches VisualElement for gesture recognizers to unbind from
22+
/// </summary>
23+
/// <param name="element"></param>
24+
public void UnBind(VisualElement element)
25+
{
26+
_addBreadcrumb = null;
27+
TryBind(element, false);
28+
}
29+
30+
private static void TryBind(VisualElement element, bool bind)
31+
{
32+
if (element is IGestureRecognizers recognizers)
33+
{
34+
foreach (var recognizer in recognizers.GestureRecognizers)
35+
{
36+
SetHooks(recognizer, bind);
37+
}
38+
}
39+
}
40+
41+
42+
private static void SetHooks(IGestureRecognizer recognizer, bool bind)
43+
{
44+
switch (recognizer)
45+
{
46+
case TapGestureRecognizer tap:
47+
tap.Tapped -= OnTapGesture;
48+
49+
if (bind)
50+
{
51+
tap.Tapped += OnTapGesture;
52+
}
53+
break;
54+
55+
case SwipeGestureRecognizer swipe:
56+
swipe.Swiped -= OnSwipeGesture;
57+
58+
if (bind)
59+
{
60+
swipe.Swiped += OnSwipeGesture;
61+
}
62+
break;
63+
64+
case PinchGestureRecognizer pinch:
65+
pinch.PinchUpdated -= OnPinchGesture;
66+
67+
if (bind)
68+
{
69+
pinch.PinchUpdated += OnPinchGesture;
70+
}
71+
break;
72+
73+
case DragGestureRecognizer drag:
74+
drag.DragStarting -= OnDragStartingGesture;
75+
drag.DropCompleted -= OnDropCompletedGesture;
76+
77+
if (bind)
78+
{
79+
drag.DragStarting += OnDragStartingGesture;
80+
drag.DropCompleted += OnDropCompletedGesture;
81+
}
82+
break;
83+
84+
case PanGestureRecognizer pan:
85+
pan.PanUpdated -= OnPanGesture;
86+
87+
if (bind)
88+
{
89+
pan.PanUpdated += OnPanGesture;
90+
}
91+
break;
92+
93+
case PointerGestureRecognizer pointer:
94+
pointer.PointerEntered -= OnPointerEnteredGesture;
95+
pointer.PointerExited -= OnPointerExitedGesture;
96+
pointer.PointerMoved -= OnPointerMovedGesture;
97+
pointer.PointerPressed -= OnPointerPressedGesture;
98+
pointer.PointerReleased -= OnPointerReleasedGesture;
99+
100+
if (bind)
101+
{
102+
pointer.PointerEntered += OnPointerEnteredGesture;
103+
pointer.PointerExited += OnPointerExitedGesture;
104+
pointer.PointerMoved += OnPointerMovedGesture;
105+
pointer.PointerPressed += OnPointerPressedGesture;
106+
pointer.PointerReleased += OnPointerReleasedGesture;
107+
}
108+
break;
109+
}
110+
}
111+
112+
private static void OnPointerReleasedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new(
113+
sender,
114+
nameof(PointerGestureRecognizer.PointerReleased),
115+
ToPointerData(e)
116+
));
117+
118+
private static void OnPointerPressedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new(
119+
sender,
120+
nameof(PointerGestureRecognizer.PointerPressed),
121+
ToPointerData(e)
122+
));
123+
124+
private static void OnPointerMovedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new(
125+
sender,
126+
nameof(PointerGestureRecognizer.PointerMoved),
127+
ToPointerData(e)
128+
));
129+
130+
private static void OnPointerExitedGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new(
131+
sender,
132+
nameof(PointerGestureRecognizer.PointerExited),
133+
ToPointerData(e)
134+
));
135+
136+
private static void OnPointerEnteredGesture(object? sender, PointerEventArgs e) => _addBreadcrumb?.Invoke(new(
137+
sender,
138+
nameof(PointerGestureRecognizer.PointerEntered),
139+
ToPointerData(e)
140+
));
141+
142+
private static IEnumerable<(string Key, string Value)> ToPointerData(PointerEventArgs e) =>
143+
[
144+
#if ANDROID
145+
("MotionEventAction", e.PlatformArgs?.MotionEvent.Action.ToString() ?? string.Empty)
146+
#elif IOS
147+
("State", e.PlatformArgs?.GestureRecognizer.State.ToString() ?? string.Empty)
148+
#endif
149+
];
150+
151+
private static void OnPanGesture(object? sender, PanUpdatedEventArgs e) => _addBreadcrumb?.Invoke(new(
152+
sender,
153+
nameof(PanGestureRecognizer.PanUpdated),
154+
[
155+
("GestureId", e.GestureId.ToString()),
156+
("StatusType", e.StatusType.ToString()),
157+
("TotalX", e.TotalX.ToString()),
158+
("TotalY", e.TotalY.ToString())
159+
]
160+
));
161+
162+
private static void OnDropCompletedGesture(object? sender, DropCompletedEventArgs e) => _addBreadcrumb?.Invoke(new(
163+
sender,
164+
nameof(DragGestureRecognizer.DropCompleted)
165+
));
166+
167+
private static void OnDragStartingGesture(object? sender, DragStartingEventArgs e) => _addBreadcrumb?.Invoke(new(
168+
sender,
169+
nameof(DragGestureRecognizer.DragStarting)
170+
));
171+
172+
173+
private static void OnPinchGesture(object? sender, PinchGestureUpdatedEventArgs e) => _addBreadcrumb?.Invoke(new(
174+
sender,
175+
nameof(PinchGestureRecognizer.PinchUpdated),
176+
[
177+
("GestureStatus", e.Status.ToString()),
178+
("Scale", e.Scale.ToString()),
179+
("ScaleOrigin", e.ScaleOrigin.ToString())
180+
]
181+
));
182+
183+
private static void OnSwipeGesture(object? sender, SwipedEventArgs e) => _addBreadcrumb?.Invoke(new(
184+
sender,
185+
nameof(SwipeGestureRecognizer.Swiped),
186+
[("Direction", e.Direction.ToString())]
187+
));
188+
189+
private static void OnTapGesture(object? sender, TappedEventArgs e) => _addBreadcrumb?.Invoke(new(
190+
sender,
191+
nameof(TapGestureRecognizer.Tapped),
192+
[("ButtonMask", e.Buttons.ToString())]
193+
));
194+
}

src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder,
5757

5858
services.AddSingleton<IMauiElementEventBinder, MauiButtonEventsBinder>();
5959
services.AddSingleton<IMauiElementEventBinder, MauiImageButtonEventsBinder>();
60+
services.AddSingleton<IMauiElementEventBinder, MauiGestureRecognizerEventsBinder>();
6061
services.AddSingleton<IMauiElementEventBinder, MauiVisualElementEventsBinder>();
6162
services.TryAddSingleton<IMauiEventsBinder, MauiEventsBinder>();
6263

0 commit comments

Comments
 (0)