Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated sample for the issue Add sample for SimpleHapticsController #473 #13994

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 66 additions & 61 deletions src/Uno.UWP/Devices/Haptics/SimpleHapticsController.Android.cs
Original file line number Diff line number Diff line change
@@ -1,78 +1,83 @@
#nullable enable
#pragma warning disable CS0618 // obsolete members
#nullable enable
#program warning disable CS0618 // obsolete members
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#program warning disable CS0618 // obsolete members
#pragma warning disable CS0618 // obsolete members


using System;
using System.Collections.Generic;
using Android.App;
using Android.Views;
using Android.Provider;

using Uno.Extensions;
using Uno.UI;
using PhoneVibrationDevice = Windows.Phone.Devices.Notification.VibrationDevice;
using Uno.Foundation.Logging;

namespace Windows.Devices.Haptics
{
public partial class SimpleHapticsController
{
public IReadOnlyList<SimpleHapticsControllerFeedback> SupportedFeedback { get; } = new SimpleHapticsControllerFeedback[]
{
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(100)),
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Press, TimeSpan.FromMilliseconds(300))
};
public partial class SimpleHapticsController
{
public IReadOnlyList<SimpleHapticsControllerFeedback> SupportedFeedback { get; } = new SimpleHapticsControllerFeedback[]
{
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Press, TimeSpan.FromMilliseconds(300)),
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(100)), // Single Click
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(150)) // Double Click
};

public void SendHapticFeedback(SimpleHapticsControllerFeedback feedback)
{
if (feedback is null)
{
throw new ArgumentNullException(nameof(feedback));
}
public void SendHapticFeedback(SimpleHapticsControllerFeedback feedback)
{
if (feedback is null)
{
throw new ArgumentNullException(nameof(feedback));
}

if (ContextHelper.Current == null)
{
throw new InvalidOperationException($"Context must be initialized before {nameof(SendHapticFeedback)} is called.");
}
try
{
var activity = (Activity)ContextHelper.Current;
var androidFeedback = FeedbackToAndroidFeedback(feedback);
#pragma warning disable CA1422 // Validate platform compatibility
bool hapticFeedbackEnabled = Settings.System.GetInt(activity.ContentResolver, Settings.System.HapticFeedbackEnabled, 0) != 0;
#pragma warning restore CA1422 // Validate platform compatibility
if (hapticFeedbackEnabled)
{
var executed = activity.Window?.DecorView.PerformHapticFeedback(androidFeedback) ?? false;
if (!executed && PhoneVibrationDevice.GetDefault() is { } vibrationDevice)
{
// Fall back to VibrationDevice
vibrationDevice.Vibrate(feedback.Duration);
}
}
}
catch (Exception ex)
{
if (this.Log().IsEnabled(LogLevel.Error))
{
this.Log().LogError($"Could not send haptic feedback: {ex}");
}
}
}
Vibrator vibrator = (Vibrator)Android.App.Application.Context.GetSystemService(Android.Content.Context.VibratorService);

private static FeedbackConstants FeedbackToAndroidFeedback(SimpleHapticsControllerFeedback feedback)
{
if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
{
return FeedbackConstants.ContextClick;
}
else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Press)
{
return FeedbackConstants.LongPress;
}
else
{
throw new NotSupportedException("Unsupported feedback waveform");
}
}
}
if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Press)
{
if (vibrator.HasVibrator)
{
vibrator.Vibrate((long)feedback.Duration.TotalMilliseconds);
}
else
{
throw new NotSupportedException("Device does not support vibration");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably best if we skip vibrating altogether. It may be common for simulators to not have this feature. We can probably log a debug message instead.

}
}
else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
{
if (feedback.Duration.TotalMilliseconds <= 100)
{
// Single Click
if (vibrator.HasVibrator)
{
vibrator.Vibrate(VibrationEffect.CreateOneShot((long)feedback.Duration.TotalMilliseconds, VibrationEffect.DefaultAmplitude));
}
else
{
throw new NotSupportedException("Device does not support vibration");
}
}
else if (feedback.Duration.TotalMilliseconds <= 200)
{
// Double Click
if (vibrator.HasVibrator)
{
long[] pattern = { 0, (long)feedback.Duration.TotalMilliseconds, 50, (long)feedback.Duration.TotalMilliseconds };
vibrator.Vibrate(VibrationEffect.CreateWaveform(pattern, -1));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be keeping this try/catch, in case there's an unexpected error. Failing haptics should not fail the app.

else
{
throw new NotSupportedException("Device does not support vibration");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, let's log instead.

}
}
else
{
throw new NotSupportedException("Unsupported feedback duration for Click waveform");
}
}
else
{
throw new NotSupportedException("Unsupported feedback waveform");
}
}
}
}
102 changes: 60 additions & 42 deletions src/Uno.UWP/Devices/Haptics/SimpleHapticsController.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,66 @@
#nullable enable

using System;
using System.Collections.Generic;
using UIKit;
public class HapticFeedbackManager
{
private DateTime? lastClickTime = null;

#nullable enable
public IReadOnlyList<SimpleHapticsControllerFeedback> SupportedFeedback { get; } = new SimpleHapticsControllerFeedback[]
{
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(100)),
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Press, TimeSpan.FromMilliseconds(300)),
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(50)), // Single Click
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(150)), // Double Click
};

