Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
Runtime updating of AppTheme values (#10442)
Browse files Browse the repository at this point in the history
* Make things BindingBase

* Update OnAppTheme.cs

* Runtime updating

* Fixed

* Cleanup

* Update VisualElement.cs

* Update Application.cs

* Fix NRE for unit tests

* Review feedback

* Update Application.cs

fixes #10391
  • Loading branch information
jfversluis committed Apr 28, 2020
1 parent b11153d commit 6c40121
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -1,42 +1,54 @@
namespace Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries
using System;

namespace Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries
{
public class AppThemeCodeGallery : ContentPage
{
AppThemeColor color = new AppThemeColor { Light = Color.Green, Dark = Color.Red };
public Color TheColor
{
get => color.Value;
}
Label _currentThemeLabel;

public AppThemeCodeGallery()
{
var currentThemeLabel = new Label
_currentThemeLabel = new Label
{
Text = Application.Current.RequestedTheme.ToString()
};

Application.Current.RequestedThemeChanged += (s, a) =>
Application.Current.RequestedThemeChanged += Current_RequestedThemeChanged;

var onThemeLabel = new Label
{
currentThemeLabel.Text = Application.Current.RequestedTheme.ToString();
OnPropertyChanged(nameof(TheColor));
Text = "TextColor through SetBinding"
};

var onThemeLabel = new Label
var onThemeLabel1 = new Label
{
Text = "TextColor through SetAppTheme"
};

var onThemeLabel2 = new Label
{
Text = "This text is green or red depending on Light (or default) or Dark"
Text = "TextColor through SetAppThemeColor"
};

onThemeLabel.SetBinding(Label.TextColorProperty, new Binding(nameof(TheColor)));
BindingContext = this;
onThemeLabel.SetBinding(Label.TextColorProperty, new AppThemeColor() { Light = Color.Green, Dark = Color.Red });

onThemeLabel1.SetOnAppTheme(Label.TextColorProperty, Color.Green, Color.Red);

onThemeLabel2.SetAppThemeColor(Label.TextColorProperty, Color.Green, Color.Red);

var stackLayout = new StackLayout
{
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Children = { currentThemeLabel, onThemeLabel }
Children = { _currentThemeLabel, onThemeLabel , onThemeLabel1,onThemeLabel2 }
};

Content = stackLayout;
}

private void Current_RequestedThemeChanged(object sender, AppThemeChangedEventArgs e)
{
_currentThemeLabel.Text = Application.Current.RequestedTheme.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?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=Darkgray}">
<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.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}" />
Expand All @@ -26,19 +29,21 @@
</ContentPage.Resources>
<ScrollView>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label TextColor="{OnAppTheme Light=Green, Dark=Red}">This text is green or red depending on Light (or default) or Dark</Label>
<Label Text="Testing 1, 2, 3">
<Label TextColor="{OnAppTheme Light=Green, Dark=Red}">OnAppThemeExtension</Label>
<Label Text="OnAppTheme XAML tag">
<Label.TextColor>
<OnAppTheme x:TypeArguments="Color" Light="Green" Dark="Red" />
</Label.TextColor>
</Label>
<Label Style="{DynamicResource Key=OSThemeStyle}">This text is green or red depending on Light (or default) or Dark (through style)</Label>
<Label TextColor="{DynamicResource MyColor}">This text is HotPink or Yellow depending on Light (or default) or Dark (through DynamicResource)</Label>
<Label TextColor="{StaticResource MyColor}">This text is HotPink or Yellow depending on Light (or default) or Dark (through StaticResource)</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>The image below is a Xamarin logo or fruits depending on Light (or default) or Dark</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" />
<!--<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>

</StackLayout>
</ScrollView>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Xamarin.Forms.Xaml;
using System;
using System.Globalization;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Xaml;

namespace Xamarin.Forms.Controls.GalleryPages.AppThemeGalleries
{
Expand All @@ -11,47 +14,62 @@ public AppThemeXamlGallery()
}
}

public class CustomControl : ContentView
{
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(CustomControl), (Color)new AppThemeColor() { Light = Color.Red, Dark = Color.Green });
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(CustomControl));
public static readonly BindableProperty BoxColorProperty = BindableProperty.Create(nameof(BoxColor), typeof(Color), typeof(CustomControl), Color.Yellow);

