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

Runtime updating of AppTheme values #10442

Merged
merged 10 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this have changed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh. No. But it's added again on the next line but it just escapes the > to a gt;. I'm guessing that's an inconsistency between VS versions/VSWin and VSMac? I'll revert to be sure

<_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