[C/X] Clean API and impl of OnAppTheme (#10592) fixes #10395

* [C/X] Clean API and impl of OnAppTheme

* [C] User override for app theme

- fixes #10395

* fix tests

* Update Xamarin.Forms.Core/Application.cs

Co-authored-by: Gerald Versluis <gerald.versluis@microsoft.com>

Co-authored-by: Rui Marinho <me@ruimarinho.net>
Co-authored-by: Gerald Versluis <gerald.versluis@microsoft.com>
This commit is contained in:
Stephane Delcroix 2020-06-01 13:17:37 +02:00 коммит произвёл GitHub
Родитель 668146339a
Коммит b6e323d157
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 347 добавлений и 401 удалений

Просмотреть файл

@ -30,7 +30,7 @@ namespace Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries
Text = "TextColor through SetAppThemeColor"
};
onThemeLabel.SetBinding(Label.TextColorProperty, new AppThemeColor() { Light = Color.Green, Dark = Color.Red });
onThemeLabel.SetBinding(Label.TextColorProperty, new OnAppTheme<Color>() { Light = Color.Green, Dark = Color.Red });
onThemeLabel1.SetOnAppTheme(Label.TextColorProperty, Color.Green, Color.Red);

Просмотреть файл

@ -1,49 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:gallerypages="clr-namespace:Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries" x:Class="Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries.AppThemeXamlGallery" BackgroundColor="{OnAppTheme Light=Lightgray, Dark=Black}">
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:gallerypages="clr-namespace:Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries" x:Class="Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries.AppThemeXamlGallery" BackgroundColor="{AppThemeBinding Light=Lightgray, Dark=Black}">
<ContentPage.Resources>
<ResourceDictionary>
<gallerypages:FooConverter x:Key="fooConv"/>
</ResourceDictionary>
<AppThemeColor x:Key="MyColor" Light="HotPink" Dark="Yellow" />
<Style x:Key="OSThemeStyle" TargetType="Label">
<Setter Property="TextColor" Value="{OnAppTheme Black, Light=Green, Dark=Red}" />
<Setter Property="TextColor" Value="{AppThemeBinding Black, Light=Green, Dark=Red}" />
</Style>
<!--<StyleSheet>
<OnAppTheme>
<OnAppTheme.Light>
<![CDATA[
#cssStyledLabel {
color: purple;
}
]]>
</OnAppTheme.Light>
<OnAppTheme.Dark>
<![CDATA[
#cssStyledLabel {
color: orange;
}
]]>
</OnAppTheme.Dark>
</OnAppTheme>
</StyleSheet>-->
</ContentPage.Resources>
<ScrollView>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label TextColor="{OnAppTheme Light=Green, Dark=Red}">OnAppThemeExtension</Label>
<Label Text="OnAppTheme XAML tag">
<Label TextColor="{AppThemeBinding Light=Green, Dark=Red}">OnAppThemeExtension</Label>
<Label Text="AppThemeBinding XAML tag">
<Label.TextColor>
<OnAppTheme x:TypeArguments="Color" Light="Green" Dark="Red" />
<AppThemeBinding Light="Green" Dark="Red" />
</Label.TextColor>
</Label>
<Label Style="{DynamicResource Key=OSThemeStyle}">DynamicResource Style</Label>
<Label TextColor="{DynamicResource MyColor}">DynamicResource Color</Label>
<Label TextColor="{StaticResource MyColor}">StaticResource</Label>
<!--<Label x:Name="cssStyledLabel">This text is Purple or Orange depending on Light (or default) or Dark (through CSS)</Label>-->
<Label>Image with OnAppThemeExtension</Label>
<Image Source="{OnAppTheme Light=xamarinlogo.png, Dark=Fruits.jpg}" />
<!--<gallerypages:CustomControl TextColor="Brown" Text="This custom control should have brown text" />-->
<Label TextColor="{OnAppTheme Light=1, Dark=2, Converter={StaticResource fooConv}}">With ValueConverter</Label>
<Image Source="{AppThemeBinding Light=xamarinlogo.png, Dark=Fruits.jpg}" />
</StackLayout>
</ScrollView>
</ContentPage>

Просмотреть файл