private StackLayout layout = new StackLayout();
public CustomControl()
{
var label = new Label();
label.SetBinding(Label.TextProperty, new Binding(nameof(Text), source: this));
label.SetBinding(Label.TextColorProperty, new Binding(nameof(TextColor), source: this, mode: BindingMode.TwoWay));
label.SetDynamicResource(Label.TextColorProperty, "MyColor");
this.layout.Children.Add(label);

var box = new BoxView();
box.SetBinding(BoxView.ColorProperty, new Binding(nameof(BoxColor), source: this, mode: BindingMode.TwoWay));
box.WidthRequest = 24;
box.HeightRequest = 24;
this.layout.Children.Add(box);

this.layout.Orientation = StackOrientation.Horizontal;
this.Content = this.layout;
}
[Preserve(AllMembers = true)]
class FooConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = value as string;
return val == "1" ? Color.Green : Color.Red;
}

public Color TextColor
{
get { return (Color)this.GetValue(TextColorProperty); }
set { this.SetValue(TextColorProperty, value); }
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Only one way bindings are supported with this converter");
}
}

public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); }
}
//public class CustomControl : ContentView
// {
// public static readonly BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(CustomControl), (Color)new AppThemeColor() { Light = Color.Red, Dark = Color.Green });
// public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(CustomControl));
// public static readonly BindableProperty BoxColorProperty = BindableProperty.Create(nameof(BoxColor), typeof(Color), typeof(CustomControl), Color.Yellow);

public Color BoxColor
{
get { return (Color)this.GetValue(BoxColorProperty); }
set { this.SetValue(BoxColorProperty, value); }
}
}
// private StackLayout layout = new StackLayout();
// public CustomControl()
// {
// var label = new Label();
// label.SetBinding(Label.TextProperty, new Binding(nameof(Text), source: this));
// label.SetBinding(Label.TextColorProperty, new Binding(nameof(TextColor), source: this, mode: BindingMode.TwoWay));
// label.SetDynamicResource(Label.TextColorProperty, "MyColor");
// this.layout.Children.Add(label);

// var box = new BoxView();
// box.SetBinding(BoxView.ColorProperty, new Binding(nameof(BoxColor), source: this, mode: BindingMode.TwoWay));
// box.WidthRequest = 24;
// box.HeightRequest = 24;
// this.layout.Children.Add(box);

// this.layout.Orientation = StackOrientation.Horizontal;
// this.Content = this.layout;
// }

// public Color TextColor
// {
// get { return (Color)this.GetValue(TextColorProperty); }
// set { this.SetValue(TextColorProperty, value); }
// }

// public string Text
// {
// get { return (string)this.GetValue(TextProperty); }
// set { this.SetValue(TextProperty, value); }
// }

// public Color BoxColor
// {
// get { return (Color)this.GetValue(BoxColorProperty); }
// set { this.SetValue(BoxColorProperty, value); }
// }
// }
}
71 changes: 71 additions & 0 deletions Xamarin.Forms.Core.UnitTests/AppThemeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using NUnit.Framework;

