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 <[email protected]> * Update MauiGestureRecognizerEventsBinder.cs * Simplified conditional compilation of visual tests * Simplified TFMs in Maui.Device.TestApp --------- Co-authored-by: Sentry Github Bot <[email protected]> Co-authored-by: James Crosswell <[email protected]>
1 parent f21135e commit f04930d

18 files changed

+418
-21
lines changed

.generated.NoMobile.sln

+4
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

+5
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

+5
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

+4
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

+2
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

+5-1
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

+4
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

+2
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"
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

+1
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

test/Directory.Build.props

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
<NoWarn>$(NoWarn);SYSLIB0005;SYSLIB0012</NoWarn>
1212
<!-- Ignore "Naming rule violation: Missing suffix: 'Async'" -->
1313
<NoWarn>$(NoWarn);IDE1006</NoWarn>
14+
15+
<!-- Visual tests need net9.0+ mobile and are flaky when using the headless runner on CI -->
16+
<EnableMauiDeviceTestVisualRunner>false</EnableMauiDeviceTestVisualRunner>
17+
<EnableMauiDeviceTestVisualRunner Condition="'$(TargetFrameworkIsNet9OrGreater)' == 'true' AND '$(PlatformIsMobile)' == 'true' AND '$(ContinuousIntegrationBuild)' != 'true'">true</EnableMauiDeviceTestVisualRunner>
1418
</PropertyGroup>
1519

1620
<!--

test/Sentry.Maui.Device.TestApp/Sentry.Maui.Device.TestApp.csproj

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks></TargetFrameworks>
5-
<TargetFrameworks Condition="'$(NO_ANDROID)' == ''">$(TargetFrameworks);net8.0-android;net9.0-android</TargetFrameworks>
6-
<TargetFrameworks Condition="'$(NO_IOS)' == '' And $([MSBuild]::IsOSPlatform('OSX'))">$(TargetFrameworks);net8.0-ios;net9.0-ios</TargetFrameworks>
7-
<!-- Pin target iOS version so that our tests don't break when new versions of Xcode are released.
8-
'net8.0-ios' resolves the latest version of the iOS SDK otherwise. -->
9-
<TargetPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">18</TargetPlatformVersion>
4+
<TargetFrameworks>$(TargetFrameworks);net8.0-android;net9.0-android</TargetFrameworks>
5+
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('OSX'))">$(TargetFrameworks);net8.0-ios;net9.0-ios</TargetFrameworks>
6+
<DefineConstants Condition="'$(EnableMauiDeviceTestVisualRunner)' == 'true'">$(DefineConstants);VISUAL_RUNNER</DefineConstants>
7+
</PropertyGroup>
8+
9+
<PropertyGroup>
1010
<UseMaui>true</UseMaui>
11-
<!-- Currently broken on .NET 7, see
12-
- https://github.com/dotnet/maui/issues/18573
13-
- https://developercommunity.visualstudio.com/t/MAUI0000:-SystemMissingMethodException:/10505327?sort=newest&ftype=problem
14-
<TargetFrameworks Condition="'$(NO_MACCATALYST)' == '' And $([MSBuild]::IsOSPlatform('OSX'))">$(TargetFrameworks);net7.0-maccatalyst</TargetFrameworks> -->
1511

1612
<OutputType>Exe</OutputType>
1713
<SingleProject>true</SingleProject>
@@ -36,7 +32,10 @@
3632

3733
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">13.0</SupportedOSPlatformVersion>
3834
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
39-
35+
<!-- Pin target iOS version so that our tests don't break when new versions of Xcode are released.
36+
'net8.0-ios' resolves the latest version of the iOS SDK otherwise. -->
37+
<TargetPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">18</TargetPlatformVersion>
38+
4039
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
4140

4241
<!-- Avoid errors of Sentry.Testing not being a self-contained executable, while including it in a self-contained executable (this).
@@ -88,11 +87,17 @@
8887
<ItemGroup>
8988
<PackageReference Include="DeviceRunners.XHarness.Maui" Version="0.1.0-preview.1"/>
9089
<PackageReference Include="DeviceRunners.XHarness.Xunit" Version="0.1.0-preview.1"/>
90+
9191
<!-- Prevent downgrade of these packages for DeviceRunners.XHarness.Xunit 0.1.0-preview.1 -->
9292
<PackageReference Include="System.Net.Primitives" Version="4.3.0"/>
9393
<PackageReference Include="System.IO.FileSystem" Version="4.3.0"/>
9494
</ItemGroup>
9595

96+
<ItemGroup Condition="'$(EnableMauiDeviceTestVisualRunner)' == 'true'">
97+
<PackageReference Include="DeviceRunners.VisualRunners.Maui" Version="0.1.0-preview.1"/>
98+
<PackageReference Include="DeviceRunners.VisualRunners.Xunit" Version="0.1.0-preview.1"/>
99+
</ItemGroup>
100+
96101
<ItemGroup>
97102
<ProjectReference Include="..\Sentry.Android.AssemblyReader.Tests\Sentry.Android.AssemblyReader.Tests.csproj" Condition="'$(TargetPlatformIdentifier)' == 'android'"/>
98103
<ProjectReference Include="..\Sentry.Tests\Sentry.Tests.csproj"/>

0 commit comments

Comments
 (0)