@ -65,7 +65,7 @@ namespace Xamarin.Forms.Core.UnitTests
void SetAppTheme(OSAppTheme theme)
{
((MockPlatformServices)Device.PlatformServices).RequestedTheme = theme;
Application.Current.OnRequestedThemeChanged(new AppThemeChangedEventArgs(theme));
Application.Current.TriggerThemeChanged(new AppThemeChangedEventArgs(theme));
}
}
}

Просмотреть файл

@ -1,4 +0,0 @@
namespace Xamarin.Forms
{
public class AppThemeColor : OnAppTheme<Color> { }
}

Просмотреть файл

@ -155,7 +155,16 @@ namespace Xamarin.Forms
}
}
public OSAppTheme RequestedTheme => Device.PlatformServices.RequestedTheme;
public OSAppTheme UserAppTheme
{
get => _userAppTheme;
set
{
_userAppTheme = value;
TriggerThemeChangedActual(new AppThemeChangedEventArgs(value));
}
}
public OSAppTheme RequestedTheme => UserAppTheme == OSAppTheme.Unspecified ? Device.PlatformServices.RequestedTheme : UserAppTheme;
public event EventHandler<AppThemeChangedEventArgs> RequestedThemeChanged
{
@ -165,9 +174,17 @@ namespace Xamarin.Forms
bool _themeChangedFiring;
OSAppTheme _lastAppTheme;
OSAppTheme _userAppTheme = OSAppTheme.Unspecified;
[EditorBrowsable(EditorBrowsableState.Never)]
public void OnRequestedThemeChanged(AppThemeChangedEventArgs args)
public void TriggerThemeChanged(AppThemeChangedEventArgs args)
{
if (UserAppTheme != OSAppTheme.Unspecified)
return;
TriggerThemeChangedActual(args);
}
void TriggerThemeChangedActual(AppThemeChangedEventArgs args)
{
if (Device.Flags == null || Device.Flags.IndexOf(ExperimentalFlags.AppThemeExperimental) == -1)
return;

Просмотреть файл

@ -67,21 +67,5 @@ namespace Xamarin.Forms
return returnIfNotSet;
}
public static void SetOnAppTheme<T>(this BindableObject self, BindableProperty targetProperty, T light, T dark, T defaultValue = default)
{
ExperimentalFlags.VerifyFlagEnabled(nameof(BindableObjectExtensions), ExperimentalFlags.AppThemeExperimental, nameof(BindableObjectExtensions), nameof(SetOnAppTheme));
var appTheme = new OnAppTheme<T> { Light = light, Dark = dark, Default = defaultValue };
self.SetBinding(targetProperty, appTheme);
}
public static void SetAppThemeColor(this BindableObject self, BindableProperty targetProperty, Color light, Color dark, Color defaultValue = default)
{
ExperimentalFlags.VerifyFlagEnabled(nameof(BindableObjectExtensions), ExperimentalFlags.AppThemeExperimental, nameof(BindableObjectExtensions), nameof(SetAppThemeColor));
var appTheme = new AppThemeColor { Light = light, Dark = dark, Default = defaultValue };
self.SetBinding(targetProperty, appTheme);
}
}
}

Просмотреть файл

