This repository has been archived by the owner on May 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 471
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Base implementations for SemanticEffect and SemanticOrderView (#1240)
* Base implementations for SemanticEffect and SemanticOrderView * - uwp and iOS fix * - uwp tab index * - hint and description * - fix iOS to auto set to important for accessibility * Add samples and fix up crashes * - for UI Test reason switch to using a delegate for reading content Description on Android * - use the delegate if the automation id is set * - fixes * - set yes less often * Remove IsInAccessibleTree from PancakeView doesn't make a big difference on Android, but the property worsens accessibility on iOS * Update SemanticEffectPage.xaml * Update SemanticOrderViewPage.xaml * Update SemanticOrderViewPage.xaml.cs * Fix Semantic Description on Android * Reword SemanticEffect about * - added temporary workaround for making a screen clickable * Fix SemanticOrderViewPage sample * Fix NRE thrown on back nav from iOS SemanticOrderView page * - fix UWP router * - fix null checks * - changed first button to button so uwp sample is interesting * Clean up code - Pedro's feedback Co-authored-by: Shane Neuville <shneuvil@microsoft.com> Co-authored-by: Javier Suárez <javiersuarezruiz@hotmail.com> Co-authored-by: Rachel Kang <rachelkang@microsoft.com> Co-authored-by: Gerald Versluis <gerald.versluis@microsoft.com>
- Loading branch information
1 parent
9902e85
commit 54e3e94
Showing
22 changed files
with
778 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using Android.Content; | ||
using Xamarin.CommunityToolkit.Sample.Droid; | ||
using Xamarin.CommunityToolkit.Sample.Pages.Views; | ||
using Xamarin.Forms; | ||
using Xamarin.Forms.Platform.Android; | ||
|
||
// This is a temporary fix for an issue in forms that will be fixed in a later release of 5.0 | ||
// https://github.com/xamarin/Xamarin.Forms/pull/14089 | ||
[assembly: ExportRenderer(typeof(SemanticOrderViewPage), typeof(AccessiblePageRenderer))] | ||
|
||
namespace Xamarin.CommunityToolkit.Sample.Droid | ||
{ | ||
public class AccessiblePageRenderer : PageRenderer | ||
{ | ||
public AccessiblePageRenderer(Context context) | ||
: base(context) | ||
{ | ||
} | ||
|
||
protected override void OnElementChanged(ElementChangedEventArgs<Page> e) | ||
{ | ||
base.OnElementChanged(e); | ||
Clickable = false; | ||
} | ||
|
||
protected override void OnAttachedToWindow() | ||
{ | ||
base.OnAttachedToWindow(); | ||
DisableFocusableInTouchMode(); | ||
} | ||
|
||
protected override void AttachViewToParent(global::Android.Views.View? child, int index, LayoutParams? @params) | ||
{ | ||
base.AttachViewToParent(child, index, @params); | ||
DisableFocusableInTouchMode(); | ||
} | ||
|
||
void DisableFocusableInTouchMode() | ||
{ | ||
var view = Parent; | ||
var className = $"{view?.GetType().Name}"; | ||
|
||
while (!className.Contains("PlatformRenderer") && view != null) | ||
{ | ||
view = view.Parent; | ||
className = $"{view?.GetType().Name}"; | ||
} | ||
|
||
if (view is global::Android.Views.View androidView) | ||
{ | ||
androidView.Focusable = false; | ||
androidView.FocusableInTouchMode = false; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<pages:BasePage xmlns="http://xamarin.com/schemas/2014/forms" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Effects.SemanticEffectPage" | ||
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages" | ||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"> | ||
<ContentPage.Content> | ||
<ScrollView> | ||
<StackLayout Padding="20"> | ||
<Label Text="I have no heading" xct:SemanticEffect.HeadingLevel="None"/> | ||
<Label Text="I am a heading 1" xct:SemanticEffect.HeadingLevel="Level1"/> | ||
<Label Text="I am a heading 2" xct:SemanticEffect.HeadingLevel="Level2"/> | ||
<Label Text="I am a heading 3" xct:SemanticEffect.HeadingLevel="Level3"/> | ||
<Label Text="I am a heading 4" xct:SemanticEffect.HeadingLevel="Level4"/> | ||
<Label Text="I am a heading 5" xct:SemanticEffect.HeadingLevel="Level5"/> | ||
<Label Text="I am a heading 6" xct:SemanticEffect.HeadingLevel="Level6"/> | ||
<Label Text="I am a heading 7" xct:SemanticEffect.HeadingLevel="Level7"/> | ||
<Label Text="I am a heading 8" xct:SemanticEffect.HeadingLevel="Level8"/> | ||
<Label Text="I am a heading 9" xct:SemanticEffect.HeadingLevel="Level9"/> | ||
|
||
<Label Text="I am a label with an automation ID" AutomationId="labelAutomationIdTest" xct:SemanticEffect.Description="This is a semantic description" /> | ||
|
||
<Label Text="The button below has a semantic hint"/> | ||
<Button | ||
Text="Button with hint" | ||
xct:SemanticEffect.Hint="This is a hint that describes the button. For example, 'sends a message'"/> | ||
|
||
<Label Text="The image below has a semantic description"/> | ||
<Image | ||
Source="{xct:ImageResource Id=Xamarin.CommunityToolkit.Sample.Images.logo.png}" | ||
xct:SemanticEffect.Description="This is a description that describes the image. For example, 'image of xamarin community toolkit logo'"/> | ||
</StackLayout> | ||
</ScrollView> | ||
</ContentPage.Content> | ||
</pages:BasePage> |
13 changes: 13 additions & 0 deletions
13
samples/XCT.Sample/Pages/Effects/SemanticEffectPage.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
| ||
using Xamarin.Forms; | ||
|
||
namespace Xamarin.CommunityToolkit.Sample.Pages.Effects | ||
{ | ||
public partial class SemanticEffectPage : BasePage | ||
{ | ||
public SemanticEffectPage() | ||
{ | ||
InitializeComponent(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<pages:BasePage xmlns="http://xamarin.com/schemas/2014/forms" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages" | ||
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Views.SemanticOrderViewPage" | ||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"> | ||
<ContentPage.Content> | ||
<StackLayout Margin="20"> | ||
<Button Text="Element outside the Semantic View"></Button> | ||
<xct:SemanticOrderView x:Name="acv"> | ||
<StackLayout> | ||
<Label x:Name="second" Text="Second" Margin="0,20" /> | ||
<Button x:Name="third" Text="Third" Margin="0,20" /> | ||
<Label x:Name="fourth" Text="Fourth" Margin="0,20" /> | ||
<Button x:Name="fifth" Text="Fifth and last" Margin="0,20" /> | ||
<Button x:Name="first" Text="First" Margin="0,20" /> | ||
</StackLayout> | ||
</xct:SemanticOrderView> | ||
</StackLayout> | ||
</ContentPage.Content> | ||
</pages:BasePage> |
15 changes: 15 additions & 0 deletions
15
samples/XCT.Sample/Pages/Views/SemanticOrderViewPage.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using System.Collections.Generic; | ||
|
||
using Xamarin.Forms; | ||
|
||
namespace Xamarin.CommunityToolkit.Sample.Pages.Views | ||
{ | ||
public partial class SemanticOrderViewPage : BasePage | ||
{ | ||
public SemanticOrderViewPage() | ||
{ | ||
InitializeComponent(); | ||
acv.ViewOrder = new List<View> { first, second, third, fourth, fifth }; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Semantic/SemanticEffect.shared.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using Xamarin.Forms; | ||
using Xamarin.CommunityToolkit.Effects.Semantic; | ||
|
||
namespace Xamarin.CommunityToolkit.Effects | ||
{ | ||
public static class SemanticEffect | ||
{ | ||
public static readonly BindableProperty HeadingLevelProperty = | ||
BindableProperty.CreateAttached("HeadingLevel", typeof(HeadingLevel), typeof(SemanticEffect), HeadingLevel.None, propertyChanged: OnPropertyChanged); | ||
|
||
public static HeadingLevel GetHeadingLevel(BindableObject view) => (HeadingLevel)view.GetValue(HeadingLevelProperty); | ||
|
||
public static void SetHeadingLevel(BindableObject view, HeadingLevel value) => view.SetValue(HeadingLevelProperty, value); | ||
|
||
|
||
public static readonly BindableProperty DescriptionProperty = BindableProperty.CreateAttached("Description", typeof(string), typeof(SemanticEffect), default(string), propertyChanged: OnPropertyChanged); | ||
|
||
public static string GetDescription(BindableObject bindable) => (string)bindable.GetValue(DescriptionProperty); | ||
|
||
public static void SetDescription(BindableObject bindable, string value) => bindable.SetValue(DescriptionProperty, value); | ||
|
||
public static readonly BindableProperty HintProperty = BindableProperty.CreateAttached("Hint", typeof(string), typeof(SemanticEffect), default(string), propertyChanged: OnPropertyChanged); | ||
|
||
public static string GetHint(BindableObject bindable) => (string)bindable.GetValue(HintProperty); | ||
|
||
public static void SetHint(BindableObject bindable, string value) => bindable.SetValue(HintProperty, value); | ||
|
||
static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue) | ||
{ | ||
if (bindable is not View view) | ||
return; | ||
|
||
if (view.Effects.FirstOrDefault(x => x is SemanticEffectRouter) == null) | ||
{ | ||
view.Effects.Add(new SemanticEffectRouter()); | ||
} | ||
} | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
...ommunityToolkit/Xamarin.CommunityToolkit/Effects/Semantic/SemanticEffectRouter.android.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
using AndroidX.Core.View; | ||
using System.ComponentModel; | ||
using Xamarin.Forms; | ||
using Xamarin.CommunityToolkit.Effects; | ||
using Effects = Xamarin.CommunityToolkit.Android.Effects; | ||
using AndroidX.Core.View.Accessibiity; | ||
using Android.Widget; | ||
|
||
[assembly: ExportEffect(typeof(Effects.SemanticEffectRouter), nameof(SemanticEffectRouter))] | ||
|
||
namespace Xamarin.CommunityToolkit.Android.Effects | ||
{ | ||
/// <summary> | ||
/// Android implementation of the <see cref="SemanticEffect" /> | ||
/// </summary> | ||
public class SemanticEffectRouter : SemanticEffectRouterBase<SemanticEffectRouter> | ||
{ | ||
SemanticAccessibilityDelegate? semanticAccessibilityDelegate; | ||
|
||
protected override void Update(global::Android.Views.View view, SemanticEffectRouter effect) | ||
{ | ||
var isHeading = SemanticEffect.GetHeadingLevel(Element) != CommunityToolkit.Effects.Semantic.HeadingLevel.None; | ||
ViewCompat.SetAccessibilityHeading(view, isHeading); | ||
var desc = SemanticEffect.GetDescription(Element); | ||
var hint = SemanticEffect.GetHint(Element); | ||
|
||
if (!string.IsNullOrEmpty(hint) || !string.IsNullOrEmpty(desc)) | ||
{ | ||
if (semanticAccessibilityDelegate == null) | ||
{ | ||
semanticAccessibilityDelegate = new SemanticAccessibilityDelegate(Element); | ||
ViewCompat.SetAccessibilityDelegate(view, semanticAccessibilityDelegate); | ||
} | ||
} | ||
else if (semanticAccessibilityDelegate != null) | ||
{ | ||
semanticAccessibilityDelegate = null; | ||
ViewCompat.SetAccessibilityDelegate(view, null); | ||
} | ||
|
||
if (semanticAccessibilityDelegate != null) | ||
{ | ||
semanticAccessibilityDelegate.Element = Element; | ||
view.ImportantForAccessibility = global::Android.Views.ImportantForAccessibility.Yes; | ||
} | ||
} | ||
|
||
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args) | ||
{ | ||
base.OnElementPropertyChanged(args); | ||
|
||
if (args.PropertyName == SemanticEffect.HeadingLevelProperty.PropertyName || | ||
args.PropertyName == SemanticEffect.DescriptionProperty.PropertyName || | ||
args.PropertyName == SemanticEffect.HintProperty.PropertyName) | ||
{ | ||
Update(); | ||
} | ||
} | ||
|
||
class SemanticAccessibilityDelegate : AccessibilityDelegateCompat | ||
{ | ||
public Element Element { get; set; } | ||
|
||
public SemanticAccessibilityDelegate(Element element) | ||
{ | ||
Element = element; | ||
} | ||
|
||
public override void OnInitializeAccessibilityNodeInfo(global::Android.Views.View host, AccessibilityNodeInfoCompat info) | ||
{ | ||
base.OnInitializeAccessibilityNodeInfo(host, info); | ||
|
||
if (Element == null) | ||
return; | ||
|
||
if (info == null) | ||
return; | ||
|
||
var hint = SemanticEffect.GetHint(Element); | ||
if (!string.IsNullOrEmpty(hint)) | ||
{ | ||
info.HintText = hint; | ||
|
||
if (host is EditText) | ||
info.ShowingHintText = false; | ||
} | ||
|
||
var desc = SemanticEffect.GetDescription(Element); | ||
if (!string.IsNullOrEmpty(desc)) | ||
{ | ||
info.ContentDescription = desc; | ||
} | ||
} | ||
} | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/Semantic/SemanticEffectRouter.ios.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using System.ComponentModel; | ||
using UIKit; | ||
using Xamarin.CommunityToolkit.Effects; | ||
using Xamarin.Forms; | ||
using Effects = Xamarin.CommunityToolkit.iOS.Effects; | ||
|
||
[assembly: ExportEffect(typeof(Effects.SemanticEffectRouter), nameof(SemanticEffectRouter))] | ||
|
||
namespace Xamarin.CommunityToolkit.iOS.Effects | ||
{ | ||
/// <summary> | ||
/// iOS implementation of the <see cref="SemanticEffect" /> | ||
/// </summary> | ||
public class SemanticEffectRouter : SemanticEffectRouterBase<SemanticEffectRouter> | ||
{ | ||
public SemanticEffectRouter() | ||
{ | ||
} | ||
|
||
protected override void Update(UIView view, SemanticEffectRouter effect) | ||
{ | ||
var isHeading = SemanticEffect.GetHeadingLevel(Element) != CommunityToolkit.Effects.Semantic.HeadingLevel.None; | ||
|
||
if (isHeading) | ||
view.AccessibilityTraits |= UIAccessibilityTrait.Header; | ||
else | ||
view.AccessibilityTraits &= ~UIAccessibilityTrait.Header; | ||
|
||
var desc = SemanticEffect.GetDescription(Element); | ||
var hint = SemanticEffect.GetHint(Element); | ||
view.AccessibilityLabel = desc; | ||
view.AccessibilityHint = hint; | ||
|
||
// UIControl elements automatically have IsAccessibilityElement set to true | ||
if (view is not UIControl && (!string.IsNullOrWhiteSpace(hint) || !string.IsNullOrWhiteSpace(desc))) | ||
{ | ||
view.IsAccessibilityElement = true; | ||
} | ||
} | ||
|
||
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args) | ||
{ | ||
base.OnElementPropertyChanged(args); | ||
|
||
if (args.PropertyName == SemanticEffect.HeadingLevelProperty.PropertyName || | ||
args.PropertyName == SemanticEffect.DescriptionProperty.PropertyName || | ||
args.PropertyName == SemanticEffect.HintProperty.PropertyName) | ||
{ | ||
Update(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.