зеркало из https://github.com/DeGsoft/maui-linux.git
* [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:
Родитель
668146339a
Коммит
b6e323d157
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче