909 lines
25 KiB
Markdown
909 lines
25 KiB
Markdown
# MASpotlightTour
|
|
|
|
A powerful, declarative spotlight tour and onboarding library for .NET MAUI applications. Create beautiful feature tours with spotlight overlays, callout cards, and step-by-step user guidance using simple XAML attached properties.
|
|
|
|
[](https://www.nuget.org/packages/MarketAlly.SpotlightTour.Maui/)
|
|
[](https://www.nuget.org/packages/MarketAlly.SpotlightTour.Maui/)
|
|
[](https://opensource.org/licenses/MIT)
|
|
[](https://dotnet.microsoft.com/download)
|
|
[](https://dotnet.microsoft.com/apps/maui)
|
|
|
|
## Features
|
|
|
|
- **Declarative XAML Syntax** - Tag UI elements with attached properties; no code-behind required for basic tours
|
|
- **Multiple Display Modes** - Spotlight with callout, callout-only (no dimming), or spotlight with inline labels
|
|
- **Flexible Callout Positioning** - Following (relative to element), fixed corner, or auto-corner placement
|
|
- **Customizable Spotlights** - Rectangle, rounded rectangle, or circle shapes with configurable padding
|
|
- **Corner Navigator** - Compact navigation control with step indicators and Previous/Next/Skip buttons
|
|
- **Animation Effects** - Entrance/exit animations, step transitions, and spotlight effects with presets (Subtle, Playful, Elegant, Snappy)
|
|
- **Intro Views** - Show custom welcome screens before the tour begins
|
|
- **Auto-Advance** - Automatically progress through steps on a timer
|
|
- **Tour Looping** - Repeat tours automatically for kiosk/demo modes
|
|
- **Theme Support** - Light, Dark, and System theme modes with automatic switching
|
|
- **RTL Support** - Full right-to-left layout support for Arabic, Hebrew, and other RTL languages
|
|
- **Awaitable Tours** - Chain actions after tour completion using async/await
|
|
- **Tour Groups** - Organize multiple tours on the same page
|
|
- **Localization** - Built-in support for 8 languages (EN, ES, FR, DE, ZH, JA, PT, IT) with easy extensibility
|
|
- **Cross-Platform** - iOS, Android, Windows, and macOS
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
dotnet add package MarketAlly.SpotlightTour.Maui
|
|
```
|
|
|
|
Or via the NuGet Package Manager:
|
|
```
|
|
Install-Package MarketAlly.SpotlightTour.Maui
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### 1. Add the namespace to your XAML
|
|
|
|
```xml
|
|
xmlns:tour="clr-namespace:MarketAlly.SpotlightTour.Maui;assembly=MarketAlly.SpotlightTour.Maui"
|
|
```
|
|
|
|
### 2. Tag elements you want to highlight
|
|
|
|
```xml
|
|
<Button
|
|
Text="Click Me"
|
|
tour:Onboarding.StepKey="welcome"
|
|
tour:Onboarding.Title="Welcome!"
|
|
tour:Onboarding.Description="This button does something amazing."
|
|
tour:Onboarding.Order="1" />
|
|
|
|
<Entry
|
|
Placeholder="Enter your name"
|
|
tour:Onboarding.StepKey="name-input"
|
|
tour:Onboarding.Title="Your Name"
|
|
tour:Onboarding.Description="Type your name here to get started."
|
|
tour:Onboarding.Order="2" />
|
|
```
|
|
|
|
### 3. Add the OnboardingHost overlay
|
|
|
|
```xml
|
|
<Grid>
|
|
<!-- Your page content -->
|
|
<VerticalStackLayout>
|
|
<!-- ... your UI elements ... -->
|
|
</VerticalStackLayout>
|
|
|
|
<!-- Add at the end, as an overlay -->
|
|
<tour:OnboardingHost x:Name="TourHost" />
|
|
</Grid>
|
|
```
|
|
|
|
### 4. Start the tour
|
|
|
|
```csharp
|
|
// Start the tour (scans for tagged elements automatically)
|
|
await TourHost.StartTourAsync(this.Content);
|
|
```
|
|
|
|
## Display Modes
|
|
|
|
### SpotlightWithCallout (Default)
|
|
Full experience with dimmed overlay, spotlight cutout, and callout card with navigation.
|
|
|
|
```xml
|
|
<tour:OnboardingHost DisplayMode="SpotlightWithCallout" />
|
|
```
|
|
|
|
### CalloutOnly
|
|
Floating callout cards without dimming - ideal for light-touch guidance that doesn't block interaction.
|
|
|
|
```xml
|
|
<tour:OnboardingHost DisplayMode="CalloutOnly" />
|
|
```
|
|
|
|
### SpotlightWithInlineLabel
|
|
Dimmed overlay with spotlight, but uses compact inline labels instead of callout cards. Best used with the corner navigator.
|
|
|
|
```xml
|
|
<tour:OnboardingHost
|
|
DisplayMode="SpotlightWithInlineLabel"
|
|
ShowCornerNavigator="True" />
|
|
```
|
|
|
|
## Callout Positioning
|
|
|
|
### Following Mode (Default)
|
|
Callout positions relative to the highlighted element (Top, Bottom, Left, Right, or Auto).
|
|
|
|
```xml
|
|
<tour:OnboardingHost CalloutPositionMode="Following" />
|
|
|
|
<!-- Per-element placement override -->
|
|
<Button tour:Onboarding.Placement="Top" ... />
|
|
```
|
|
|
|
### Fixed Corner Mode
|
|
Callout stays in a fixed screen corner while spotlights move.
|
|
|
|
```xml
|
|
<tour:OnboardingHost
|
|
CalloutPositionMode="FixedCorner"
|
|
CalloutCorner="BottomLeft" />
|
|
```
|
|
|
|
### Auto Corner Mode
|
|
Callout automatically positions in the corner that least interferes with the highlighted element.
|
|
|
|
```xml
|
|
<tour:OnboardingHost CalloutPositionMode="AutoCorner" />
|
|
```
|
|
|
|
## Corner Navigator
|
|
|
|
A compact navigation control that sits in a screen corner with step indicators and navigation buttons.
|
|
|
|
```xml
|
|
<tour:OnboardingHost
|
|
ShowCornerNavigator="True"
|
|
CornerNavigatorPlacement="BottomRight"
|
|
ShowSkipButton="True" />
|
|
```
|
|
|
|
When set to `Auto`, the navigator automatically positions to avoid overlapping with the spotlight and callout card.
|
|
|
|
```xml
|
|
<tour:OnboardingHost
|
|
ShowCornerNavigator="True"
|
|
CornerNavigatorPlacement="Auto"
|
|
CalloutPositionMode="AutoCorner" />
|
|
```
|
|
|
|
## Intro Views
|
|
|
|
Show a custom welcome screen before the tour begins:
|
|
|
|
```xml
|
|
<tour:OnboardingHost x:Name="TourHost">
|
|
<tour:OnboardingHost.IntroView>
|
|
<Frame BackgroundColor="White" Padding="30" CornerRadius="16">
|
|
<VerticalStackLayout Spacing="20">
|
|
<Label Text="Welcome!" FontSize="28" FontAttributes="Bold" />
|
|
<Label Text="Let us show you around." />
|
|
<Button Text="Start Tour" Clicked="OnStartTourClicked" />
|
|
</VerticalStackLayout>
|
|
</Frame>
|
|
</tour:OnboardingHost.IntroView>
|
|
</tour:OnboardingHost>
|
|
```
|
|
|
|
```csharp
|
|
private async void OnStartTourClicked(object sender, EventArgs e)
|
|
{
|
|
TourHost.DismissIntro(); // Dismisses intro and continues to tour
|
|
}
|
|
|
|
// Start with intro
|
|
await TourHost.StartTourWithIntroAsync(this.Content);
|
|
```
|
|
|
|
## Spotlight Customization
|
|
|
|
### Shapes
|
|
|
|
```xml
|
|
<!-- Rounded rectangle (default) -->
|
|
<Button tour:Onboarding.SpotlightShape="RoundedRectangle"
|
|
tour:Onboarding.SpotlightCornerRadius="12" ... />
|
|
|
|
<!-- Circle -->
|
|
<Image tour:Onboarding.SpotlightShape="Circle" ... />
|
|
|
|
<!-- Rectangle -->
|
|
<Frame tour:Onboarding.SpotlightShape="Rectangle" ... />
|
|
```
|
|
|
|
### Padding
|
|
|
|
```xml
|
|
<!-- Uniform padding -->
|
|
<Button tour:Onboarding.SpotlightPadding="16" ... />
|
|
|
|
<!-- Per-side padding -->
|
|
<Button tour:Onboarding.SpotlightPadding="8,16,8,16" ... />
|
|
```
|
|
|
|
### Tap Behavior
|
|
|
|
```xml
|
|
<!-- Tap spotlight to advance -->
|
|
<Button tour:Onboarding.TapBehavior="Advance" ... />
|
|
|
|
<!-- Tap spotlight to close tour -->
|
|
<Button tour:Onboarding.TapBehavior="Close" ... />
|
|
|
|
<!-- Allow interaction with underlying element -->
|
|
<Button tour:Onboarding.TapBehavior="AllowInteraction" ... />
|
|
```
|
|
|
|
## Theming
|
|
|
|
```xml
|
|
<!-- Light theme -->
|
|
<tour:OnboardingHost Theme="Light" />
|
|
|
|
<!-- Dark theme -->
|
|
<tour:OnboardingHost Theme="Dark" />
|
|
|
|
<!-- Follow system theme -->
|
|
<tour:OnboardingHost Theme="System" />
|
|
```
|
|
|
|
### Overlay Customization
|
|
|
|
```xml
|
|
<tour:OnboardingHost
|
|
DimOpacity="0.7"
|
|
DimColor="Black" />
|
|
```
|
|
|
|
## Tour Groups
|
|
|
|
Organize multiple tours on the same page:
|
|
|
|
```xml
|
|
<!-- Basic features tour -->
|
|
<Button tour:Onboarding.Group="basics" tour:Onboarding.Order="1" ... />
|
|
<Entry tour:Onboarding.Group="basics" tour:Onboarding.Order="2" ... />
|
|
|
|
<!-- Advanced features tour -->
|
|
<Button tour:Onboarding.Group="advanced" tour:Onboarding.Order="1" ... />
|
|
<Slider tour:Onboarding.Group="advanced" tour:Onboarding.Order="2" ... />
|
|
```
|
|
|
|
```csharp
|
|
// Start specific group
|
|
await TourHost.StartTourAsync(this.Content, group: "basics");
|
|
```
|
|
|
|
## Auto-Advance & Looping
|
|
|
|
### Auto-Advance
|
|
Automatically progress through steps:
|
|
|
|
```xml
|
|
<tour:OnboardingHost AutoAdvanceDelay="3000" /> <!-- 3 seconds per step -->
|
|
```
|
|
|
|
### Auto-Loop
|
|
Repeat the tour automatically (great for kiosk/demo modes):
|
|
|
|
```xml
|
|
<tour:OnboardingHost
|
|
AutoAdvanceDelay="2000"
|
|
AutoLoop="3" /> <!-- Repeat 3 times -->
|
|
```
|
|
|
|
### Auto-Start
|
|
Start the tour automatically when the page loads:
|
|
|
|
```xml
|
|
<tour:OnboardingHost AutoStartDelay="500" /> <!-- Start after 500ms -->
|
|
```
|
|
|
|
## Awaitable Tours
|
|
|
|
Chain actions after tour completion:
|
|
|
|
```csharp
|
|
var result = await TourHost.StartTourAsync(this.Content);
|
|
|
|
switch (result)
|
|
{
|
|
case TourResult.Completed:
|
|
await DisplayAlert("Done!", "You completed the tour.", "OK");
|
|
break;
|
|
case TourResult.Skipped:
|
|
// User skipped the tour
|
|
break;
|
|
case TourResult.Cancelled:
|
|
// Tour was cancelled programmatically
|
|
break;
|
|
case TourResult.NoSteps:
|
|
// No tour steps were found
|
|
break;
|
|
}
|
|
```
|
|
|
|
## Programmatic Navigation
|
|
|
|
```csharp
|
|
// Navigate to specific step
|
|
await TourHost.GoToStepAsync(2);
|
|
|
|
// Navigate by step key
|
|
var step = OnboardingScanner.FindStepByKey(this.Content, "welcome");
|
|
|
|
// Go to next/previous (async methods preferred)
|
|
await TourHost.GoToNextStepAsync();
|
|
await TourHost.GoToPreviousStepAsync();
|
|
|
|
// End tour
|
|
await TourHost.CompleteTourAsync();
|
|
TourHost.SkipTour();
|
|
TourHost.StopTour();
|
|
```
|
|
|
|
## Step Actions
|
|
|
|
Execute custom code when entering or leaving tour steps. Perfect for UI preparation like switching tabs, opening drawers, or triggering animations before the spotlight appears.
|
|
|
|
### Attached Property (Per-Element)
|
|
|
|
```csharp
|
|
// In code-behind, attach action directly to the element
|
|
Onboarding.SetOnEntering(SettingsTab, async (step, cancellationToken) =>
|
|
{
|
|
// Switch to the settings tab before spotlight appears
|
|
MainTabView.SelectedIndex = 2;
|
|
await Task.Delay(100); // Wait for tab transition
|
|
});
|
|
|
|
Onboarding.SetOnLeaving(SettingsTab, async (step, cancellationToken) =>
|
|
{
|
|
// Cleanup when leaving this step
|
|
MainTabView.SelectedIndex = 0;
|
|
});
|
|
```
|
|
|
|
### Centralized Registration (By StepKey)
|
|
|
|
```csharp
|
|
// Register actions by step key - keeps tour logic organized
|
|
TourHost.RegisterStepEnteringAction("drawer-step", async (step, token) =>
|
|
{
|
|
Shell.Current.FlyoutIsPresented = true;
|
|
await Task.Delay(250); // Wait for drawer animation
|
|
});
|
|
|
|
TourHost.RegisterStepEnteringAction("settings-tab", async (step, token) =>
|
|
{
|
|
SettingsTabButton.IsSelected = true;
|
|
});
|
|
|
|
// Remove registered actions when no longer needed
|
|
TourHost.UnregisterStepEnteringAction("drawer-step");
|
|
TourHost.ClearStepActions(); // Clear all
|
|
```
|
|
|
|
### Step Action Events
|
|
|
|
```csharp
|
|
// Global event - fires for every step (useful for analytics, logging, conditional skipping)
|
|
TourHost.StepEntering += async (sender, e) =>
|
|
{
|
|
// Skip premium features for non-premium users
|
|
if (e.Step.StepKey == "premium-feature" && !currentUser.IsPremium)
|
|
{
|
|
e.Skip = true; // Skip this step, move to next
|
|
return;
|
|
}
|
|
|
|
// Log analytics
|
|
Analytics.Track("tour_step_entering", e.Step.StepKey);
|
|
};
|
|
|
|
// Fires after step is fully visible (animations complete)
|
|
TourHost.StepEntered += (sender, e) =>
|
|
{
|
|
Console.WriteLine($"Now viewing step: {e.Step.Title}");
|
|
};
|
|
|
|
// Fires before leaving current step
|
|
TourHost.StepLeaving += async (sender, e) =>
|
|
{
|
|
// Perform cleanup before navigating away
|
|
await SaveStepProgressAsync(e.StepIndex);
|
|
};
|
|
```
|
|
|
|
### Execution Order
|
|
|
|
Actions execute in this order when navigating to a step:
|
|
|
|
1. `StepLeaving` event + actions (previous step)
|
|
2. `StepEntering` event (can set `Skip = true`)
|
|
3. `OnEntering` attached property action
|
|
4. Registered entering action (by StepKey)
|
|
5. Scroll target into view & animate spotlight
|
|
6. `StepEntered` event
|
|
|
|
## Events
|
|
|
|
```csharp
|
|
// Tour lifecycle events
|
|
TourHost.TourStarted += (s, e) => { /* Tour began */ };
|
|
TourHost.TourCompleted += (s, e) => { /* User finished all steps */ };
|
|
TourHost.TourSkipped += (s, e) => { /* User skipped */ };
|
|
TourHost.TourEnded += (s, e) => { /* Tour ended (any reason) */ };
|
|
|
|
// Step navigation events
|
|
TourHost.StepEntering += async (s, e) => { /* Before entering step - can set e.Skip = true */ };
|
|
TourHost.StepEntered += (s, e) => { /* After step is visible */ };
|
|
TourHost.StepLeaving += async (s, e) => { /* Before leaving step */ };
|
|
TourHost.StepChanged += (s, e) =>
|
|
{
|
|
Console.WriteLine($"Step {e.StepIndex + 1}/{e.TotalSteps}: {e.Step.Title}");
|
|
};
|
|
|
|
// Intro events
|
|
TourHost.IntroShown += (s, e) => { /* Intro view displayed */ };
|
|
TourHost.IntroDismissed += (s, e) => { /* Intro view dismissed */ };
|
|
```
|
|
|
|
## Animation Effects
|
|
|
|
The library provides a comprehensive animation system with entrance/exit animations, step transitions, and spotlight effects.
|
|
|
|
### Animation Configuration
|
|
|
|
Configure animations using the `AnimationConfiguration` class:
|
|
|
|
```csharp
|
|
using MarketAlly.SpotlightTour.Maui.Animations;
|
|
|
|
// Use a preset
|
|
var playful = AnimationConfiguration.Playful;
|
|
var elegant = AnimationConfiguration.Elegant;
|
|
var snappy = AnimationConfiguration.Snappy;
|
|
var subtle = AnimationConfiguration.Subtle;
|
|
|
|
// Or customize your own
|
|
var custom = new AnimationConfiguration
|
|
{
|
|
// Entrance animations
|
|
CalloutEntrance = EntranceAnimation.SlideUpFade,
|
|
OverlayEntrance = EntranceAnimation.FadeIn,
|
|
NavigatorEntrance = EntranceAnimation.SlideFromRight,
|
|
InlineLabelEntrance = EntranceAnimation.PopIn,
|
|
|
|
// Exit animations
|
|
CalloutExit = ExitAnimation.FadeOut,
|
|
OverlayExit = ExitAnimation.FadeOut,
|
|
|
|
// Step transitions
|
|
StepTransition = StepTransition.Slide,
|
|
|
|
// Spotlight effects (continuous animations)
|
|
SpotlightEffect = SpotlightEffect.Pulse,
|
|
|
|
// Timing
|
|
EntranceDurationMs = 300,
|
|
ExitDurationMs = 250,
|
|
StepTransitionDurationMs = 200,
|
|
|
|
// Easing
|
|
Easing = AnimationEasing.CubicInOut
|
|
};
|
|
```
|
|
|
|
### Using Animations with OnboardingHost
|
|
|
|
```xml
|
|
<tour:OnboardingHost
|
|
x:Name="TourHost"
|
|
AnimationsEnabled="True"
|
|
AnimationDuration="300" />
|
|
```
|
|
|
|
```csharp
|
|
// Apply animation configuration
|
|
TourHost.AnimationConfiguration = AnimationConfiguration.Playful;
|
|
|
|
// Or use custom configuration
|
|
TourHost.AnimationConfiguration = new AnimationConfiguration
|
|
{
|
|
SpotlightEffect = SpotlightEffect.Breathe,
|
|
CalloutEntrance = EntranceAnimation.BounceIn,
|
|
StepTransition = StepTransition.Push
|
|
};
|
|
```
|
|
|
|
### Available Animation Types
|
|
|
|
#### Entrance Animations
|
|
| Animation | Description |
|
|
|-----------|-------------|
|
|
| `None` | No animation |
|
|
| `FadeIn` | Fade in from transparent |
|
|
| `SlideFromLeft` | Slide in from left |
|
|
| `SlideFromRight` | Slide in from right |
|
|
| `SlideFromTop` | Slide in from top |
|
|
| `SlideFromBottom` | Slide in from bottom |
|
|
| `ScaleUp` | Scale up from small |
|
|
| `BounceIn` | Bounce in with overshoot |
|
|
| `PopIn` | Pop in with scale |
|
|
| `SlideUpFade` | Slide up while fading in |
|
|
| `ZoomIn` | Zoom in from small |
|
|
|
|
#### Exit Animations
|
|
| Animation | Description |
|
|
|-----------|-------------|
|
|
| `None` | No animation |
|
|
| `FadeOut` | Fade out to transparent |
|
|
| `SlideToLeft` | Slide out to left |
|
|
| `SlideToRight` | Slide out to right |
|
|
| `SlideToTop` | Slide out to top |
|
|
| `SlideToBottom` | Slide out to bottom |
|
|
| `ScaleDown` | Scale down to small |
|
|
| `ZoomOut` | Zoom out |
|
|
| `PopOut` | Pop out with scale |
|
|
|
|
#### Spotlight Effects (Continuous)
|
|
| Effect | Description |
|
|
|--------|-------------|
|
|
| `None` | No effect |
|
|
| `Pulse` | Gentle pulsing scale |
|
|
| `Breathe` | Slow breathing scale |
|
|
| `Glow` | Opacity breathing effect |
|
|
|
|
#### Step Transitions
|
|
| Transition | Description |
|
|
|------------|-------------|
|
|
| `Crossfade` | Fade between steps |
|
|
| `Slide` | Slide in direction of navigation |
|
|
| `Push` | Push old content out |
|
|
|
|
### Animation Presets
|
|
|
|
```csharp
|
|
// Subtle - minimal, professional animations
|
|
AnimationConfiguration.Subtle
|
|
|
|
// Playful - bouncy, fun animations
|
|
AnimationConfiguration.Playful
|
|
|
|
// Elegant - smooth, refined animations
|
|
AnimationConfiguration.Elegant
|
|
|
|
// Snappy - fast, responsive animations
|
|
AnimationConfiguration.Snappy
|
|
|
|
// None - no animations
|
|
AnimationConfiguration.None
|
|
```
|
|
|
|
### Disable Animations
|
|
|
|
```xml
|
|
<!-- Disable all animations -->
|
|
<tour:OnboardingHost AnimationsEnabled="False" />
|
|
```
|
|
|
|
### Legacy Animation Duration
|
|
|
|
For simple animation timing control:
|
|
|
|
```xml
|
|
<!-- Custom animation duration (milliseconds) -->
|
|
<tour:OnboardingHost AnimationDuration="400" />
|
|
```
|
|
|
|
## Configuration
|
|
|
|
The library provides a `TourConfiguration` class for fine-grained control over timing, sizing, and visual settings:
|
|
|
|
```csharp
|
|
// Create custom configuration
|
|
var config = new TourConfiguration
|
|
{
|
|
// Timing
|
|
ScrollTimeoutMs = 1500, // Timeout for scroll operations
|
|
LayoutSettleDelayMs = 100, // Delay after scrolling
|
|
DefaultAnimationDurationMs = 300, // Animation duration
|
|
|
|
// Sizing
|
|
CalloutMaxWidth = 450, // Maximum callout width
|
|
NavigatorEstimatedWidth = 220, // Navigator width for auto-placement
|
|
DefaultMargin = 16, // Default margin from edges
|
|
|
|
// Visual
|
|
DisabledButtonOpacity = 0.4, // Opacity for disabled buttons
|
|
DefaultDimOpacity = 0.7, // Default overlay opacity
|
|
CalloutCornerRadius = 16, // Callout card corner radius
|
|
NavButtonSize = 40, // Navigation button size
|
|
|
|
// Colors
|
|
PrimaryAccentColor = "#0078D4", // Primary button color
|
|
SuccessColor = "#107C10", // Done button color
|
|
DangerColor = "#D13438", // Skip button color
|
|
};
|
|
|
|
// Use configuration with components
|
|
var overlay = new SpotlightOverlay(config);
|
|
var callout = new CalloutCard(config);
|
|
```
|
|
|
|
## RTL (Right-to-Left) Support
|
|
|
|
The library includes full RTL support for languages like Arabic, Hebrew, and Persian:
|
|
|
|
```xml
|
|
<!-- Auto-detect from system culture or parent -->
|
|
<onboard:OnboardingHost TourFlowDirection="MatchParent" />
|
|
|
|
<!-- Force RTL layout -->
|
|
<onboard:OnboardingHost TourFlowDirection="RightToLeft" />
|
|
|
|
<!-- Force LTR layout -->
|
|
<onboard:OnboardingHost TourFlowDirection="LeftToRight" />
|
|
```
|
|
|
|
When `MatchParent` is set (the default), the library will:
|
|
1. Check parent elements for explicit `FlowDirection`
|
|
2. Fall back to system culture (`CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft`)
|
|
|
|
RTL mode automatically:
|
|
- Reverses button order in callout cards and navigator
|
|
- Adjusts text alignment for titles and descriptions
|
|
- Mirrors the overall layout direction
|
|
|
|
## Interfaces for Testability
|
|
|
|
The library provides interfaces for dependency injection and unit testing:
|
|
|
|
```csharp
|
|
// IStepScanner - for scanning visual trees
|
|
public interface IStepScanner
|
|
{
|
|
IReadOnlyList<OnboardingStep> FindSteps(Element root, string? group = null);
|
|
OnboardingStep? FindStepByKey(Element root, string stepKey);
|
|
IReadOnlyList<string> GetGroups(Element root);
|
|
int CountSteps(Element root, string? group = null);
|
|
}
|
|
|
|
// Use the default implementation
|
|
IStepScanner scanner = DefaultStepScanner.Instance;
|
|
var steps = scanner.FindSteps(this.Content);
|
|
```
|
|
|
|
## Resource Management
|
|
|
|
`OnboardingHost` implements `IDisposable` for proper resource cleanup:
|
|
|
|
```csharp
|
|
// In a page with tour
|
|
public partial class MyPage : ContentPage, IDisposable
|
|
{
|
|
private readonly OnboardingHost _tourHost;
|
|
|
|
public MyPage()
|
|
{
|
|
InitializeComponent();
|
|
_tourHost = new OnboardingHost();
|
|
// ... setup
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_tourHost.Dispose();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Localization
|
|
|
|
The library includes built-in support for 8 languages with automatic detection based on the device culture:
|
|
|
|
**Supported Languages:**
|
|
- English (en)
|
|
- Spanish (es)
|
|
- French (fr)
|
|
- German (de)
|
|
- Chinese (zh)
|
|
- Japanese (ja)
|
|
- Portuguese (pt)
|
|
- Italian (it)
|
|
|
|
### Automatic Language Detection
|
|
|
|
By default, the library uses `CultureInfo.CurrentUICulture` to select the appropriate language:
|
|
|
|
```csharp
|
|
// UI text automatically uses device language
|
|
await TourHost.StartTourAsync(this.Content);
|
|
```
|
|
|
|
### Manual Language Selection
|
|
|
|
You can override the language programmatically:
|
|
|
|
```csharp
|
|
using MarketAlly.SpotlightTour.Maui.Localization;
|
|
using System.Globalization;
|
|
|
|
// Set to Spanish
|
|
TourStrings.CurrentCulture = new CultureInfo("es");
|
|
|
|
// Set to Japanese
|
|
TourStrings.CurrentCulture = new CultureInfo("ja");
|
|
|
|
// Reset to system default
|
|
TourStrings.CurrentCulture = null;
|
|
```
|
|
|
|
### Custom Translations
|
|
|
|
Add or override translations for any language:
|
|
|
|
```csharp
|
|
// Add custom translations
|
|
TourStrings.RegisterTranslations("es", new Dictionary<string, string>
|
|
{
|
|
["Next"] = "Siguiente paso",
|
|
["Done"] = "Terminado"
|
|
});
|
|
|
|
// Add a new language
|
|
TourStrings.RegisterTranslations("ko", new Dictionary<string, string>
|
|
{
|
|
["Previous"] = "이전",
|
|
["Next"] = "다음",
|
|
["Done"] = "완료",
|
|
["Skip"] = "건너뛰기",
|
|
["StepIndicator"] = "{0} / {1}"
|
|
});
|
|
```
|
|
|
|
### Available String Keys
|
|
|
|
| Key | English | Description |
|
|
|-----|---------|-------------|
|
|
| `Previous` | Previous | Back button text |
|
|
| `Next` | Next | Forward button text |
|
|
| `Done` | Done | Completion button text |
|
|
| `Skip` | Skip | Skip tour button text |
|
|
| `Close` | Close | Close button text |
|
|
| `Start` | Start | Start tour button text |
|
|
| `GotIt` | Got it | Acknowledgment button text |
|
|
| `Continue` | Continue | Continue button text |
|
|
| `Finish` | Finish | Finish button text |
|
|
| `StepIndicator` | {0} / {1} | Step counter format |
|
|
|
|
### Responding to Language Changes
|
|
|
|
Components automatically update when the culture changes:
|
|
|
|
```csharp
|
|
// Subscribe to culture changes
|
|
TourStrings.CultureChanged += (s, e) =>
|
|
{
|
|
// Handle language change if needed
|
|
Console.WriteLine($"Language changed to: {TourStrings.EffectiveCulture.Name}");
|
|
};
|
|
```
|
|
|
|
## Async Best Practices
|
|
|
|
The library provides proper async methods with cancellation token support:
|
|
|
|
```csharp
|
|
// Cancellation support
|
|
using var cts = new CancellationTokenSource();
|
|
cts.CancelAfter(TimeSpan.FromMinutes(5));
|
|
|
|
var result = await TourHost.StartTourAsync(this.Content, cancellationToken: cts.Token);
|
|
|
|
// Proper async navigation
|
|
await TourHost.GoToNextStepAsync();
|
|
await TourHost.GoToPreviousStepAsync();
|
|
await TourHost.CompleteTourAsync();
|
|
await TourHost.DismissIntroAsync();
|
|
|
|
// Legacy sync methods are available but marked obsolete
|
|
// TourHost.GoToNextStep(); // Use GoToNextStepAsync() instead
|
|
```
|
|
|
|
## Complete Example
|
|
|
|
```xml
|
|
<?xml version="1.0" encoding="utf-8" ?>
|
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
xmlns:tour="clr-namespace:MarketAlly.SpotlightTour.Maui;assembly=MarketAlly.SpotlightTour.Maui"
|
|
x:Class="MyApp.MainPage">
|
|
|
|
<Grid>
|
|
<ScrollView>
|
|
<VerticalStackLayout Padding="20" Spacing="20">
|
|
|
|
<Image Source="logo.png"
|
|
tour:Onboarding.StepKey="logo"
|
|
tour:Onboarding.Title="Welcome to MyApp"
|
|
tour:Onboarding.Description="This is our beautiful logo!"
|
|
tour:Onboarding.Order="1"
|
|
tour:Onboarding.SpotlightShape="Circle" />
|
|
|
|
<Entry Placeholder="Username"
|
|
tour:Onboarding.StepKey="username"
|
|
tour:Onboarding.Title="Enter Username"
|
|
tour:Onboarding.Description="Type your username to sign in."
|
|
tour:Onboarding.Order="2" />
|
|
|
|
<Entry Placeholder="Password" IsPassword="True"
|
|
tour:Onboarding.StepKey="password"
|
|
tour:Onboarding.Title="Enter Password"
|
|
tour:Onboarding.Description="Your password is secure with us."
|
|
tour:Onboarding.Order="3" />
|
|
|
|
<Button Text="Sign In"
|
|
tour:Onboarding.StepKey="signin"
|
|
tour:Onboarding.Title="Sign In"
|
|
tour:Onboarding.Description="Tap here to access your account."
|
|
tour:Onboarding.Order="4"
|
|
Clicked="OnSignInClicked" />
|
|
|
|
</VerticalStackLayout>
|
|
</ScrollView>
|
|
|
|
<tour:OnboardingHost
|
|
x:Name="TourHost"
|
|
Theme="System"
|
|
ShowCornerNavigator="True"
|
|
CornerNavigatorPlacement="Auto"
|
|
CalloutPositionMode="AutoCorner" />
|
|
</Grid>
|
|
|
|
</ContentPage>
|
|
```
|
|
|
|
```csharp
|
|
public partial class MainPage : ContentPage
|
|
{
|
|
public MainPage()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
protected override async void OnAppearing()
|
|
{
|
|
base.OnAppearing();
|
|
|
|
// Check if user has seen the tour
|
|
if (!Preferences.Get("HasSeenTour", false))
|
|
{
|
|
var result = await TourHost.StartTourAsync(this.Content);
|
|
if (result == TourResult.Completed)
|
|
{
|
|
Preferences.Set("HasSeenTour", true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## API Reference
|
|
|
|
For complete API documentation, see [API_Reference.md](API_Reference.md).
|
|
|
|
## Platform Support
|
|
|
|
| Platform | Minimum Version |
|
|
|----------|-----------------|
|
|
| iOS | 15.0 |
|
|
| Android | API 21 (5.0) |
|
|
| Windows | 10.0.17763.0 |
|
|
| macOS | 15.0 (Catalyst) |
|
|
|
|
## License
|
|
|
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
|
|
## Repository
|
|
|
|
[https://git.marketally.com/marketally/MASpotlightTour](https://git.marketally.com/marketally/MASpotlightTour)
|
|
|
|
---
|
|
|
|
**Built with precision by [MarketAlly](https://marketally.com)**
|
|
|
|
*Enterprise-grade onboarding solutions for .NET MAUI applications.*
|