@ -2,29 +2,14 @@
namespace Xamarin.Forms
{
public class OnAppTheme<T> : BindingBase
class OnAppTheme<T> : BindingBase
{
WeakReference<BindableObject> _weakTarget;
BindableProperty _targetProperty;
public OnAppTheme()
{
Application.Current.RequestedThemeChanged += ThemeChanged;
}
void ThemeChanged(object sender, AppThemeChangedEventArgs e)
{
Device.BeginInvokeOnMainThread(() => ApplyCore());
}
public new BindingMode Mode
{
get => base.Mode;
private set { }
}
internal override BindingBase Clone()
{
return new OnAppTheme<T> { Light = Light, Dark = Dark, Default = Default };
}
public OnAppTheme() => Application.Current.RequestedThemeChanged += (o,e) => Device.BeginInvokeOnMainThread(() => ApplyCore());
internal override BindingBase Clone() => new OnAppTheme<T> { Light = Light, Dark = Dark, Default = Default };
internal override void Apply(bool fromTarget)
{
@ -39,6 +24,14 @@ namespace Xamarin.Forms
base.Apply(context, bindObj, targetProperty, fromBindingContextChanged);
ApplyCore();
}
internal override void Unapply(bool fromBindingContextChanged = false)
{
base.Unapply(fromBindingContextChanged);
_weakTarget = null;
_targetProperty = null;
}
void ApplyCore()
{
if (_weakTarget == null || !_weakTarget.TryGetTarget(out var target))
@ -63,6 +56,7 @@ namespace Xamarin.Forms
_isLightSet = true;
}
}
public T Dark
{
get => _dark;
@ -72,6 +66,7 @@ namespace Xamarin.Forms
_isDarkSet = true;
}
}
public T Default
{
get => _default;
@ -82,13 +77,6 @@ namespace Xamarin.Forms
}
}
public static implicit operator T(OnAppTheme<T> onAppTheme)
{
return onAppTheme.GetValue();
}
public T Value => GetValue();
T GetValue()
{
switch (Application.Current.RequestedTheme)

Просмотреть файл

@ -250,13 +250,6 @@ namespace Xamarin.Forms
return OnBackButtonPressed();
}
protected override void OnRequestedThemeChanged(OSAppTheme newValue)
{
base.OnRequestedThemeChanged(newValue);
Resources?.Reload();
}
protected virtual void LayoutChildren(double x, double y, double width, double height)
{
var area = new Rectangle(x, y, width, height);

Просмотреть файл

@ -275,13 +275,6 @@ namespace Xamarin.Forms
internal VisualElement()
{
if (Application.Current != null)
Application.Current.RequestedThemeChanged += (s, a) => OnRequestedThemeChanged(a.RequestedTheme);
}
protected virtual void OnRequestedThemeChanged(OSAppTheme newValue)
{
ExperimentalFlags.VerifyFlagEnabled(nameof(VisualElement), ExperimentalFlags.AppThemeExperimental, nameof(OnRequestedThemeChanged));
}
public double AnchorX

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Xamarin.Forms
{
public static class VisualElementExtensions
{
public static void SetOnAppTheme<T>(this VisualElement self, BindableProperty targetProperty, T light, T dark)
{
ExperimentalFlags.VerifyFlagEnabled(nameof(BindableObjectExtensions), ExperimentalFlags.AppThemeExperimental, nameof(BindableObjectExtensions), nameof(SetOnAppTheme));
self.SetBinding(targetProperty, new OnAppTheme<T> { Light = light, Dark = dark});
}
public static void SetAppThemeColor(this VisualElement self, BindableProperty targetProperty, Color light, Color dark) => SetOnAppTheme(self, targetProperty, light, dark);
}
}

Просмотреть файл

@ -87,7 +87,7 @@ namespace Xamarin.Forms.Platform.Android
base.OnConfigurationChanged(newConfig);
ConfigurationChanged?.Invoke(this, new EventArgs());
Xamarin.Forms.Application.Current?.OnRequestedThemeChanged(new AppThemeChangedEventArgs(Xamarin.Forms.Application.Current.RequestedTheme));
Xamarin.Forms.Application.Current?.TriggerThemeChanged(new AppThemeChangedEventArgs(Xamarin.Forms.Application.Current.RequestedTheme));
}
public override bool OnOptionsItemSelected(IMenuItem item)

Просмотреть файл

@ -171,7 +171,7 @@ namespace Xamarin.Forms.Platform.UWP
async void UISettingsColorValuesChanged(UISettings sender, object args)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => Application.Current?.OnRequestedThemeChanged(new AppThemeChangedEventArgs(Application.Current.RequestedTheme)));
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => Application.Current?.TriggerThemeChanged(new AppThemeChangedEventArgs(Application.Current.RequestedTheme)));
}
async Task TryAllDispatchers(Action action)
@ -261,4 +261,4 @@ namespace Xamarin.Forms.Platform.UWP
public OSAppTheme RequestedTheme => Windows.UI.Xaml.Application.Current.RequestedTheme == ApplicationTheme.Dark ? OSAppTheme.Dark : OSAppTheme.Light;
}
}
}

Просмотреть файл

