297 lines
9.6 KiB
C#
297 lines
9.6 KiB
C#
namespace MarketAlly.SpotlightTour.Maui;
|
|
|
|
/// <summary>
|
|
/// Provides attached properties for tagging UI elements as onboarding/tour steps.
|
|
/// </summary>
|
|
public static class Onboarding
|
|
{
|
|
#region StepKey
|
|
|
|
/// <summary>
|
|
/// Identifies the step with a unique key. Falls back to AutomationId if not set.
|
|
/// </summary>
|
|
public static readonly BindableProperty StepKeyProperty =
|
|
BindableProperty.CreateAttached(
|
|
"StepKey",
|
|
typeof(string),
|
|
typeof(Onboarding),
|
|
defaultValue: null);
|
|
|
|
public static string? GetStepKey(BindableObject view) =>
|
|
(string?)view.GetValue(StepKeyProperty);
|
|
|
|
public static void SetStepKey(BindableObject view, string? value) =>
|
|
view.SetValue(StepKeyProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region Title
|
|
|
|
/// <summary>
|
|
/// The title text displayed in the callout for this step.
|
|
/// </summary>
|
|
public static readonly BindableProperty TitleProperty =
|
|
BindableProperty.CreateAttached(
|
|
"Title",
|
|
typeof(string),
|
|
typeof(Onboarding),
|
|
defaultValue: null);
|
|
|
|
public static string? GetTitle(BindableObject view) =>
|
|
(string?)view.GetValue(TitleProperty);
|
|
|
|
public static void SetTitle(BindableObject view, string? value) =>
|
|
view.SetValue(TitleProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region Description
|
|
|
|
/// <summary>
|
|
/// The description text displayed in the callout for this step.
|
|
/// </summary>
|
|
public static readonly BindableProperty DescriptionProperty =
|
|
BindableProperty.CreateAttached(
|
|
"Description",
|
|
typeof(string),
|
|
typeof(Onboarding),
|
|
defaultValue: null);
|
|
|
|
public static string? GetDescription(BindableObject view) =>
|
|
(string?)view.GetValue(DescriptionProperty);
|
|
|
|
public static void SetDescription(BindableObject view, string? value) =>
|
|
view.SetValue(DescriptionProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region Order
|
|
|
|
/// <summary>
|
|
/// The order/sequence number for this step within its group.
|
|
/// </summary>
|
|
public static readonly BindableProperty OrderProperty =
|
|
BindableProperty.CreateAttached(
|
|
"Order",
|
|
typeof(int),
|
|
typeof(Onboarding),
|
|
defaultValue: 0);
|
|
|
|
public static int GetOrder(BindableObject view) =>
|
|
(int)view.GetValue(OrderProperty);
|
|
|
|
public static void SetOrder(BindableObject view, int value) =>
|
|
view.SetValue(OrderProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region Group
|
|
|
|
/// <summary>
|
|
/// The group name for organizing multiple tours on the same page.
|
|
/// </summary>
|
|
public static readonly BindableProperty GroupProperty =
|
|
BindableProperty.CreateAttached(
|
|
"Group",
|
|
typeof(string),
|
|
typeof(Onboarding),
|
|
defaultValue: null);
|
|
|
|
public static string? GetGroup(BindableObject view) =>
|
|
(string?)view.GetValue(GroupProperty);
|
|
|
|
public static void SetGroup(BindableObject view, string? value) =>
|
|
view.SetValue(GroupProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region SpotlightEnabled
|
|
|
|
/// <summary>
|
|
/// Whether this element participates in spotlight cutouts.
|
|
/// </summary>
|
|
public static readonly BindableProperty SpotlightEnabledProperty =
|
|
BindableProperty.CreateAttached(
|
|
"SpotlightEnabled",
|
|
typeof(bool),
|
|
typeof(Onboarding),
|
|
defaultValue: true);
|
|
|
|
public static bool GetSpotlightEnabled(BindableObject view) =>
|
|
(bool)view.GetValue(SpotlightEnabledProperty);
|
|
|
|
public static void SetSpotlightEnabled(BindableObject view, bool value) =>
|
|
view.SetValue(SpotlightEnabledProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region Placement
|
|
|
|
/// <summary>
|
|
/// Where to place the callout card relative to the spotlight.
|
|
/// </summary>
|
|
public static readonly BindableProperty PlacementProperty =
|
|
BindableProperty.CreateAttached(
|
|
"Placement",
|
|
typeof(CalloutPlacement),
|
|
typeof(Onboarding),
|
|
defaultValue: CalloutPlacement.Auto);
|
|
|
|
public static CalloutPlacement GetPlacement(BindableObject view) =>
|
|
(CalloutPlacement)view.GetValue(PlacementProperty);
|
|
|
|
public static void SetPlacement(BindableObject view, CalloutPlacement value) =>
|
|
view.SetValue(PlacementProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region SpotlightShape
|
|
|
|
/// <summary>
|
|
/// The shape of the spotlight cutout for this step.
|
|
/// </summary>
|
|
public static readonly BindableProperty SpotlightShapeProperty =
|
|
BindableProperty.CreateAttached(
|
|
"SpotlightShape",
|
|
typeof(SpotlightShape),
|
|
typeof(Onboarding),
|
|
defaultValue: SpotlightShape.RoundedRectangle);
|
|
|
|
public static SpotlightShape GetSpotlightShape(BindableObject view) =>
|
|
(SpotlightShape)view.GetValue(SpotlightShapeProperty);
|
|
|
|
public static void SetSpotlightShape(BindableObject view, SpotlightShape value) =>
|
|
view.SetValue(SpotlightShapeProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region SpotlightPadding
|
|
|
|
/// <summary>
|
|
/// Padding around the target element in the spotlight cutout.
|
|
/// </summary>
|
|
public static readonly BindableProperty SpotlightPaddingProperty =
|
|
BindableProperty.CreateAttached(
|
|
"SpotlightPadding",
|
|
typeof(Thickness),
|
|
typeof(Onboarding),
|
|
defaultValue: new Thickness(8));
|
|
|
|
public static Thickness GetSpotlightPadding(BindableObject view) =>
|
|
(Thickness)view.GetValue(SpotlightPaddingProperty);
|
|
|
|
public static void SetSpotlightPadding(BindableObject view, Thickness value) =>
|
|
view.SetValue(SpotlightPaddingProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region SpotlightCornerRadius
|
|
|
|
/// <summary>
|
|
/// Corner radius for RoundedRectangle spotlight shape.
|
|
/// </summary>
|
|
public static readonly BindableProperty SpotlightCornerRadiusProperty =
|
|
BindableProperty.CreateAttached(
|
|
"SpotlightCornerRadius",
|
|
typeof(double),
|
|
typeof(Onboarding),
|
|
defaultValue: 8.0);
|
|
|
|
public static double GetSpotlightCornerRadius(BindableObject view) =>
|
|
(double)view.GetValue(SpotlightCornerRadiusProperty);
|
|
|
|
public static void SetSpotlightCornerRadius(BindableObject view, double value) =>
|
|
view.SetValue(SpotlightCornerRadiusProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region TapBehavior
|
|
|
|
/// <summary>
|
|
/// Behavior when the user taps on the spotlighted element.
|
|
/// </summary>
|
|
public static readonly BindableProperty TapBehaviorProperty =
|
|
BindableProperty.CreateAttached(
|
|
"TapBehavior",
|
|
typeof(SpotlightTapBehavior),
|
|
typeof(Onboarding),
|
|
defaultValue: SpotlightTapBehavior.None);
|
|
|
|
public static SpotlightTapBehavior GetTapBehavior(BindableObject view) =>
|
|
(SpotlightTapBehavior)view.GetValue(TapBehaviorProperty);
|
|
|
|
public static void SetTapBehavior(BindableObject view, SpotlightTapBehavior value) =>
|
|
view.SetValue(TapBehaviorProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region OnEntering
|
|
|
|
/// <summary>
|
|
/// Action to execute when the tour enters this step.
|
|
/// The action runs before the spotlight animates to the element, allowing UI preparation
|
|
/// (e.g., switching tabs, opening drawers) to complete first.
|
|
/// </summary>
|
|
public static readonly BindableProperty OnEnteringProperty =
|
|
BindableProperty.CreateAttached(
|
|
"OnEntering",
|
|
typeof(Func<OnboardingStep, CancellationToken, Task>),
|
|
typeof(Onboarding),
|
|
defaultValue: null);
|
|
|
|
public static Func<OnboardingStep, CancellationToken, Task>? GetOnEntering(BindableObject view) =>
|
|
(Func<OnboardingStep, CancellationToken, Task>?)view.GetValue(OnEnteringProperty);
|
|
|
|
public static void SetOnEntering(BindableObject view, Func<OnboardingStep, CancellationToken, Task>? value) =>
|
|
view.SetValue(OnEnteringProperty, value);
|
|
|
|
#endregion
|
|
|
|
#region OnLeaving
|
|
|
|
/// <summary>
|
|
/// Action to execute when the tour leaves this step.
|
|
/// The action runs before navigating to the next/previous step.
|
|
/// </summary>
|
|
public static readonly BindableProperty OnLeavingProperty =
|
|
BindableProperty.CreateAttached(
|
|
"OnLeaving",
|
|
typeof(Func<OnboardingStep, CancellationToken, Task>),
|
|
typeof(Onboarding),
|
|
defaultValue: null);
|
|
|
|
public static Func<OnboardingStep, CancellationToken, Task>? GetOnLeaving(BindableObject view) =>
|
|
(Func<OnboardingStep, CancellationToken, Task>?)view.GetValue(OnLeavingProperty);
|
|
|
|
public static void SetOnLeaving(BindableObject view, Func<OnboardingStep, CancellationToken, Task>? value) =>
|
|
view.SetValue(OnLeavingProperty, value);
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Helper to get the effective step key, falling back to AutomationId.
|
|
/// </summary>
|
|
public static string? GetEffectiveStepKey(BindableObject view)
|
|
{
|
|
var stepKey = GetStepKey(view);
|
|
if (!string.IsNullOrWhiteSpace(stepKey))
|
|
return stepKey;
|
|
|
|
if (view is VisualElement ve && !string.IsNullOrWhiteSpace(ve.AutomationId))
|
|
return ve.AutomationId;
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if an element is tagged as an onboarding step.
|
|
/// </summary>
|
|
public static bool IsOnboardingStep(BindableObject view)
|
|
{
|
|
// An element is considered a step if it has a StepKey, Title, or Description set
|
|
return !string.IsNullOrWhiteSpace(GetStepKey(view)) ||
|
|
!string.IsNullOrWhiteSpace(GetTitle(view)) ||
|
|
!string.IsNullOrWhiteSpace(GetDescription(view));
|
|
}
|
|
}
|