Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0efdc5a
Base implementations for SemanticEffect and SemanticOrderView
jamesmontemagno Apr 26, 2021
c92d669
- uwp and iOS fix
PureWeen Apr 29, 2021
dfdb369
- uwp tab index
PureWeen Apr 29, 2021
616359c
- hint and description
PureWeen Apr 29, 2021
7c8e4b4
- fix iOS to auto set to important for accessibility
PureWeen Apr 29, 2021
c18f37a
Merge branch 'main' into accessibility-views-effects
jsuarezruiz May 4, 2021
7d1193b
Add samples and fix up crashes
jamesmontemagno May 4, 2021
980c8e6
- for UI Test reason switch to using a delegate for reading content D…
PureWeen May 5, 2021
a5b52cf
- use the delegate if the automation id is set
PureWeen May 5, 2021
5c0b0f4
- fixes
PureWeen May 6, 2021
86db533
- set yes less often
PureWeen May 6, 2021
edb5026
Remove IsInAccessibleTree from PancakeView
rachelkang May 6, 2021
46d5f84
Update SemanticEffectPage.xaml
rachelkang May 6, 2021
a7239c7
Update SemanticOrderViewPage.xaml
rachelkang May 6, 2021
6c57f52
Update SemanticOrderViewPage.xaml.cs
rachelkang May 6, 2021
aff986f
Fix Semantic Description on Android
rachelkang May 6, 2021
83cb46f
Reword SemanticEffect about
rachelkang May 6, 2021
09c4640
- added temporary workaround for making a screen clickable
PureWeen May 6, 2021
f5ca514
Merge branch 'accessibility-views-effects' of https://github.com/xama…
rachelkang May 6, 2021
18ae418
Fix SemanticOrderViewPage sample
rachelkang May 6, 2021
300d569
Fix NRE thrown on back nav from iOS SemanticOrderView page
rachelkang May 6, 2021
d200667
- fix UWP router
PureWeen May 6, 2021
c507bad
- fix null checks
PureWeen May 6, 2021
727873e
- changed first button to button so uwp sample is interesting
PureWeen May 6, 2021
626e09b
Clean up code - Pedro's feedback
rachelkang May 7, 2021
36b39e4
Merge branch 'accessibility-views-effects' of https://github.com/xama…
rachelkang May 7, 2021
dfdd78a
Merge branch 'main' into accessibility-views-effects
rachelkang May 7, 2021
b34d677
Merge branch 'main' into accessibility-views-effects
jfversluis May 7, 2021
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
56 changes: 56 additions & 0 deletions samples/XCT.Sample.Android/AccessiblePageRenderer.cs
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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Include="AccessiblePageRenderer.cs" />
<Compile Include="MainActivity.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\Resource.designer.cs" />
Expand Down
35 changes: 35 additions & 0 deletions samples/XCT.Sample/Pages/Effects/SemanticEffectPage.xaml
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 samples/XCT.Sample/Pages/Effects/SemanticEffectPage.xaml.cs
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();
}
}
}
21 changes: 21 additions & 0 deletions samples/XCT.Sample/Pages/Views/SemanticOrderViewPage.xaml
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 samples/XCT.Sample/Pages/Views/SemanticOrderViewPage.xaml.cs
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 };
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new SectionModel(
typeof(SafeAreaEffectPage),
nameof(SafeAreaEffect),
"The SafeAreaEffect is an effectwill help to make sure that content isn't clipped by rounded device corners, the home indicator, or the sensor housing on an iPhone X (or alike)"),
"The SafeAreaEffect is an effect that will help to make sure that content isn't clipped by rounded device corners, the home indicator, or the sensor housing on an iPhone X (or alike)"),

new SectionModel(
typeof(RemoveBorderEffectPage),
Expand Down Expand Up @@ -42,7 +42,12 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new SectionModel(
typeof(ShadowEffectPage),
nameof(ShadowEffect),
"The ShadowEffect allows all views to display shadow.")
"The ShadowEffect allows all views to display shadow."),

new SectionModel(
typeof(SemanticEffectPage),
nameof(SemanticEffect),
"The SemanticEffect allows you to set semantic properties for accessibility.")
};
}
}
3 changes: 3 additions & 0 deletions samples/XCT.Sample/ViewModels/Views/ViewsGalleryViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new SectionModel(typeof(RangeSliderPage), "RangeSlider",
"The RangeSlider is a slider with two thumbs allowing to select numeric ranges"),

new SectionModel(typeof(SemanticOrderViewPage), "SemanticOrderView",
"Set accessiblity ordering on views"),

new SectionModel(typeof(SnackBarPage), "SnackBar/Toast",
"Show SnackBar, Toast etc"),

Expand Down
6 changes: 6 additions & 0 deletions samples/XCT.Sample/Xamarin.CommunityToolkit.Sample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Pages\Effects\SemanticEffectPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Pages\Views\SemanticOrderViewPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resx\AppResources.es.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<DependentUpon>AppResources.resx</DependentUpon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,10 @@ sealed class EffectIds
/// Effect Id for <see cref="ShadowEffect"/>
/// </summary>
public static string ShadowEffect => $"{effectResolutionGroupName}.{nameof(ShadowEffect)}";

/// <summary>
/// Effect Id for <see cref="SemanticEffect"/>
/// </summary>
public static string Semantic => $"{effectResolutionGroupName}.{nameof(SemanticEffectRouter)}";
}
}
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());
}
}
}
}
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;

if (semanticAccessibilityDelegate != null)
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;
}
}
}
}
}
Loading