namespace Xamarin.Forms.Core.UnitTests
{
public class AppThemeTests : BaseTestFixture
{
[SetUp]
public override void Setup()
{
base.Setup();
Application.Current = new MockApplication();

Device.SetFlags(new[] { ExperimentalFlags.AppThemeExperimental });
}

[Test]
public void ThemeChangeUsingSetAppThemeColor()
{
var label = new Label
{
Text = "Green on Light, Red on Dark"
};

label.SetAppThemeColor(Label.TextColorProperty, Color.Green, Color.Red);
Assert.AreEqual(Color.Green, label.TextColor);

SetAppTheme(OSAppTheme.Dark);

Assert.AreEqual(Color.Red, label.TextColor);
}

[Test]
public void ThemeChangeUsingSetAppTheme()
{
var label = new Label
{
Text = "Green on Light, Red on Dark"
};

label.SetOnAppTheme(Label.TextColorProperty, Color.Green, Color.Red);
Assert.AreEqual(Color.Green, label.TextColor);

SetAppTheme(OSAppTheme.Dark);

Assert.AreEqual(Color.Red, label.TextColor);
}

[Test]
public void ThemeChangeUsingSetBinding()
{
var label = new Label
{
Text = "Green on Light, Red on Dark"
};

label.SetBinding(Label.TextColorProperty, new OnAppTheme<Color> { Light = Color.Green, Dark = Color.Red });
Assert.AreEqual(Color.Green, label.TextColor);

SetAppTheme(OSAppTheme.Dark);

Assert.AreEqual(Color.Red, label.TextColor);
}

void SetAppTheme(OSAppTheme theme)
{
((MockPlatformServices)Device.PlatformServices).RequestedTheme = theme;
Application.Current.OnRequestedThemeChanged(new AppThemeChangedEventArgs(theme));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@
<Compile Include="FlexLayoutAlignSelfTests.cs" />
<Compile Include="FlexOrderTests.cs" />
<Compile Include="FontAttributeConverterUnitTests.cs" />
<Compile Include="AppThemeTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">
Expand All @@ -249,7 +250,7 @@
<Name>Xamarin.Forms.Maps</Name>
</ProjectReference>
<ProjectReference Include="..\Xamarin.Forms.Platform\Xamarin.Forms.Platform.csproj">
<Project>{67f9d3a8-f71e-4428-913f-c37ae82cdb24}</Project>
<Project>{D31A6537-ED9C-4EBD-B231-A8D4FE44126A}</Project>
<Name>Xamarin.Forms.Platform</Name>
</ProjectReference>
<ProjectReference Include="..\Xamarin.Forms.Xaml\Xamarin.Forms.Xaml.csproj">
Expand All @@ -274,7 +275,7 @@
<ItemGroup />
<Target Name="_CopyNUnitTestAdapterFiles" AfterTargets="Build">
<ItemGroup>
<_NUnitTestAdapterFiles Include="$(NuGetPackageRoot)NUnit3TestAdapter\%(Version)\build\net35\**" Condition="@(PackageReference -> '%(Identity)') == 'NUnit3TestAdapter'" InProject="False" />
<_NUnitTestAdapterFiles Include="$(NuGetPackageRoot)NUnit3TestAdapter\%(Version)\build\net35\**" Condition="@(PackageReference -&gt; '%(Identity)') == 'NUnit3TestAdapter'" InProject="False" />
</ItemGroup>
<Copy SourceFiles="@(_NUnitTestAdapterFiles)" DestinationFolder="$(SolutionDir)packages\NUnitTestAdapter.AnyVersion\tools\%(RecursiveDir)" ContinueOnError="true" Retries="0" />
<Copy SourceFiles="@(_NUnitTestAdapterFiles)" DestinationFolder="$(SolutionDir)packages\NUnitTestAdapter.AnyVersion\build\%(RecursiveDir)" ContinueOnError="true" Retries="0" />
Expand Down
36 changes: 26 additions & 10 deletions Xamarin.Forms.Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
using System.Threading.Tasks;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform;
using System.Diagnostics;
using Xamarin.Forms.Core;

namespace Xamarin.Forms
{
Expand Down Expand Up @@ -59,7 +57,7 @@ public IAppLinks AppLinks
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetCurrentApplication(Application value) => Current = value;
public static void SetCurrentApplication(Application value) => Current = value;

public static Application Current { get; set; }

Expand Down Expand Up @@ -161,18 +159,36 @@ public ResourceDictionary Resources

public event EventHandler<AppThemeChangedEventArgs> RequestedThemeChanged
{
add
{
ExperimentalFlags.VerifyFlagEnabled(nameof(Application), ExperimentalFlags.AppThemeExperimental, nameof(RequestedThemeChanged));

_weakEventManager.AddEventHandler(value);
}
add => _weakEventManager.AddEventHandler(value);
remove => _weakEventManager.RemoveEventHandler(value);
}

bool _themeChangedFiring;
OSAppTheme _lastAppTheme;

[EditorBrowsable(EditorBrowsableState.Never)]
public void OnRequestedThemeChanged(AppThemeChangedEventArgs args)
=> _weakEventManager.HandleEvent(this, args, nameof(RequestedThemeChanged));
{
if (Device.Flags.IndexOf(ExperimentalFlags.AppThemeExperimental) == -1)
return;

// On iOS the event is triggered more than once.
// To minimize that for us, we only do it when the theme actually changes and it's not currently firing
if (!_themeChangedFiring && RequestedTheme != _lastAppTheme)
{
try
{
_themeChangedFiring = true;
_lastAppTheme = RequestedTheme;

_weakEventManager.HandleEvent(this, args, nameof(RequestedThemeChanged));
}
finally
{
_themeChangedFiring = false;
}
}
}

public event EventHandler<ModalPoppedEventArgs> ModalPopped;

Expand Down
Loading

0 comments on commit 6c40121

Please sign in to comment.