@ -370,7 +370,7 @@ namespace Xamarin.Forms.Platform.iOS
#if __XCODE11__
if (Forms.IsiOS13OrNewer && previousTraitCollection.UserInterfaceStyle != TraitCollection.UserInterfaceStyle)
Application.Current?.OnRequestedThemeChanged(new AppThemeChangedEventArgs(Application.Current.RequestedTheme));
Application.Current?.TriggerThemeChanged(new AppThemeChangedEventArgs(Application.Current.RequestedTheme));
#endif
}

Просмотреть файл

@ -0,0 +1,159 @@
using NUnit.Framework;
using Xamarin.Forms.Core.UnitTests;
namespace Xamarin.Forms.Xaml.UnitTests
{
[TestFixture]
public class OnAppThemeTests : BaseTestFixture
{
[SetUp]
public override void Setup()
{
base.Setup();
Device.SetFlags(new[] { "AppTheme_Experimental" });
Application.Current = new MockApplication();
}
[TearDown]
public override void TearDown()
{
Application.Current = null;
base.TearDown();
}
[Test]
public void OnAppThemeExtensionLightDarkColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml"" TextColor=""{AppThemeBinding Light = Green, Dark = Red}
"">This text is green or red depending on Light (or default) or Dark</Label>";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Dark;
label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Red, label.TextColor);
}
[Test]
public void OnAppThemeLightDarkColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeBinding Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Dark;
label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Red, label.TextColor);
}
[Test]
public void OnAppThemeUnspecifiedThemeDefaultsToLightColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeBinding Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Unspecified;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
[Test]
public void OnAppThemeUnspecifiedLightColorDefaultsToDefault()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeBinding Default=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
[Test]
public void AppThemeColorLightDark()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeBinding Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Dark;
label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Red, label.TextColor);
}
[Test]
public void AppThemeColorUnspecifiedThemeDefaultsToLightColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeBinding Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Unspecified;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
[Test]
public void AppThemeColorUnspecifiedLightColorDefaultsToDefault()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeBinding Default=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Unspecified;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
}
}

Просмотреть файл

@ -129,157 +129,4 @@ namespace Xamarin.Forms.Xaml.UnitTests
Assert.AreEqual (StackOrientation.Horizontal, layout.Orientation);
}
}
[TestFixture]
public class OnAppThemeTests : BaseTestFixture
{
[SetUp]
public override void Setup()
{
base.Setup();
Device.SetFlags(new[] { "AppTheme_Experimental" });
Application.Current = new MockApplication();
}
[TearDown]
public override void TearDown()
{
Application.Current = null;
base.TearDown();
}
[Test]
public void OnAppThemeExtensionLightDarkColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml"" TextColor=""{OnAppTheme Light = Green, Dark = Red}
"">This text is green or red depending on Light (or default) or Dark</Label>";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Dark;
label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Red, label.TextColor);
}
[Test]
public void OnAppThemeLightDarkColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<OnAppTheme x:TypeArguments=""Color"" Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Dark;
label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Red, label.TextColor);
}
[Test]
public void OnAppThemeUnspecifiedThemeDefaultsToLightColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<OnAppTheme x:TypeArguments=""Color"" Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Unspecified;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
[Test]
public void OnAppThemeUnspecifiedLightColorDefaultsToDefault()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<OnAppTheme x:TypeArguments=""Color"" Default=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
[Test]
public void AppThemeColorLightDark()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeColor Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Light;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Dark;
label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Red, label.TextColor);
}
[Test]
public void AppThemeColorUnspecifiedThemeDefaultsToLightColor()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeColor Light=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Unspecified;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
[Test]
public void AppThemeColorUnspecifiedLightColorDefaultsToDefault()
{
var xaml = @"
<Label
xmlns=""http://xamarin.com/schemas/2014/forms""
xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml""
Text=""This text is green or red depending on Light(or default) or Dark"">
<Label.TextColor>
<AppThemeColor Default=""Green"" Dark=""Red"" />
</Label.TextColor>
</Label> ";
((MockPlatformServices)Device.PlatformServices).RequestedTheme = OSAppTheme.Unspecified;
var label = new Label().LoadFromXaml(xaml);
Assert.AreEqual(Color.Green, label.TextColor);
}
}
}

Просмотреть файл