namespace Windows.Devices.Haptics
{
public partial class SimpleHapticsController
{
public IReadOnlyList<SimpleHapticsControllerFeedback> SupportedFeedback { get; } = new SimpleHapticsControllerFeedback[]
{
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(100)),
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Press, TimeSpan.FromMilliseconds(300))
};
public void SendHapticFeedback(SimpleHapticsControllerFeedback feedback)
{
if (feedback is null)
{
throw new ArgumentNullException(nameof(feedback));
}

public void SendHapticFeedback(SimpleHapticsControllerFeedback feedback)
{
if (feedback is null)
{
throw new ArgumentNullException(nameof(feedback));
}
var impactStyle = FeedbackToImpactStyle(feedback);
using var impact = new UIImpactFeedbackGenerator(impactStyle);
impact.Prepare();

var impactStyle = FeedbackToImpactStyle(feedback);
using var impact = new UIImpactFeedbackGenerator(impactStyle);
impact.Prepare();
impact.ImpactOccurred();
}
if (feedback.Duration.TotalMilliseconds <= 100)
{
// Single Click
impact.ImpactOccurred();
}
else if (feedback.Duration.TotalMilliseconds <= 200)
{
// Potential Double Click - Check time interval with last click
if (lastClickTime.HasValue && (DateTime.Now - lastClickTime.Value).TotalMilliseconds < 200)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dr1rrb how do we determine that something is a double tap in gestures? We may want to align this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using this https://github.com/unoplatform/uno/blob/master/src/Uno.UWP/UI/Input/GestureRecognizer.cs#L27

But anyway, I'm not sure to understand the condition and why try to make distinction between simple click and double click. Here we end by doing impact.ImpactOccurred(); anyway. Is it expected ?

{
// Double Click
impact.ImpactOccurred();
}
else
{
// Treat as a single click if not a double click
lastClickTime = DateTime.Now;
impact.ImpactOccurred();
}
}
else
{
// Handle other feedback types as before
impact.ImpactOccurred();
}
}

private UIImpactFeedbackStyle FeedbackToImpactStyle(SimpleHapticsControllerFeedback feedback)
{
if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
{
return UIImpactFeedbackStyle.Light;
}
else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Press)
{
return UIImpactFeedbackStyle.Medium;
}
else
{
throw new NotSupportedException("Unsupported feedback waveform");
}
}
}
private UIImpactFeedbackStyle FeedbackToImpactStyle(SimpleHapticsControllerFeedback feedback)
{
if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
{
return UIImpactFeedbackStyle.Light;
}
else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Press)
{
return UIImpactFeedbackStyle.Medium;
}
else
{
throw new NotSupportedException("Unsupported feedback waveform");
}
}
}
67 changes: 44 additions & 23 deletions src/Uno.UWP/Devices/Haptics/SimpleHapticsController.macOS.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,54 @@
#nullable enable
#nullable enable

using System;
using System.Collections.Generic;
using AppKit;

namespace Windows.Devices.Haptics
{
public partial class SimpleHapticsController
{
public IReadOnlyList<SimpleHapticsControllerFeedback> SupportedFeedback { get; } = new SimpleHapticsControllerFeedback[]
{
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Press, TimeSpan.FromMilliseconds(300))
};
public partial class SimpleHapticsController
{
public IReadOnlyList<SimpleHapticsControllerFeedback> SupportedFeedback { get; } = new SimpleHapticsControllerFeedback[]
{
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(100)), // Single Click
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Click, TimeSpan.FromMilliseconds(150)), // Double Click
new SimpleHapticsControllerFeedback(KnownSimpleHapticsControllerWaveforms.Press, TimeSpan.FromMilliseconds(300))
};

public void SendHapticFeedback(SimpleHapticsControllerFeedback feedback)
{
if (feedback is null)
{
throw new ArgumentNullException(nameof(feedback));
}
public void SendHapticFeedback(SimpleHapticsControllerFeedback feedback)
{
if (feedback is null)
{
throw new ArgumentNullException(nameof(feedback));
}

if (feedback.Waveform != KnownSimpleHapticsControllerWaveforms.Press)
{
throw new NotSupportedException("Unsupported feedback waveform");
}

NSHapticFeedbackManager.DefaultPerformer.PerformFeedback(
NSHapticFeedbackPattern.Generic,
NSHapticFeedbackPerformanceTime.Default);
}
}
if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Click)
{
// Determine if it's a single click or double click based on the duration
if (feedback.Duration.TotalMilliseconds <= 100)
{
// Single Click
NSHapticFeedbackManager.DefaultPerformer.PerformFeedback(NSHapticFeedbackPattern.Generic, NSHapticFeedbackPerformanceTime.Default);
}
else if (feedback.Duration.TotalMilliseconds <= 200)
{
// Double Click
NSHapticFeedbackManager.DefaultPerformer.PerformFeedback(NSHapticFeedbackPattern.Generic, NSHapticFeedbackPerformanceTime.Default);
NSHapticFeedbackManager.DefaultPerformer.PerformFeedback(NSHapticFeedbackPattern.Generic, NSHapticFeedbackPerformanceTime.Default);
}
else
{
throw new NotSupportedException("Unsupported feedback duration for Click waveform");
}
}
else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Press)
{
NSHapticFeedbackManager.DefaultPerformer.PerformFeedback(NSHapticFeedbackPattern.Generic, NSHapticFeedbackPerformanceTime.Default);
}
else
{
throw new NotSupportedException("Unsupported feedback waveform");
}
}
}
}