@ -23,7 +23,7 @@ namespace Xamarin.Forms.Xaml
else if (match == "OnIdiom")
markupExtension = new OnIdiomExtension();
else if (match == "OnAppTheme")
markupExtension = new OnAppThemeExtension();
markupExtension = new AppThemeBindingExtension();
else if (match == "DataTemplate")
markupExtension = new DataTemplateExtension();
else

Просмотреть файл

@ -0,0 +1,126 @@
using System;
using System.Globalization;
using System.Reflection;
namespace Xamarin.Forms.Xaml
{
[ContentProperty(nameof(Default))]
public class AppThemeBindingExtension : IMarkupExtension<BindingBase>
{
object _default;
bool _hasdefault;
object _light;
bool _haslight;
object _dark;
bool _hasdark;
public AppThemeBindingExtension()
{
ExperimentalFlags.VerifyFlagEnabled(nameof(AppThemeBindingExtension), ExperimentalFlags.AppThemeExperimental, nameof(AppThemeBindingExtension));
}
public object Default
{
get => _default; set
{
_default = value;
_hasdefault = true;
}
}
public object Light
{
get => _light; set
{
_light = value;
_haslight = true;
}
}
public object Dark
{
get => _dark; set
{
_dark = value;
_hasdark = true;
}
}
public object Value { get; private set; }
public object ProvideValue(IServiceProvider serviceProvider) => (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
{
if (Default == null
&& Light == null
&& Dark == null)
throw new XamlParseException("AppThemeBindingExtension requires a non-null value to be specified for at least one theme or Default.", serviceProvider);
var valueProvider = serviceProvider?.GetService<IProvideValueTarget>() ?? throw new ArgumentException();
BindableProperty bp;
PropertyInfo pi = null;
Type propertyType = null;
if (valueProvider.TargetObject is Setter setter)
bp = setter.Property;
else
{
bp = valueProvider.TargetProperty as BindableProperty;
pi = valueProvider.TargetProperty as PropertyInfo;
}
propertyType = bp?.ReturnType
?? pi?.PropertyType
?? throw new InvalidOperationException("Cannot determine property to provide the value for.");
var converterProvider = serviceProvider?.GetService<IValueConverterProvider>();
MemberInfo minforetriever()
{
if (pi != null)
return pi;
MemberInfo minfo = null;
try
{
minfo = bp.DeclaringType.GetRuntimeProperty(bp.PropertyName);
}
catch (AmbiguousMatchException e)
{
throw new XamlParseException($"Multiple properties with name '{bp.DeclaringType}.{bp.PropertyName}' found.", serviceProvider, innerException: e);
}
if (minfo != null)
return minfo;
try
{
return bp.DeclaringType.GetRuntimeMethod("Get" + bp.PropertyName, new[] { typeof(BindableObject) });
}
catch (AmbiguousMatchException e)
{
throw new XamlParseException($"Multiple methods with name '{bp.DeclaringType}.Get{bp.PropertyName}' found.", serviceProvider, innerException: e);
}
}
var binding = new OnAppTheme<object>();
if (converterProvider != null)
{
if (_haslight) binding.Light = converterProvider.Convert(Light, propertyType, minforetriever, serviceProvider);
if (_hasdark) binding.Dark = converterProvider.Convert(Dark, propertyType, minforetriever, serviceProvider);
if (_hasdefault) binding.Default = converterProvider.Convert(Default, propertyType, minforetriever, serviceProvider);
return binding;
}
Exception converterException = null;
if (converterException != null && _haslight)
binding.Light = Light.ConvertTo(propertyType, minforetriever, serviceProvider, out converterException);
if (converterException != null && _hasdark)
binding.Dark = Dark.ConvertTo(propertyType, minforetriever, serviceProvider, out converterException);
if (converterException != null && _hasdefault)
binding.Default = Default.ConvertTo(propertyType, minforetriever, serviceProvider, out converterException);
if (converterException != null)
throw converterException;
return binding;
}
}
}

Просмотреть файл

@ -1,147 +0,0 @@
using System;
using System.Globalization;
using System.Reflection;
namespace Xamarin.Forms.Xaml
{
[ContentProperty(nameof(Default))]
public class OnAppThemeExtension : IMarkupExtension<BindingBase>
{
public OnAppThemeExtension()
{
ExperimentalFlags.VerifyFlagEnabled(nameof(OnAppThemeExtension), ExperimentalFlags.AppThemeExperimental, nameof(OnAppThemeExtension));
Application.Current.RequestedThemeChanged += RequestedThemeChanged;
}
public object Default { get; set; }
public object Light { get; set; }
public object Dark { get; set; }
public object Value { get; private set; }
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
}
object GetValue()
{
switch (Application.Current?.RequestedTheme)
{
default:
case OSAppTheme.Light:
return Light ?? Default;
case OSAppTheme.Dark:
return Dark ?? Default;
}
}
void RequestedThemeChanged(object sender, AppThemeChangedEventArgs e)
{
Value = GetValue();
}
BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
{
if (Default == null
&& Light == null
&& Dark == null)
throw new XamlParseException("OnAppThemeExtension requires a non-null value to be specified for at least one theme or Default.", serviceProvider);
var valueProvider = serviceProvider?.GetService<IProvideValueTarget>() ?? throw new ArgumentException();
BindableProperty bp;
PropertyInfo pi = null;
Type propertyType = null;
if (valueProvider.TargetObject is Setter setter)
{
bp = setter.Property;
}
else
{
bp = valueProvider.TargetProperty as BindableProperty;
pi = valueProvider.TargetProperty as PropertyInfo;
}
propertyType = bp?.ReturnType
?? pi?.PropertyType
?? throw new InvalidOperationException("Cannot determine property to provide the value for.");
if (Converter != null)
{
var light = Converter.Convert(Light, propertyType, ConverterParameter, CultureInfo.CurrentUICulture);
var dark = Converter.Convert(Dark, propertyType, ConverterParameter, CultureInfo.CurrentUICulture);
var def = Converter.Convert(Dark, propertyType, ConverterParameter, CultureInfo.CurrentUICulture);
return new OnAppTheme<object> { Light = light, Dark = dark, Default = def };
}
var converterProvider = serviceProvider?.GetService<IValueConverterProvider>();
if (converterProvider != null)
{
MemberInfo minforetriever()
{
if (pi != null)
return pi;
MemberInfo minfo = null;
try
{
minfo = bp.DeclaringType.GetRuntimeProperty(bp.PropertyName);
}
catch (AmbiguousMatchException e)
{
throw new XamlParseException($"Multiple properties with name '{bp.DeclaringType}.{bp.PropertyName}' found.", serviceProvider, innerException: e);
}
if (minfo != null)
return minfo;
try
{
return bp.DeclaringType.GetRuntimeMethod("Get" + bp.PropertyName, new[] { typeof(BindableObject) });
}
catch (AmbiguousMatchException e)
{
throw new XamlParseException($"Multiple methods with name '{bp.DeclaringType}.Get{bp.PropertyName}' found.", serviceProvider, innerException: e);
}
}
var light = converterProvider.Convert(Light, propertyType, minforetriever, serviceProvider);
var dark = converterProvider.Convert(Dark, propertyType, minforetriever, serviceProvider);
var def = converterProvider.Convert(Dark, propertyType, minforetriever, serviceProvider);
return new OnAppTheme<object> { Light = light, Dark = dark, Default = def };
}
if (converterProvider != null)
{
var light = converterProvider.Convert(Light, propertyType, () => pi, serviceProvider);
var dark = converterProvider.Convert(Dark, propertyType, () => pi, serviceProvider);
var def = converterProvider.Convert(Default, propertyType, () => pi, serviceProvider);
return new OnAppTheme<object> { Light = light, Dark = dark, Default = def };
}
var lightConverted = Light.ConvertTo(propertyType, () => pi, serviceProvider, out Exception lightException);
if (lightException != null)
throw lightException;
var darkConverted = Dark.ConvertTo(propertyType, () => pi, serviceProvider, out Exception darkException);
if (darkException != null)
throw darkException;
var defaultConverted = Dark.ConvertTo(propertyType, () => pi, serviceProvider, out Exception defaultException);
if (defaultException != null)
throw defaultException;
return new OnAppTheme<object> { Light = Light, Dark = Dark, Default = defaultConverted };
}
}
}