From 4d391878e29a0a7990506de596aca4459a0a4bde Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:07:18 -0700 Subject: [PATCH 01/59] Update to .NET 9 --- Directory.Build.props | 15 ++++++++++++--- azure-pipelines.yml | 4 ++-- global.json | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9833a5c6..3f48b116 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,13 +1,15 @@ - net8.0 - 8.0.91 + net9.0 + 9.0.0-rc.2.24503.2 9.1.0 latest enable enable NETSDK1023 + true + all true true false @@ -39,13 +41,20 @@ CS1711: XML comment has a typeparam tag, but there is no type parameter by that name CS1712: Type parameter has no matching typeparam tag in the XML comment CS1723: XML comment has cref attribute that refers to a type parameter - CS1734: XML comment has a paramref tag, but there is no parameter by that name + CS1734: XML comment has a paramref tag, but there is no parameter by that name + NU1900 Error communicating with package source, while getting vulnerability information. + NU1901 Package with low severity detected + NU1902 Package with moderate severity detected + NU1903 Package with high severity detected + NU1904 Package with critical severity detected + NU1905 An audit source does not provide a vulnerability database NUnit*: NUnit Analyzers IL2***: Trim Warnings IL3***: AOT Warnings--> nullable, CS0419,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1587,CS1589,CS1590,CS1591,CS1592,CS1598,CS1658,CS1710,CS1711,CS1712,CS1723,CS1734, + NU1900,NU1901,NU1902,NU1903,NU1904,NU1905, NUnit1001,NUnit1002,NUnit1003,NUnit1004,NUnit1005,NUnit1006,NUnit1007,NUnit1008,NUnit1009,NUnit1010,NUnit1011,NUnit1012,NUnit1013,NUnit1014,NUnit1015,NUnit1016,NUnit1017,NUnit1018,NUnit1019,NUnit1020,NUnit1021,NUnit1022,NUnit1023,NUnit1024,NUnit1025,NUnit1026,NUnit1027,NUnit1028,NUnit1029,NUnit1030,NUnit1031,NUnit1032,NUnit1033, NUnit2001,NUnit2002,NUnit2003,NUnit2004,NUnit2005,NUnit2006,NUnit2007,NUnit2008,NUnit2009,NUnit2010,NUnit2011,NUnit2012,NUnit2013,NUnit2014,NUnit2015,NUnit2016,NUnit2017,NUnit2018,NUnit2019,NUnit2020,NUnit2021,NUnit2022,NUnit2023,NUnit2024,NUnit2025,NUnit2026,NUnit2027,NUnit2028,NUnit2029,NUnit2030,NUnit2031,NUnit2032,NUnit2033,NUnit2034,NUnit2035,NUnit2036,NUnit2037,NUnit2038,NUnit2039,NUnit2040,NUnit2041,NUnit2042,NUnit2043,NUnit2044,NUnit2045,NUnit2046,NUnit2047,NUnit2048,NUnit2049,NUnit2050, NUnit3001,NUnit3002,NUnit3003,NUnit3004, diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1b0b92a7..8f7c66a5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ variables: PreviewNumber: $[counter(variables['CurrentSemanticVersionBase'], 1001)] CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)' NugetPackageVersion: '$(CurrentSemanticVersion)' - NET_VERSION: '8.0.x' + NET_VERSION: '9.0.x' RunPoliCheck: false PathToLibrarySolution: 'src/CommunityToolkit.Maui.Markup.sln' PathToSamplesSolution: 'samples/CommunityToolkit.Maui.Markup.Sample.sln' @@ -66,7 +66,7 @@ jobs: inputs: packageType: 'sdk' version: '$(NET_VERSION)' - includePreviewVersions: false + includePreviewVersions: true - task: CmdLine@2 displayName: 'Install .NET MAUI Workload' diff --git a/global.json b/global.json index 59b63ebe..8271095b 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "8.0.203", + "version": "9.0.0-rc.2.24473.5", "rollForward": "latestFeature", - "allowPrerelease": false + "allowPrerelease": true } } \ No newline at end of file From 1eaca0c820edd08ba902aa1622df0040bcaf462a Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:24:55 -0700 Subject: [PATCH 02/59] Update for .NET 9 --- Directory.Build.props | 2 +- .../App.cs | 7 +- .../AppShell.cs | 2 +- ...CommunityToolkit.Maui.Markup.Sample.csproj | 12 +- .../Pages/SettingsPage.cs | 16 +- .../Benchmarks/ExecuteBindingsBase.cs | 9 - .../ExecuteBindings_ViewModelToView.cs | 7 - .../ExecuteBindings_ViewToViewModel.cs | 7 - .../Benchmarks/InitializeBindings.cs | 8 - ...unityToolkit.Maui.Markup.Benchmarks.csproj | 2 +- .../Base/BaseMarkupTestFixture.cs | 10 +- .../BindableObjectExtensionsTests.cs | 810 ------------------ .../BindableObjectMultiBindExtensionsTests.cs | 15 +- .../GesturesExtensionsTests.cs | 253 +----- .../GridRowsColumns.cs | 38 +- .../TextAlignmentExtensionsTests.cs | 2 +- .../VisualElementExtensionsTests.cs | 1 - .../AbsoluteLayoutExtensions.cs | 2 +- .../BindableObjectExtensions.cs | 166 +--- .../DefaultBindableProperties.cs | 4 +- .../DynamicResourceHandlerExtensions.cs | 2 +- .../ElementExtensions.cs | 4 +- .../GesturesExtensions.TypedBindings.cs | 419 --------- .../GesturesExtensions.cs | 373 +++++--- .../GridRowsColumns.cs | 24 +- .../LabelExtensions.cs | 2 +- src/CommunityToolkit.Maui.Markup/Style.cs | 10 +- .../TypedBinding.cs | 196 ++++- .../VisualElementExtensions.cs | 2 +- 29 files changed, 535 insertions(+), 1870 deletions(-) delete mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs delete mode 100644 src/CommunityToolkit.Maui.Markup/GesturesExtensions.TypedBindings.cs diff --git a/Directory.Build.props b/Directory.Build.props index 3f48b116..d8322ec3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ net9.0 9.0.0-rc.2.24503.2 9.1.0 - latest + preview enable enable NETSDK1023 diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/App.cs b/samples/CommunityToolkit.Maui.Markup.Sample/App.cs index 38e27194..eecc138f 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/App.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/App.cs @@ -2,10 +2,13 @@ class App : Application { + readonly AppShell appShell; + public App(AppShell shell) { Resources = new AppStyles(); - - MainPage = shell; + appShell = shell; } + + protected override Window CreateWindow(IActivationState? activationState) => new(appShell); } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs b/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs index 5eb5a0a1..bb46b1d2 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs @@ -32,7 +32,7 @@ public static string GetRoute(Type type) return route; } - static KeyValuePair CreateRoutePageMapping<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TPage, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TViewModel>() where TPage : BaseContentPage + static KeyValuePair CreateRoutePageMapping<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPage, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TViewModel>() where TPage : BaseContentPage where TViewModel : BaseViewModel { var route = CreateRoute(); diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index 6dd6d025..d0a5cd1e 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -22,11 +22,13 @@ 1 6.5 - 14.2 - 14.0 + 15.0 + 15.0 21.0 10.0.17763.0 10.0.17763.0 + + 10.0.19041.41 - - + + - + diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs index 543ca71d..7ed991f8 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs @@ -18,14 +18,17 @@ public SettingsPage(SettingsViewModel settingsViewModel) : base(settingsViewMode new Label() .Text("Top Stories To Fetch") - .AppThemeBinding(Label.TextColorProperty,AppStyles.BlackColor, AppStyles.PrimaryTextColorDark) + .AppThemeBinding(Label.TextColorProperty, AppStyles.BlackColor, AppStyles.PrimaryTextColorDark) .LayoutFlags(AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional) .LayoutBounds(0, 0, 1, 40) .TextCenterHorizontal() .TextBottom() .Assign(out Label topNewsStoriesToFetchLabel), - new Entry { Keyboard = Keyboard.Numeric } + new Entry + { + Keyboard = Keyboard.Numeric + } .BackgroundColor(Colors.White) .Placeholder($"Provide a value between {SettingsService.MinimumStoriesToFetch} and {SettingsService.MaximumStoriesToFetch}", Colors.Grey) .LayoutFlags(AbsoluteLayoutFlags.XProportional, AbsoluteLayoutFlags.WidthProportional) @@ -43,16 +46,11 @@ public SettingsPage(SettingsViewModel settingsViewModel) : base(settingsViewMode .Bind(Entry.TextProperty, static (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, static (vm, text) => vm.NumberOfTopStoriesToFetch = text), new Label() - .Bind( - Label.TextProperty, - binding1: new Binding { Source = SettingsService.MinimumStoriesToFetch }, - binding2: new Binding { Source = SettingsService.MaximumStoriesToFetch }, - convert: ((int minimum, int maximum) values) => string.Format(CultureInfo.CurrentUICulture, $"The number must be between {values.minimum} and {values.maximum}."), - mode: BindingMode.OneTime) + .Text(string.Format(CultureInfo.CurrentUICulture, $"The number must be between {SettingsService.MinimumStoriesToFetch} and {SettingsService.MaximumStoriesToFetch}.")) .LayoutFlags(AbsoluteLayoutFlags.XProportional | AbsoluteLayoutFlags.WidthProportional) .LayoutBounds(0, 90, 1, 40) .TextCenter() - .AppThemeColorBinding(Label.TextColorProperty,AppStyles.BlackColor, AppStyles.PrimaryTextColorDark) + .AppThemeColorBinding(Label.TextColorProperty, AppStyles.BlackColor, AppStyles.PrimaryTextColorDark) .Font(size: 12, italic: true) .SemanticHint($"The minimum and maximum possible values for the {topNewsStoriesToFetchLabel.Text} field above.") } diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs index 915a5483..ac2bd519 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs @@ -16,13 +16,6 @@ protected ExecuteBindingsBase() DefaultBindingsLabel.SetBinding(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); DefaultBindingsLabel.EnableAnimations(); - DefaultMarkupBindingsLabel = new Label - { - BindingContext = DefaultMarkupBindingsLabelViewModel - }.Bind(Label.TextProperty, nameof(LabelViewModel.Text), mode: BindingMode.TwoWay) - .Bind(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); - DefaultMarkupBindingsLabel.EnableAnimations(); - TypedMarkupBindingsLabel = new Label { BindingContext = TypedMarkupBindingsLabelViewModel @@ -38,10 +31,8 @@ protected ExecuteBindingsBase() } protected LabelViewModel DefaultBindingsLabelViewModel { get; } = new(); - protected LabelViewModel DefaultMarkupBindingsLabelViewModel { get; } = new(); protected LabelViewModel TypedMarkupBindingsLabelViewModel { get; } = new(); protected Label DefaultBindingsLabel { get; } - protected Label DefaultMarkupBindingsLabel { get; } protected Label TypedMarkupBindingsLabel { get; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs index f147654d..4e9b1741 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs @@ -11,13 +11,6 @@ public void ExecuteDefaultBindings_ViewModelToView() DefaultBindingsLabelViewModel.Text = helloWorldText; } - [Benchmark] - public void ExecuteDefaultBindingsMarkup_ViewModelToView() - { - DefaultMarkupBindingsLabelViewModel.TextColor = Colors.Green; - DefaultMarkupBindingsLabelViewModel.Text = helloWorldText; - } - [Benchmark] public void ExecuteTypedBindingsMarkup_ViewModelToView() { diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs index ebeec5c4..2936ad73 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs @@ -11,13 +11,6 @@ public void ExecuteDefaultBindings_ViewToViewModel() DefaultBindingsLabel.Text = helloWorldText; } - [Benchmark] - public void ExecuteDefaultBindingsMarkup_ViewToViewModel() - { - DefaultMarkupBindingsLabel.TextColor = Colors.Green; - DefaultMarkupBindingsLabel.Text = helloWorldText; - } - [Benchmark] public void ExecuteTypedBindingsMarkup_ViewToViewModel() { diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs index f7320472..5ead50e4 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs @@ -27,14 +27,6 @@ public void InitializeDefaultBindings() defaultBindingsLabel.SetBinding(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); } - [Benchmark] - public void InitializeDefaultBindingsMarkup() - { - defaultMarkupBindingsLabel - .Bind(Label.TextProperty, nameof(LabelViewModel.Text), mode: BindingMode.TwoWay) - .Bind(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); - } - [Benchmark] public void InitializeTypedBindingsMarkup() { diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/CommunityToolkit.Maui.Markup.Benchmarks.csproj b/src/CommunityToolkit.Maui.Markup.Benchmarks/CommunityToolkit.Maui.Markup.Benchmarks.csproj index 39dcbaf0..8e93a860 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/CommunityToolkit.Maui.Markup.Benchmarks.csproj +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/CommunityToolkit.Maui.Markup.Benchmarks.csproj @@ -18,7 +18,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs index 2a0f2427..bd8adf4b 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs @@ -23,17 +23,17 @@ public override void TearDown() protected void TestPropertiesSet( Action modify, - params (BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)[] propertyChanges) + params List<(BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)> propertyChanges) => TestPropertiesSet(Bindable, modify, propertyChanges); protected void TestPropertiesSet( Action modify, - params (BindableProperty property, object? beforeValue, object? expectedValue)[] propertyChanges) + params List<(BindableProperty property, object? beforeValue, object? expectedValue)> propertyChanges) => TestPropertiesSet(Bindable, modify, propertyChanges); protected void TestPropertiesSet( Action modify, - params (BindableProperty property, object? expectedValue)[] propertyChanges) + params List<(BindableProperty property, object? expectedValue)> propertyChanges) => TestPropertiesSet(Bindable, modify, propertyChanges); } @@ -42,7 +42,7 @@ abstract class BaseMarkupTestFixture : BaseTestFixture protected static void TestPropertiesSet( TBindable bindable, Action modify, - params (BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)[] propertyChanges) where TBindable : BindableObject + params List<(BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)> propertyChanges) where TBindable : BindableObject { foreach (var (property, beforeValue, expectedValue) in propertyChanges) { @@ -61,7 +61,7 @@ protected static void TestPropertiesSet( protected static void TestPropertiesSet( TBindable bindable, Action modify, - params (BindableProperty property, TPropertyValue expectedValue)[] propertyChanges) where TBindable : BindableObject + params List<(BindableProperty property, TPropertyValue expectedValue)> propertyChanges) where TBindable : BindableObject { foreach (var (property, expectedValue) in propertyChanges) { diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs deleted file mode 100644 index 18027b3c..00000000 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs +++ /dev/null @@ -1,810 +0,0 @@ -using System.Windows.Input; -using BindableObjectViews; -using CommunityToolkit.Maui.Markup.UnitTests.Base; -using NUnit.Framework; -namespace CommunityToolkit.Maui.Markup.UnitTests -{ - [TestFixture] - class BindableObjectExtensionsTests : BaseMarkupTestFixture - { - ViewModel? viewModel; - - [SetUp] - public override void Setup() - { - base.Setup(); - viewModel = new ViewModel(); - } - - [TearDown] - public override void TearDown() - { - viewModel = null; - base.TearDown(); - } - - [Test] - public void BindSpecifiedPropertyWithDefaults() - { - var label = new Label(); - label.Bind(Label.TextColorProperty, nameof(viewModel.TextColor)); - BindingHelpers.AssertBindingExists(label, Label.TextColorProperty, nameof(viewModel.TextColor)); - } - - // Note that we test positional parameters to catch API parameter order changes (which would be breaking). - // Testing named parameters is not useful because a parameter rename operation in the API would also rename it in the test - [Test] - public void BindSpecifiedPropertyWithPositionalParameters() - { - var button = new Button(); - object converterParameter = 1; - var stringFormat = nameof(BindSpecifiedPropertyWithPositionalParameters) + " {0}"; - IValueConverter converter = new ToStringConverter(); - object source = new ViewModel(); - object targetNullValue = nameof(BindSpecifiedPropertyWithPositionalParameters) + " null"; - object fallbackValue = nameof(BindSpecifiedPropertyWithPositionalParameters) + " fallback"; - - button.Bind( - Button.TextProperty, - nameof(viewModel.Text), - BindingMode.OneWay, - converter, - converterParameter, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - button, - targetProperty: Button.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.OneWay, - converter: converter, - converterParameter: converterParameter, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineOneWayConvertAndDefaults() - { - var label = new Label(); - label.Bind( - Label.TextColorProperty, - nameof(viewModel.IsRed), - convert: (bool? isRed) => isRed.HasValue && isRed.Value ? Colors.Red : Colors.Transparent - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextColorProperty, - nameof(viewModel.IsRed), - assertConverterInstanceIsAnyNotNull: true, - assertConvert: c => c.AssertConvert(true, Colors.Red).AssertConvert(false, Colors.Transparent) - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineOneWayConvertUsingValueTypeAndDefaults() - { - const double additionalHeight = 5; - - var label = new Label(); - label.Bind( - Label.HeightRequestProperty, - nameof(viewModel.HeightRequest), - convert: (double heightRequest) => heightRequest + additionalHeight - ); - - BindingHelpers.AssertBindingExists( - label, - Label.HeightRequestProperty, - nameof(viewModel.HeightRequest), - targetNullValue: 0.0, - assertConverterInstanceIsAnyNotNull: true, - assertConvert: c => c.AssertConvert(0, additionalHeight) - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndDefaults() - { - var label = new Label(); - label.Bind(Label.TextColorProperty, - nameof(viewModel.IsRed), - convert: (bool? isRed, float? alpha) => - { - ArgumentNullException.ThrowIfNull(alpha); - return (isRed.HasValue && isRed.Value ? Colors.Red : Colors.Green).MultiplyAlpha(alpha.Value); - }, - converterParameter: 0.5f - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextColorProperty, - nameof(viewModel.IsRed), - assertConverterInstanceIsAnyNotNull: true, - converterParameter: 0.5f, - assertConvert: c => c.AssertConvert(true, 0.5f, Colors.Red.MultiplyAlpha(0.5f)) - .AssertConvert(false, 0.2f, Colors.Green.MultiplyAlpha(0.2f)) - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineTwoWayConvertAndDefaults() - { - var label = new Label(); - label.Bind( - Label.TextColorProperty, - nameof(viewModel.IsRed), - BindingMode.TwoWay, - (bool? isRed) => isRed.HasValue && isRed.Value ? Colors.Red : Colors.Transparent, - color => color == Colors.Red - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextColorProperty, - nameof(viewModel.IsRed), - BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - assertConvert: c => c.AssertConvert(true, Colors.Red, twoWay: true) - .AssertConvert(false, Colors.Transparent, twoWay: true) - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndDefaults() - { - var label = new Label(); - label.Bind( - Label.TextColorProperty, - nameof(viewModel.IsRed), - BindingMode.TwoWay, - convert(), - convertBack(), - 0.5f - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextColorProperty, - nameof(viewModel.IsRed), - BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - converterParameter: 0.5f, - assertConvert: c => c.AssertConvert(true, 0.5f, Colors.Red.MultiplyAlpha(0.5f), twoWay: true) - .AssertConvert(false, 0.2f, Colors.Green.MultiplyAlpha(0.2f), twoWay: true) - ); - - static Func convert() - { - return (isRed, alpha) => (isRed ? Colors.Red : Colors.Green).MultiplyAlpha(alpha); - } - - static Func convertBack() - { - return (color, alpha) => color?.Alpha == alpha && color.Red == Colors.Red.Red && color.Green == Colors.Red.Green && color.Blue == Colors.Red.Blue; - } - } - - [Test] - public void BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters() - { - var button = new Button(); - var stringFormat = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " fallback"; - - button.Bind( - Button.TextProperty, - nameof(viewModel.Text), - BindingMode.OneWay, - (string? text) => $"'{text?.Trim('\'')}'", - null, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - button, - targetProperty: Button.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.OneWay, - assertConverterInstanceIsAnyNotNull: true, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", "'test'") - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters() - { - var button = new Button(); - var converterParameter = 1; - var stringFormat = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " fallback"; - - button.Bind( - Button.TextProperty, - nameof(viewModel.Text), - BindingMode.OneWay, - (string? text, int? repeat) => - { - ArgumentNullException.ThrowIfNull(repeat); - return string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat.Value)); - }, - null, - converterParameter, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - button, - targetProperty: Button.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.OneWay, - assertConverterInstanceIsAnyNotNull: true, - converterParameter: converterParameter, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", 2, "'test''test'") - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters() - { - var button = new Button(); - var stringFormat = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " fallback"; - - button.Bind( - Button.TextProperty, - nameof(viewModel.Text), - BindingMode.TwoWay, - text => $"'{text?.Trim('\'')}'", - text => - { - ArgumentNullException.ThrowIfNull(text); - return text.Trim('\''); - }, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - button, - targetProperty: Button.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", "'test'", twoWay: true) - ); - } - - [Test] - public void BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters() - { - var button = new Button(); - var converterParameter = 1; - var stringFormat = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " fallback"; - - button.Bind( - Button.TextProperty, - nameof(viewModel.Text), - BindingMode.TwoWay, - (string? text, int? repeat) => - { - ArgumentNullException.ThrowIfNull(repeat); - - return string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat.Value)); - }, - (text, repeat) => - { - ArgumentNullException.ThrowIfNull(text); - ArgumentNullException.ThrowIfNull(repeat); - - return text[..(text.Length / repeat.Value)].Trim('\''); - }, - converterParameter, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - button, - targetProperty: Button.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - converterParameter: converterParameter, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", 2, "'test''test'", twoWay: true) - ); - } - - [Test] - public void BindDefaultPropertyWithDefaults() - { - var label = new Label(); - label.Bind(nameof(viewModel.Text)); - BindingHelpers.AssertBindingExists(label, Label.TextProperty, nameof(viewModel.Text)); - } - - [Test] - public void BindDefaultPropertyWithPositionalParameters() - { - var label = new Label(); - object converterParameter = 1; - var stringFormat = nameof(BindDefaultPropertyWithPositionalParameters) + " {0}"; - IValueConverter converter = new ToStringConverter(); - object source = new ViewModel(); - object targetNullValue = nameof(BindDefaultPropertyWithPositionalParameters) + " null"; - object fallbackValue = nameof(BindDefaultPropertyWithPositionalParameters) + " fallback"; - - label.Bind( - nameof(viewModel.Text), - BindingMode.OneWay, - converter, - converterParameter, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - label, - targetProperty: Label.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.OneWay, - converter: converter, - converterParameter: converterParameter, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue - ); - } - - [Test] - public void BindDefaultPropertyWithInlineOneWayConvertAndDefaults() - { - var label = new Label(); - label.Bind( - nameof(viewModel.Text), - convert: (string? text) => $"'{text}'" - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextProperty, - nameof(viewModel.Text), - assertConverterInstanceIsAnyNotNull: true, - assertConvert: c => c.AssertConvert("test", "'test'") - ); - } - - [Test] - public void BindDefaultPropertyWithInlineOneWayParameterizedConvertAndDefaults() - { - var label = new Label(); - label.Bind( - nameof(viewModel.Text), - convert: (string? text, int? repeat) => - { - ArgumentNullException.ThrowIfNull(text); - ArgumentNullException.ThrowIfNull(repeat); - - return string.Concat(Enumerable.Repeat($"'{text.Trim('\'')}'", repeat.Value)); - }, - converterParameter: 1 - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextProperty, - nameof(viewModel.Text), - assertConverterInstanceIsAnyNotNull: true, - converterParameter: 1, - assertConvert: c => c.AssertConvert("test", 2, "'test''test'") - ); - } - - [Test] - public void BindDefaultPropertyWithInlineTwoWayConvertAndDefaults() - { - var label = new Label(); - label.Bind( - nameof(viewModel.Text), - BindingMode.TwoWay, - (string? text) => $"'{text?.Trim('\'')}'", - text => - { - ArgumentNullException.ThrowIfNull(text); - return text.Trim('\''); - } - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextProperty, - nameof(viewModel.Text), - BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - assertConvert: c => c.AssertConvert("test", "'test'", twoWay: true) - ); - } - - [Test] - public void BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndDefaults() - { - var label = new Label(); - label.Bind( - nameof(viewModel.Text), - BindingMode.TwoWay, - (string? text, int? repeat) => - { - ArgumentNullException.ThrowIfNull(text); - ArgumentNullException.ThrowIfNull(repeat); - - return string.Concat(Enumerable.Repeat($"'{text.Trim('\'')}'", repeat.Value)); - }, - (text, repeat) => - { - ArgumentNullException.ThrowIfNull(text); - ArgumentNullException.ThrowIfNull(repeat); - - return text[..(text.Length / repeat.Value)].Trim('\''); - }, - 2 - ); - - BindingHelpers.AssertBindingExists( - label, - Label.TextProperty, - nameof(viewModel.Text), - BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - converterParameter: 2, - assertConvert: c => c.AssertConvert("test", 2, "'test''test'", twoWay: true) - ); - } - - [Test] - public void BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters() - { - var label = new Label(); - var stringFormat = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " fallback"; - - label.Bind( - nameof(viewModel.Text), - BindingMode.OneWay, - (string? text) => $"'{text?.Trim('\'')}'", - null, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - label, - targetProperty: Label.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.OneWay, - assertConverterInstanceIsAnyNotNull: true, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", "'test'") - ); - } - - [Test] - public void BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters() - { - var label = new Label(); - var converterParameter = 1; - var stringFormat = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " fallback"; - - label.Bind( - nameof(viewModel.Text), - BindingMode.OneWay, - (string? text, int? repeat) => - { - ArgumentNullException.ThrowIfNull(repeat); - - return string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat.Value)); - }, - null, - converterParameter, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - label, - targetProperty: Label.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.OneWay, - assertConverterInstanceIsAnyNotNull: true, - converterParameter: converterParameter, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", 2, "'test''test'") - ); - } - - [Test] - public void BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters() - { - var label = new Label(); - var stringFormat = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " fallback"; - - label.Bind( - nameof(viewModel.Text), - BindingMode.TwoWay, - text => $"'{text?.Trim('\'')}'", - text => - { - ArgumentNullException.ThrowIfNull(text); - return text.Trim('\''); - }, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - label, - targetProperty: Label.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", "'test'", twoWay: true) - ); - } - - [Test] - public void BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters() - { - var label = new Label(); - var converterParameter = 1; - var stringFormat = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " {0}"; - object source = new ViewModel(); - var targetNullValue = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " null"; - var fallbackValue = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " fallback"; - - label.Bind( - nameof(viewModel.Text), - BindingMode.TwoWay, - (string? text, int? repeat) => - { - ArgumentNullException.ThrowIfNull(repeat); - - return string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat.Value)); - }, - (text, repeat) => - { - ArgumentNullException.ThrowIfNull(text); - ArgumentNullException.ThrowIfNull(repeat); - - return text[..(text.Length / repeat.Value)].Trim('\''); - }, - converterParameter, - stringFormat, - source, - targetNullValue, - fallbackValue - ); - - BindingHelpers.AssertBindingExists( - label, - targetProperty: Label.TextProperty, - path: nameof(viewModel.Text), - mode: BindingMode.TwoWay, - assertConverterInstanceIsAnyNotNull: true, - converterParameter: converterParameter, - stringFormat: stringFormat, - source: source, - targetNullValue: targetNullValue, - fallbackValue: fallbackValue, - assertConvert: c => c.AssertConvert("test", 2, "'test''test'", twoWay: true) - ); - } - - [Test] - public void BindCommandWithDefaults() - { - var textCell = new TextCell(); - var path = nameof(viewModel.Command); - - textCell.BindCommand(path); - - BindingHelpers.AssertBindingExists(textCell, TextCell.CommandProperty, path); - BindingHelpers.AssertBindingExists(textCell, TextCell.CommandParameterProperty); - } - - [Test] - public void BindCommandWithoutParameter() - { - var textCell = new TextCell(); - var path = nameof(viewModel.Command); - - textCell.BindCommand(path, parameterPath: null); - - BindingHelpers.AssertBindingExists(textCell, TextCell.CommandProperty, path); - Assert.That(BindingHelpers.GetBinding(textCell, TextCell.CommandParameterProperty), Is.Null); - } - - [Test] - public void BindCommandWithPositionalParameters() - { - var textCell = new TextCell(); - object source = new ViewModel(); - var path = nameof(viewModel.Command); - var parameterPath = nameof(viewModel.Id); - object parameterSource = new ViewModel(); - - textCell.BindCommand(path, source, parameterPath, parameterSource); - - BindingHelpers.AssertBindingExists(textCell, TextCell.CommandProperty, path, source: source); - BindingHelpers.AssertBindingExists(textCell, TextCell.CommandParameterProperty, parameterPath, source: parameterSource); - } - - [Test] - public void SupportDerivedElements() - { - Assert.Multiple(() => - { - Assert.That(new DerivedFromLabel() - .Bind(nameof(viewModel.Text)) - .Bind( - nameof(viewModel.Text), - convert: (string? text) => $"'{text}'") - .Bind( - nameof(viewModel.Text), - convert: (string? text, int? repeat) => - { - ArgumentNullException.ThrowIfNull(text); - ArgumentNullException.ThrowIfNull(repeat); - - return string.Concat(Enumerable.Repeat($"'{text.Trim('\'')}'", repeat.Value)); - }) - .Bind( - DerivedFromLabel.TextColorProperty, - nameof(viewModel.TextColor)) - .Bind( - DerivedFromLabel.BackgroundColorProperty, - nameof(viewModel.IsRed), - convert: (bool? isRed) => isRed.HasValue && isRed.Value ? Colors.Black : Colors.Transparent) - .Bind( - Label.TextColorProperty, - nameof(viewModel.IsRed), - convert: (bool? isRed, float? alpha) => - { - ArgumentNullException.ThrowIfNull(alpha); - - return (isRed.HasValue && isRed.Value ? Colors.Red : Colors.Green).MultiplyAlpha(alpha.Value); - }) - .Invoke(l => l.Text = nameof(SupportDerivedElements)) - .Assign(out DerivedFromLabel assignDerivedFromLabel), - Is.InstanceOf()); - - Assert.That(new DerivedFromTextCell().BindCommand(nameof(viewModel.Command)), Is.InstanceOf()); - Assert.That(assignDerivedFromLabel, Is.InstanceOf()); - }); - } - - [TestCase(AppTheme.Light)] - [TestCase(AppTheme.Dark)] - [TestCase(AppTheme.Unspecified)] - public void AppThemeColorBindingCorrectlySetsPropertyToChangeBasedOnApplicationsAppTheme(AppTheme appTheme) - { - var expectedColor = appTheme == AppTheme.Dark ? Colors.Orange : Colors.Purple; - - ApplicationTestHelpers.PerformAppThemeBasedTest( - appTheme, - () => new Label().AppThemeColorBinding(Label.TextColorProperty, Colors.Purple, Colors.Orange), - label => Assert.That(label.TextColor, Is.EqualTo(expectedColor))); - } - - [TestCase(AppTheme.Light)] - [TestCase(AppTheme.Dark)] - [TestCase(AppTheme.Unspecified)] - public void AppThemeBindingCorrectlySetsPropertyToChangeBasedOnApplicationsAppTheme(AppTheme appTheme) - { - const string dark = nameof(AppTheme.Dark); - const string light = nameof(AppTheme.Light); - - var expectedText = appTheme == AppTheme.Dark ? dark : light; - - ApplicationTestHelpers.PerformAppThemeBasedTest( - appTheme, - () => new Label().AppThemeBinding(Label.TextProperty, light, dark), - label => Assert.That(label.Text, Is.EqualTo(expectedText))); - } - - sealed class ViewModel - { - public Guid Id { get; set; } - - public ICommand? Command { get; set; } - - public string? Text { get; set; } - - public Color TextColor { get; set; } = Colors.Transparent; - - public bool IsRed { get; set; } - - public double HeightRequest { get; set; } - } - } -} - -namespace BindableObjectViews // This namespace simulates derived controls defined in a separate app, for use in the tests in this file only -{ - class DerivedFromLabel : Label - { - } - - class DerivedFromTextCell : TextCell - { - } -} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs index 37640fe3..e4d9c6d1 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs @@ -1,4 +1,5 @@ -using CommunityToolkit.Maui.Markup.UnitTests.Base; +using System.Text; +using CommunityToolkit.Maui.Markup.UnitTests.Base; using NUnit.Framework; namespace CommunityToolkit.Maui.Markup.UnitTests; @@ -476,15 +477,17 @@ public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConve static string RemoveDots(string text, int count) => text.Substring(count); - static string Format(int parameter, params object?[] values) + static string Format(int parameter, params List values) { - var formatted = $"'{PrefixDots(values[0], parameter)}'"; - for (var i = 1; i < values.Length; i++) + var stringBuilder = new StringBuilder(); + + stringBuilder.Append($"'{PrefixDots(values[0], parameter)}'"); + for (var i = 1; i < values.Count; i++) { - formatted += $", '{values[i]}'"; + stringBuilder.Append($", '{values[i]}'"); } - return formatted; + return stringBuilder.ToString(); } static (string? Text, Guid Id, bool IsDone, double Fraction, int Count) Unformat(int parameter, string formatted) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs index 918ccc34..da7345a9 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs @@ -6,96 +6,6 @@ namespace CommunityToolkit.Maui.Markup.UnitTests; [TestFixture(typeof(Label))] // Derived from View class GesturesExtensionsTests : BaseMarkupTestFixture where TGestureElement : View, IGestureRecognizers, new() { - [Test] - [Obsolete("ClickGestureRecognizer is now obsolete")] - public void BindClickGestureDefaults() - { - var gestureElement = new TGestureElement(); - - gestureElement.BindClickGesture(nameof(ViewModel.SetGuidCommand)); - - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - BindingHelpers.AssertBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandProperty, nameof(ViewModel.SetGuidCommand)); - } - - [Test] - [Obsolete("ClickGestureRecognizer is now obsolete")] - public void BindClickGesturePositionalParameters() - { - const int numberOfClicks = 2; - - var gestureElement = new TGestureElement(); - object commandSource = new ViewModel(); - object parameterSource = new ViewModel(); - - gestureElement.BindClickGesture(nameof(ViewModel.SetGuidCommand), commandSource, nameof(ViewModel.Id), parameterSource, numberOfClicks); - - Assert.Multiple(() => - { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - Assert.That(((ClickGestureRecognizer)gestureElement.GestureRecognizers[0]).NumberOfClicksRequired, Is.EqualTo(numberOfClicks)); - }); - - BindingHelpers.AssertBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandProperty, nameof(ViewModel.SetGuidCommand), source: commandSource); - BindingHelpers.AssertBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandParameterProperty, nameof(ViewModel.Id), source: parameterSource); - } - - [Test] - public void BindTapGestureDefaults() - { - var gestureElement = new TGestureElement(); - - gestureElement.BindTapGesture(nameof(ViewModel.SetGuidCommand)); - - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - BindingHelpers.AssertBindingExists((TapGestureRecognizer)gestureElement.GestureRecognizers[0], TapGestureRecognizer.CommandProperty, nameof(ViewModel.SetGuidCommand)); - } - - [Test] - public void BindTapGesturePositionalParameters() - { - const int numberOfTaps = 2; - - var gestureElement = new TGestureElement(); - object commandSource = new ViewModel(); - object parameterSource = new ViewModel(); - - gestureElement.BindTapGesture(nameof(ViewModel.SetGuidCommand), commandSource, nameof(ViewModel.Id), parameterSource, numberOfTaps); - - Assert.Multiple(() => - { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - Assert.That(((TapGestureRecognizer)gestureElement.GestureRecognizers[0]).NumberOfTapsRequired, Is.EqualTo(numberOfTaps)); - }); - BindingHelpers.AssertBindingExists((TapGestureRecognizer)gestureElement.GestureRecognizers[0], TapGestureRecognizer.CommandProperty, nameof(ViewModel.SetGuidCommand), source: commandSource); - BindingHelpers.AssertBindingExists((TapGestureRecognizer)gestureElement.GestureRecognizers[0], TapGestureRecognizer.CommandParameterProperty, nameof(ViewModel.Id), source: parameterSource); - } - - [Test] - [Obsolete("ClickGestureRecognizer is now obsolete")] - public void ClickGesture() - { - const int numberOfClicks = 2; - int clicks = 0; - - var gestureElement = new TGestureElement(); - - gestureElement.ClickGesture(() => clicks++, numberOfClicks); - ((ClickGestureRecognizer)gestureElement.GestureRecognizers[0]).SendClicked(null, ButtonsMask.Primary); - - Assert.Multiple(() => - { - Assert.That(clicks, Is.GreaterThan(0)); - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - Assert.That(((ClickGestureRecognizer)gestureElement.GestureRecognizers[0]).NumberOfClicksRequired, Is.EqualTo(numberOfClicks)); - }); - } - [Test] public void TapGesture() { @@ -116,42 +26,6 @@ public void TapGesture() }); } - [Test] - public void BindSwipeGestureDefaults() - { - var gestureElement = new TGestureElement(); - - gestureElement.BindSwipeGesture(nameof(ViewModel.SetGuidCommand)); - - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - BindingHelpers.AssertBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandProperty, nameof(ViewModel.SetGuidCommand)); - } - - [Test] - public void BindSwipeGesturePositionalParameters() - { - const SwipeDirection direction = SwipeDirection.Up; - const uint threshold = 2000; - - var gestureElement = new TGestureElement(); - object commandSource = new ViewModel(); - object parameterSource = new ViewModel(); - - gestureElement.BindSwipeGesture(nameof(ViewModel.SetGuidCommand), commandSource, nameof(ViewModel.Id), parameterSource, direction, threshold); - - Assert.Multiple(() => - { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - Assert.That(((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0]).Direction, Is.EqualTo(direction)); - Assert.That(((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0]).Threshold, Is.EqualTo(threshold)); - }); - - BindingHelpers.AssertBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandProperty, nameof(ViewModel.SetGuidCommand), source: commandSource); - BindingHelpers.AssertBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandParameterProperty, nameof(ViewModel.Id), source: parameterSource); - } - [Test] public void PanGesture() { @@ -244,125 +118,6 @@ public void SupportDerivedFromLabel() // A View [TestFixture(typeof(Label))] // Derived from View class GesturesExtensionsTypedBindingsTests : BaseMarkupTestFixture where TGestureElement : View, IGestureRecognizers, new() { - [Test] - [Obsolete("ClickGestureRecognizer is now obsolete")] - public void BindClickGestureDefaults() - { - var gestureElement = new TGestureElement - { - BindingContext = new ViewModel() - }; - - gestureElement.BindClickGesture(static (ViewModel vm) => vm.SetGuidCommand); - - Assert.Multiple(() => - { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - }); - - BindingHelpers.AssertTypedBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandProperty, BindingMode.Default, gestureElement.BindingContext); - } - - [Test] - [Obsolete("ClickGestureRecognizer is now obsolete")] - public void BindClickGestureDefaultsWithNestedBindings() - { - var guid = Guid.NewGuid(); - - var gestureElement = new TGestureElement - { - BindingContext = new ViewModel() - }; - - gestureElement.BindClickGesture( - getter: static (ViewModel vm) => vm.NestedCommand.SetGuidCommand, - handlers: - [ - (vm => vm, nameof(ViewModel.NestedCommand)), - (vm => vm.NestedCommand, nameof(ViewModel.NestedCommand.SetGuidCommand)) - ], - mode: BindingMode.OneTime); - - Assert.Multiple(() => - { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - }); - - BindingHelpers.AssertTypedBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandProperty, BindingMode.OneTime, gestureElement.BindingContext); - } - - [Test] - [Obsolete("ClickGestureRecognizer is now obsolete")] - public void BindClickGesturePositionalParameters() - { - const int numberOfClicks = 2; - - var gestureElement = new TGestureElement - { - BindingContext = new ViewModel() - }; - object commandSource = gestureElement.BindingContext; - object parameterSource = gestureElement.BindingContext; - - gestureElement.BindClickGesture( - static (ViewModel vm) => vm.SetGuidCommand, - commandBindingMode: BindingMode.OneTime, - parameterGetter: static (ViewModel vm) => vm.Id, - numberOfClicksRequired: numberOfClicks); - - Assert.Multiple(() => - { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - Assert.That(((ClickGestureRecognizer)gestureElement.GestureRecognizers[0]).NumberOfClicksRequired, Is.EqualTo(numberOfClicks)); - }); - - BindingHelpers.AssertTypedBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandProperty, BindingMode.OneTime, gestureElement.BindingContext); - BindingHelpers.AssertTypedBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandParameterProperty, BindingMode.Default, gestureElement.BindingContext); - } - - - [Test] - [Obsolete("ClickGestureRecognizer is now obsolete")] - public void BindClickGesturePositionalParametersWithNestedBindings() - { - const int numberOfClicks = 2; - - var gestureElement = new TGestureElement - { - BindingContext = new ViewModel() - }; - object commandSource = gestureElement.BindingContext; - object parameterSource = gestureElement.BindingContext; - - gestureElement.BindClickGesture( - getter: static (ViewModel vm) => vm.NestedCommand.SetGuidCommand, - handlers: new (Func, string)[] - { - (vm => vm, nameof(ViewModel.NestedCommand)), (vm => vm.NestedCommand, nameof(ViewModel.NestedCommand.SetGuidCommand)) - }, - commandBindingMode: BindingMode.OneTime, - parameterGetter: static (ViewModel vm) => vm.NestedCommand.Id, - parameterHandlers: - [ - (vm => vm, nameof(ViewModel.NestedCommand)), - (vm => vm.NestedCommand, nameof(ViewModel.NestedCommand.Id)) - ], - numberOfClicksRequired: numberOfClicks); - - Assert.Multiple(() => - { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); - Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - Assert.That(((ClickGestureRecognizer)gestureElement.GestureRecognizers[0]).NumberOfClicksRequired, Is.EqualTo(numberOfClicks)); - }); - - BindingHelpers.AssertTypedBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandProperty, BindingMode.OneTime, gestureElement.BindingContext); - BindingHelpers.AssertTypedBindingExists((ClickGestureRecognizer)gestureElement.GestureRecognizers[0], ClickGestureRecognizer.CommandParameterProperty, BindingMode.Default, gestureElement.BindingContext); - } - [Test] public void BindTapGestureDefaults() { @@ -616,11 +371,11 @@ public void BindSwipeGesturePositionalParametersWithNestedBindings() public void MultipleGestureBindings() { var gestureElement = new TGestureElement - { - BindingContext = new ViewModel() - }.BindSwipeGesture(static (ViewModel vm) => vm.SetGuidCommand) + { + BindingContext = new ViewModel() + }.BindSwipeGesture(static (ViewModel vm) => vm.SetGuidCommand) .BindTapGesture(static (ViewModel vm) => vm.SetGuidCommand) - .BindClickGesture(static (ViewModel vm) => vm.SetGuidCommand); + .BindTapGesture(static (ViewModel vm) => vm.SetGuidCommand); Assert.Multiple(() => { diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/GridRowsColumns.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/GridRowsColumns.cs index e0ac70e9..373654d8 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/GridRowsColumns.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/GridRowsColumns.cs @@ -8,7 +8,7 @@ namespace CommunityToolkit.Maui.Markup.UnitTests; class GridRowsColumns : BaseMarkupTestFixture { const double starsValue = 1.5; - readonly GridLength starsLength = new GridLength(starsValue, GridUnitType.Star); + readonly GridLength starsLength = new(starsValue, GridUnitType.Star); enum Row { First, Second, Third, Fourth } enum Col { First, Second, Third, Fourth, Fifth } @@ -24,10 +24,10 @@ public void DefineRowsWithoutEnums() Assert.Multiple(() => { Assert.That(grid.RowDefinitions, Has.Count.EqualTo(4)); - Assert.That(grid.RowDefinitions[0]?.Height, Is.EqualTo(GridLength.Auto)); - Assert.That(grid.RowDefinitions[1]?.Height, Is.EqualTo(GridLength.Star)); - Assert.That(grid.RowDefinitions[2]?.Height, Is.EqualTo(starsLength)); - Assert.That(grid.RowDefinitions[3]?.Height, Is.EqualTo(new GridLength(20))); + Assert.That(grid.RowDefinitions[0].Height, Is.EqualTo(GridLength.Auto)); + Assert.That(grid.RowDefinitions[1].Height, Is.EqualTo(GridLength.Star)); + Assert.That(grid.RowDefinitions[2].Height, Is.EqualTo(starsLength)); + Assert.That(grid.RowDefinitions[3].Height, Is.EqualTo(new GridLength(20))); }); } @@ -47,10 +47,10 @@ public void DefineRowsWithEnums() Assert.Multiple(() => { Assert.That(grid.RowDefinitions, Has.Count.EqualTo(4)); - Assert.That(grid.RowDefinitions[0]?.Height, Is.EqualTo(GridLength.Auto)); - Assert.That(grid.RowDefinitions[1]?.Height, Is.EqualTo(GridLength.Star)); - Assert.That(grid.RowDefinitions[2]?.Height, Is.EqualTo(starsLength)); - Assert.That(grid.RowDefinitions[3]?.Height, Is.EqualTo(new GridLength(20))); + Assert.That(grid.RowDefinitions[0].Height, Is.EqualTo(GridLength.Auto)); + Assert.That(grid.RowDefinitions[1].Height, Is.EqualTo(GridLength.Star)); + Assert.That(grid.RowDefinitions[2].Height, Is.EqualTo(starsLength)); + Assert.That(grid.RowDefinitions[3].Height, Is.EqualTo(new GridLength(20))); }); } @@ -74,11 +74,11 @@ public void DefineColumnsWithoutEnums() Assert.Multiple(() => { Assert.That(grid.ColumnDefinitions, Has.Count.EqualTo(5)); - Assert.That(grid.ColumnDefinitions[0]?.Width, Is.EqualTo(GridLength.Auto)); - Assert.That(grid.ColumnDefinitions[1]?.Width, Is.EqualTo(GridLength.Star)); - Assert.That(grid.ColumnDefinitions[2]?.Width, Is.EqualTo(starsLength)); - Assert.That(grid.ColumnDefinitions[3]?.Width, Is.EqualTo(new GridLength(20))); - Assert.That(grid.ColumnDefinitions[4]?.Width, Is.EqualTo(new GridLength(40))); + Assert.That(grid.ColumnDefinitions[0].Width, Is.EqualTo(GridLength.Auto)); + Assert.That(grid.ColumnDefinitions[1].Width, Is.EqualTo(GridLength.Star)); + Assert.That(grid.ColumnDefinitions[2].Width, Is.EqualTo(starsLength)); + Assert.That(grid.ColumnDefinitions[3].Width, Is.EqualTo(new GridLength(20))); + Assert.That(grid.ColumnDefinitions[4].Width, Is.EqualTo(new GridLength(40))); }); } @@ -99,11 +99,11 @@ public void DefineColumnsWithEnums() Assert.Multiple(() => { Assert.That(grid.ColumnDefinitions, Has.Count.EqualTo(5)); - Assert.That(grid.ColumnDefinitions[0]?.Width, Is.EqualTo(GridLength.Auto)); - Assert.That(grid.ColumnDefinitions[1]?.Width, Is.EqualTo(GridLength.Star)); - Assert.That(grid.ColumnDefinitions[2]?.Width, Is.EqualTo(starsLength)); - Assert.That(grid.ColumnDefinitions[3]?.Width, Is.EqualTo(new GridLength(20))); - Assert.That(grid.ColumnDefinitions[4]?.Width, Is.EqualTo(new GridLength(40))); + Assert.That(grid.ColumnDefinitions[0].Width, Is.EqualTo(GridLength.Auto)); + Assert.That(grid.ColumnDefinitions[1].Width, Is.EqualTo(GridLength.Star)); + Assert.That(grid.ColumnDefinitions[2].Width, Is.EqualTo(starsLength)); + Assert.That(grid.ColumnDefinitions[3].Width, Is.EqualTo(new GridLength(20))); + Assert.That(grid.ColumnDefinitions[4].Width, Is.EqualTo(new GridLength(40))); }); } diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs index 213453cd..481069a5 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/TextAlignmentExtensionsTests.cs @@ -265,7 +265,7 @@ class MyEntry : Entry, IEntry interface IMyEntry { - public string Type { get; set; } + string Type { get; set; } } } diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/VisualElementExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/VisualElementExtensionsTests.cs index 0bf7d76a..4f5941e7 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/VisualElementExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/VisualElementExtensionsTests.cs @@ -295,7 +295,6 @@ public void ZIndex() public void Style() { var style = new Style(); - Bindable.Style = null; Bindable.Style(style); Assert.That(Bindable.Style, Is.EqualTo(style.MauiStyle)); } diff --git a/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs b/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs index 123deecd..7f993141 100644 --- a/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs @@ -45,7 +45,7 @@ public static TBindable LayoutFlags(this TBindable bindable, Absolute /// /// /// - public static TBindable LayoutFlags(this TBindable bindable, params AbsoluteLayoutFlags[] flags) where TBindable : BindableObject + public static TBindable LayoutFlags(this TBindable bindable, params List flags) where TBindable : BindableObject { var newFlags = AbsoluteLayoutFlags.None; diff --git a/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.cs b/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.cs index a1955b2f..3b3e1b17 100644 --- a/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.cs @@ -1,173 +1,11 @@ -namespace CommunityToolkit.Maui.Markup; +namespace CommunityToolkit.Maui.Markup; /// /// Extension Methods for Bindable Objects /// public static class BindableObjectExtensions { - /// Bind to a specified property - public static TBindable Bind( - this TBindable bindable, - BindableProperty targetProperty, - string path = Binding.SelfPath, - BindingMode mode = BindingMode.Default, - IValueConverter? converter = null, - object? converterParameter = null, - string? stringFormat = null, - object? source = null, - object? targetNullValue = null, - object? fallbackValue = null) where TBindable : BindableObject - { - bindable.SetBinding( - targetProperty, - new Binding(path, mode, converter, converterParameter, stringFormat, source) - { - TargetNullValue = targetNullValue, - FallbackValue = fallbackValue - }); - - return bindable; - } - - /// Bind to a specified property with inline conversion - public static TBindable Bind( - this TBindable bindable, - BindableProperty targetProperty, - string path = Binding.SelfPath, - BindingMode mode = BindingMode.Default, - Func? convert = null, - Func? convertBack = null, - string? stringFormat = null, - object? source = null, - TDest? targetNullValue = default, - TDest? fallbackValue = default) - where TBindable : BindableObject - { - var converter = new FuncConverter(convert, convertBack); - bindable.SetBinding( - targetProperty, - new Binding(path, mode, converter, null, stringFormat, source) - { - TargetNullValue = targetNullValue, - FallbackValue = fallbackValue - }); - - return bindable; - } - - /// Bind to a specified property with inline conversion and conversion parameter - public static TBindable Bind( - this TBindable bindable, - BindableProperty targetProperty, - string path = Binding.SelfPath, - BindingMode mode = BindingMode.Default, - Func? convert = null, - Func? convertBack = null, - TParam? converterParameter = default, - string? stringFormat = null, - object? source = null, - TDest? targetNullValue = default, - TDest? fallbackValue = default) where TBindable : BindableObject - { - var converter = new FuncConverter(convert, convertBack); - bindable.SetBinding( - targetProperty, - new Binding(path, mode, converter, converterParameter, stringFormat, source) - { - TargetNullValue = targetNullValue, - FallbackValue = fallbackValue - }); - - return bindable; - } - - /// Bind to the default property - public static TBindable Bind( - this TBindable bindable, - string path = Binding.SelfPath, - BindingMode mode = BindingMode.Default, - IValueConverter? converter = null, - object? converterParameter = null, - string? stringFormat = null, - object? source = null, - object? targetNullValue = null, - object? fallbackValue = null) where TBindable : BindableObject - { - bindable.Bind( - DefaultBindableProperties.GetDefaultProperty(), - path, mode, converter, converterParameter, stringFormat, source, targetNullValue, fallbackValue); - - return bindable; - } - - /// Bind to the default property with inline conversion - public static TBindable Bind( - this TBindable bindable, - string path = Binding.SelfPath, - BindingMode mode = BindingMode.Default, - Func? convert = null, - Func? convertBack = null, - string? stringFormat = null, - object? source = null, - TDest? targetNullValue = default, - TDest? fallbackValue = default) where TBindable : BindableObject - { - var converter = new FuncConverter(convert, convertBack); - - bindable.Bind( - DefaultBindableProperties.GetDefaultProperty(), - path, mode, converter, null, stringFormat, source, targetNullValue, fallbackValue); - - return bindable; - } - - /// Bind to the default property with inline conversion and conversion parameter - public static TBindable Bind( - this TBindable bindable, - string path = Binding.SelfPath, - BindingMode mode = BindingMode.Default, - Func? convert = null, - Func? convertBack = null, - TParam? converterParameter = default, - string? stringFormat = null, - object? source = null, - TDest? targetNullValue = default, - TDest? fallbackValue = default) where TBindable : BindableObject - { - var converter = new FuncConverter(convert, convertBack); - bindable.Bind( - DefaultBindableProperties.GetDefaultProperty(), - path, mode, converter, converterParameter, stringFormat, source, targetNullValue, fallbackValue); - - return bindable; - } - - /// Bind to the 's default Command and CommandParameter properties - /// The Bindable Object - /// Binding Path - /// Binding Source - /// If null, no binding is created for the CommandParameter property - /// Parameter Binding Source - public static TBindable BindCommand( - this TBindable bindable, - string path = Binding.SelfPath, - object? source = null, - string? parameterPath = Binding.SelfPath, - object? parameterSource = null) where TBindable : BindableObject - { - var (commandProperty, parameterProperty) = DefaultBindableProperties.GetCommandAndCommandParameterProperty(); - - bindable.SetBinding(commandProperty, new Binding(path: path, source: source)); - - if (parameterPath is not null) - { - bindable.SetBinding(parameterProperty, new Binding(path: parameterPath, source: parameterSource)); - } - - return bindable; - } - - /// + /// /// Sets the property values for and themes respectively. /// /// The type of the object. diff --git a/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs b/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs index 4c009363..458ab7b8 100644 --- a/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs +++ b/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs @@ -101,7 +101,7 @@ public static class DefaultBindableProperties /// Registers Bindable Properties /// /// - public static void Register(params BindableProperty[] properties) + public static void Register(params List properties) { foreach (var property in properties) { @@ -116,7 +116,7 @@ public static void Register(params BindableProperty[] properties) /// Registers Command and CommandParameter Properties /// /// - public static void RegisterForCommand(params (BindableProperty commandProperty, BindableProperty parameterProperty)[] propertyPairs) + public static void RegisterForCommand(params List<(BindableProperty commandProperty, BindableProperty parameterProperty)> propertyPairs) { foreach (var propertyPair in propertyPairs) { diff --git a/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs b/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs index 8a61536c..80ded52e 100644 --- a/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs @@ -29,7 +29,7 @@ public static TDynamicResourceHandler DynamicResource(t /// /// /// Layout with added Dynamic Resource - public static TDynamicResourceHandler DynamicResources(this TDynamicResourceHandler dynamicResourceHandler, params (BindableProperty property, string key)[] resources) + public static TDynamicResourceHandler DynamicResources(this TDynamicResourceHandler dynamicResourceHandler, params List<(BindableProperty property, string key)> resources) where TDynamicResourceHandler : IDynamicResourceHandler { foreach (var (property, key) in resources) diff --git a/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs b/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs index d3ba703e..1f6ffdc8 100644 --- a/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs @@ -59,7 +59,7 @@ public static TLayout Paddings(this TLayout paddingElement, double left /// /// /// Layout without Dynamic Resource - public static TBindable RemoveDynamicResources(this TBindable bindable, params BindableProperty[] properties) where TBindable : BindableObject + public static TBindable RemoveDynamicResources(this TBindable bindable, params List properties) where TBindable : BindableObject { foreach (var property in properties) { @@ -76,7 +76,7 @@ public static TBindable RemoveDynamicResources(this TBindable bindabl /// /// /// Element with added Effects - public static TElement Effects(this TElement element, params Effect[] effects) where TElement : Element + public static TElement Effects(this TElement element, params List effects) where TElement : Element { foreach (var effect in effects) { diff --git a/src/CommunityToolkit.Maui.Markup/GesturesExtensions.TypedBindings.cs b/src/CommunityToolkit.Maui.Markup/GesturesExtensions.TypedBindings.cs deleted file mode 100644 index 958e10cf..00000000 --- a/src/CommunityToolkit.Maui.Markup/GesturesExtensions.TypedBindings.cs +++ /dev/null @@ -1,419 +0,0 @@ -using System.Linq.Expressions; -using System.Windows.Input; -namespace CommunityToolkit.Maui.Markup; - -public static partial class GesturesExtensions -{ - /// Add a and bind to its Command - [Obsolete($"{nameof(BindClickGesture)} is deprecated; please use ${nameof(BindTapGesture)} instead.")] - public static TGestureElement BindClickGesture( - this TGestureElement gestureElement, - Expression> getter, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode mode = BindingMode.Default, - int? numberOfClicksRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - { - return BindClickGesture( - gestureElement, - getter, - setter, - source, - mode, - numberOfClicksRequired: numberOfClicksRequired); - } - - /// Add a and bind to its Command and (optionally) CommandParameter properties - [Obsolete($"{nameof(BindClickGesture)} is deprecated; please use ${nameof(BindTapGesture)} instead.")] - public static TGestureElement BindClickGesture( - this TGestureElement gestureElement, - Expression> getter, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode commandBindingMode = BindingMode.Default, - Expression>? parameterGetter = null, - Action? parameterSetter = null, - BindingMode parameterBindingMode = BindingMode.Default, - TParameterBindingContext? parameterSource = default, - int? numberOfClicksRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - where TParameterBindingContext : class? - { - var getterFunc = ConvertExpressionToFunc(getter); - var parameterGetterFunc = parameterGetter switch - { - null => null, - _ => ConvertExpressionToFunc(parameterGetter) - }; - - (Func, string)[]? parameterGetterHandlers = parameterGetter switch - { - null => null, - _ => [(b => b, GetMemberName(parameterGetter))] - }; - - return BindClickGesture( - gestureElement, - getterFunc, - [ - (b => b, GetMemberName(getter)) - ], - setter, - source, - commandBindingMode, - parameterGetterFunc, - parameterGetterHandlers, - parameterSetter, - parameterBindingMode, - parameterSource, - numberOfClicksRequired); - } - - /// Add a and bind to its Command - [Obsolete($"{nameof(BindClickGesture)} is deprecated; please use ${nameof(BindTapGesture)} instead.")] - public static TGestureElement BindClickGesture( - this TGestureElement gestureElement, - Func getter, - (Func, string)[] handlers, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode mode = BindingMode.Default, - int? numberOfClicksRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - { - return gestureElement.BindClickGesture( - getter, - handlers, - setter, - source, - mode, - numberOfClicksRequired: numberOfClicksRequired); - } - - /// Add a and bind to its Command and (optionally) CommandParameter properties - [Obsolete($"{nameof(BindClickGesture)} is deprecated; please use ${nameof(BindTapGesture)} instead.")] - public static TGestureElement BindClickGesture( - this TGestureElement gestureElement, - Func getter, - (Func, string)[] handlers, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode commandBindingMode = BindingMode.Default, - Func? parameterGetter = null, - (Func, string)[]? parameterHandlers = null, - Action? parameterSetter = null, - BindingMode parameterBindingMode = BindingMode.Default, - TParameterBindingContext? parameterSource = default, - int? numberOfClicksRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - where TParameterBindingContext : class? - { - var clickGesture = gestureElement.BindGesture( - getter, - handlers, - setter, - source, - commandBindingMode, - parameterGetter, - parameterHandlers, - parameterSetter, - parameterBindingMode, - parameterSource); - - return gestureElement.ConfigureClickGesture(clickGesture, numberOfClicksRequired); - } - - /// Add a and bind to its Command - public static TGestureElement BindSwipeGesture( - this TGestureElement gestureElement, - Expression> getter, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode mode = BindingMode.Default, - SwipeDirection? direction = null, - uint? threshold = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - { - return BindSwipeGesture( - gestureElement, - getter, - setter, - source, - mode, - direction: direction, - threshold: threshold); - } - - /// Add a and bind to its Command and (optionally) CommandParameter properties - public static TGestureElement BindSwipeGesture( - this TGestureElement gestureElement, - Expression> getter, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode commandBindingMode = BindingMode.Default, - Expression>? parameterGetter = null, - Action? parameterSetter = null, - BindingMode parameterBindingMode = BindingMode.Default, - TParameterBindingContext? parameterSource = default, - SwipeDirection? direction = null, - uint? threshold = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - where TParameterBindingContext : class? - { - var getterFunc = ConvertExpressionToFunc(getter); - var parameterGetterFunc = parameterGetter switch - { - null => null, - _ => ConvertExpressionToFunc(parameterGetter) - }; - - (Func, string)[]? parameterGetterHandlers = parameterGetter switch - { - null => null, - _ => [(b => b, GetMemberName(parameterGetter))] - }; - - return BindSwipeGesture( - gestureElement, - getterFunc, - [ - (b => b, GetMemberName(getter)) - ], - setter, - source, - commandBindingMode, - parameterGetterFunc, - parameterGetterHandlers, - parameterSetter, - parameterBindingMode, - parameterSource, - direction, - threshold); - } - - /// Add a and bind to its Command - public static TGestureElement BindSwipeGesture( - this TGestureElement gestureElement, - Func getter, - (Func, string)[] handlers, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode mode = BindingMode.Default, - SwipeDirection? direction = null, - uint? threshold = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - { - return gestureElement.BindSwipeGesture( - getter, - handlers, - setter, - source, - mode, - direction: direction, - threshold: threshold); - } - - /// Add a and bind to its Command and (optionally) CommandParameter properties - public static TGestureElement BindSwipeGesture( - this TGestureElement gestureElement, - Func getter, - (Func, string)[] handlers, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode commandBindingMode = BindingMode.Default, - Func? parameterGetter = null, - (Func, string)[]? parameterHandlers = null, - Action? parameterSetter = null, - BindingMode parameterBindingMode = BindingMode.Default, - TParameterBindingContext? parameterSource = default, - SwipeDirection? direction = null, - uint? threshold = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - where TParameterBindingContext : class? - { - var clickGesture = gestureElement.BindGesture( - getter, - handlers, - setter, - source, - commandBindingMode, - parameterGetter, - parameterHandlers, - parameterSetter, - parameterBindingMode, - parameterSource); - - return gestureElement.ConfigureSwipeGesture(clickGesture, direction, threshold); - } - - /// Adds a and bind its Command - public static TGestureElement BindTapGesture( - this TGestureElement gestureElement, - Expression> getter, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode mode = BindingMode.Default, - int? numberOfTapsRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - { - - return BindTapGesture( - gestureElement, - getter, - setter, - source, - mode, - numberOfTapsRequired: numberOfTapsRequired); - } - - /// Adds a and bind its Command and (optionally) CommandParameter properties - public static TGestureElement BindTapGesture( - this TGestureElement gestureElement, - Expression> getter, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode commandBindingMode = BindingMode.Default, - Expression>? parameterGetter = null, - Action? parameterSetter = null, - BindingMode parameterBindingMode = BindingMode.Default, - TParameterBindingContext? parameterSource = default, - int? numberOfTapsRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - where TParameterBindingContext : class? - { - var getterFunc = ConvertExpressionToFunc(getter); - var parameterGetterFunc = parameterGetter switch - { - null => null, - _ => ConvertExpressionToFunc(parameterGetter) - }; - - (Func, string)[]? parameterGetterHandlers = parameterGetter switch - { - null => null, - _ => [(b => b, GetMemberName(parameterGetter))] - }; - - return BindTapGesture( - gestureElement, - getterFunc, - [ - (b => b, GetMemberName(getter)) - ], - setter, - source, - commandBindingMode, - parameterGetterFunc, - parameterGetterHandlers, - parameterSetter, - parameterBindingMode, - parameterSource, - numberOfTapsRequired); - } - - /// Adds a and bind its Command - public static TGestureElement BindTapGesture( - this TGestureElement gestureElement, - Func getter, - (Func, string)[] handlers, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode mode = BindingMode.Default, - int? numberOfTapsRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - { - return gestureElement.BindTapGesture( - getter, - handlers, - setter, - source, - mode, - numberOfTapsRequired: numberOfTapsRequired); - } - - /// Adds a and bind its Command and (optionally) CommandParameter properties - public static TGestureElement BindTapGesture( - this TGestureElement gestureElement, - Func getter, - (Func, string)[] handlers, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode commandBindingMode = BindingMode.Default, - Func? parameterGetter = null, - (Func, string)[]? parameterHandlers = null, - Action? parameterSetter = null, - BindingMode parameterBindingMode = BindingMode.Default, - TParameterBindingContext? parameterSource = default, - int? numberOfTapsRequired = null) - where TGestureElement : BindableObject, IGestureRecognizers - where TCommandBindingContext : class? - where TParameterBindingContext : class? - { - var tapGesture = gestureElement.BindGesture( - getter, - handlers, - setter, - source, - commandBindingMode, - parameterGetter, - parameterHandlers, - parameterSetter, - parameterBindingMode, - parameterSource); - - return gestureElement.ConfigureTapGesture(tapGesture, numberOfTapsRequired); - } - - static Func ConvertExpressionToFunc(in Expression> expression) => expression.Compile(); - - static string GetMemberName(in Expression expression) => expression.Body switch - { - MemberExpression m => m.Member.Name, - UnaryExpression { Operand: MemberExpression m } => m.Member.Name, - _ => throw new InvalidOperationException("Invalid getter. The `getter` parameter must point directly to a property in the ViewModel and cannot add additional logic") - }; - - static TGestureRecognizer BindGesture( - this TGestureElement gestureElement, - Func getter, - (Func, string)[] handlers, - Action? setter = null, - TCommandBindingContext? source = default, - BindingMode commandBindingMode = BindingMode.Default, - Func? parameterGetter = null, - (Func, string)[]? parameterHandlers = null, - Action? parameterSetter = null, - BindingMode parameterBindingMode = BindingMode.Default, - TParameterBindingContext? parameterSource = default) - where TGestureElement : IGestureRecognizers - where TGestureRecognizer : BindableObject, IGestureRecognizer, new() - where TCommandBindingContext : class? - where TParameterBindingContext : class? - { - var gestureRecognizer = new TGestureRecognizer().BindCommand(getter, - handlers, - setter, - source, - commandBindingMode, - parameterGetter, - parameterHandlers, - parameterSetter, - parameterSource, - parameterBindingMode); - gestureElement.GestureRecognizers.Add(gestureRecognizer); - - return gestureRecognizer; - } -} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup/GesturesExtensions.cs b/src/CommunityToolkit.Maui.Markup/GesturesExtensions.cs index dcf34f6f..b5314bc9 100644 --- a/src/CommunityToolkit.Maui.Markup/GesturesExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/GesturesExtensions.cs @@ -1,101 +1,260 @@ -using System.Windows.Input; +using System.Linq.Expressions; +using System.Windows.Input; namespace CommunityToolkit.Maui.Markup; /// /// Extension Methods for Element Gestures /// -public static partial class GesturesExtensions +public static class GesturesExtensions { - /// Add a and bind to its Command and (optionally) CommandParameter properties - /// An implementing - /// Path to Command Binding - /// Binding source for Command Binding - /// If not specified or null, no binding is created for the CommandParameter property - /// Binding source for Command Binding - /// Total number of clicks required to trigger - /// - [Obsolete($"{nameof(BindClickGesture)} is deprecated; please use ${nameof(BindTapGesture)} instead.")] - public static TGestureElement BindClickGesture(this TGestureElement gestureElement, - string commandPath, - object? commandSource = null, - string? parameterPath = null, - object? parameterSource = null, - int? numberOfClicksRequired = null) where TGestureElement : IGestureRecognizers + /// Add a and bind to its Command + public static TGestureElement BindSwipeGesture( + this TGestureElement gestureElement, + Expression> getter, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode mode = BindingMode.Default, + SwipeDirection? direction = null, + uint? threshold = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class { - var clickGesture = gestureElement.BindGesture(commandPath, commandSource, parameterPath, parameterSource); - - return gestureElement.ConfigureClickGesture(clickGesture, numberOfClicksRequired); + return BindSwipeGesture( + gestureElement, + getter, + setter, + source, + mode, + direction: direction, + threshold: threshold); } /// Add a and bind to its Command and (optionally) CommandParameter properties - /// An implementing - /// Path to Command Binding - /// Binding source for Command Binding - /// If not specified or null, no binding is created for the CommandParameter property - /// Binding source for Command Binding - /// Swipe gesture direction - /// Minimum swipe distance that will cause the gesture to be recognized - /// - public static TGestureElement BindSwipeGesture(this TGestureElement gestureElement, - string commandPath, - object? commandSource = null, - string? parameterPath = null, - object? parameterSource = null, - SwipeDirection? direction = null, - uint? threshold = null) where TGestureElement : IGestureRecognizers + public static TGestureElement BindSwipeGesture( + this TGestureElement gestureElement, + Expression> getter, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode commandBindingMode = BindingMode.Default, + Expression>? parameterGetter = null, + Action? parameterSetter = null, + BindingMode parameterBindingMode = BindingMode.Default, + TParameterBindingContext? parameterSource = default, + SwipeDirection? direction = null, + uint? threshold = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class + where TParameterBindingContext : class { - var swipeGesture = gestureElement.BindGesture(commandPath, commandSource, parameterPath, parameterSource); + var getterFunc = ConvertExpressionToFunc(getter); + var parameterGetterFunc = parameterGetter switch + { + null => null, + _ => ConvertExpressionToFunc(parameterGetter) + }; + + (Func, string)[]? parameterGetterHandlers = parameterGetter switch + { + null => null, + _ => [(b => b, GetMemberName(parameterGetter))] + }; - return gestureElement.ConfigureSwipeGesture(swipeGesture, direction, threshold); + return BindSwipeGesture( + gestureElement, + getterFunc, + [ + (b => b, GetMemberName(getter)) + ], + setter, + source, + commandBindingMode, + parameterGetterFunc, + parameterGetterHandlers, + parameterSetter, + parameterBindingMode, + parameterSource, + direction, + threshold); } - /// Adds a and bind its Command and (optionally) CommandParameter properties - /// An implementing - /// Path to Command Binding - /// Binding source for Command Binding - /// If not specified or null, no binding is created for the CommandParameter property - /// Binding source for Command Binding - /// Number of taps required to trigger the - /// - public static TGestureElement BindTapGesture(this TGestureElement gestureElement, - string commandPath, - object? commandSource = null, - string? parameterPath = null, - object? parameterSource = null, - int? numberOfTapsRequired = null) where TGestureElement : IGestureRecognizers + /// Add a and bind to its Command + public static TGestureElement BindSwipeGesture( + this TGestureElement gestureElement, + Func getter, + (Func, string)[] handlers, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode mode = BindingMode.Default, + SwipeDirection? direction = null, + uint? threshold = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class { - var tapGesture = gestureElement.BindGesture(commandPath, commandSource, parameterPath, parameterSource); + return gestureElement.BindSwipeGesture( + getter, + handlers, + setter, + source, + mode, + direction: direction, + threshold: threshold); + } - return gestureElement.ConfigureTapGesture(tapGesture, numberOfTapsRequired); + /// Add a and bind to its Command and (optionally) CommandParameter properties + public static TGestureElement BindSwipeGesture( + this TGestureElement gestureElement, + Func getter, + (Func, string)[] handlers, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode commandBindingMode = BindingMode.Default, + Func? parameterGetter = null, + (Func, string)[]? parameterHandlers = null, + Action? parameterSetter = null, + BindingMode parameterBindingMode = BindingMode.Default, + TParameterBindingContext? parameterSource = default, + SwipeDirection? direction = null, + uint? threshold = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class + where TParameterBindingContext : class + { + var clickGesture = gestureElement.BindGesture( + getter, + handlers, + setter, + source, + commandBindingMode, + parameterGetter, + parameterHandlers, + parameterSetter, + parameterBindingMode, + parameterSource); + + return gestureElement.ConfigureSwipeGesture(clickGesture, direction, threshold); } - /// - /// Adds a and sets defines its action when clicked - /// - /// An implementing - /// invoked once threshold is reached - /// Total number of clicks required to trigger - /// - [Obsolete($"{nameof(ClickGesture)} is deprecated; please use ${nameof(TapGesture)} instead.")] - public static TGestureElement ClickGesture(this TGestureElement gestureElement, - Action onClicked, - int? numberOfClicksRequired = null) where TGestureElement : IGestureRecognizers + /// Adds a and bind its Command + public static TGestureElement BindTapGesture( + this TGestureElement gestureElement, + Expression> getter, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode mode = BindingMode.Default, + int? numberOfTapsRequired = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class { - var gestureRecognizer = new ClickGestureRecognizer + + return BindTapGesture( + gestureElement, + getter, + setter, + source, + mode, + numberOfTapsRequired: numberOfTapsRequired); + } + + /// Adds a and bind its Command and (optionally) CommandParameter properties + public static TGestureElement BindTapGesture( + this TGestureElement gestureElement, + Expression> getter, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode commandBindingMode = BindingMode.Default, + Expression>? parameterGetter = null, + Action? parameterSetter = null, + BindingMode parameterBindingMode = BindingMode.Default, + TParameterBindingContext? parameterSource = default, + int? numberOfTapsRequired = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class + where TParameterBindingContext : class + { + var getterFunc = ConvertExpressionToFunc(getter); + var parameterGetterFunc = parameterGetter switch { - Command = new Command(onClicked) + null => null, + _ => ConvertExpressionToFunc(parameterGetter) }; - if (numberOfClicksRequired is not null) + (Func, string)[]? parameterGetterHandlers = parameterGetter switch { - gestureRecognizer.NumberOfClicksRequired = numberOfClicksRequired.Value; - } + null => null, + _ => [(b => b, GetMemberName(parameterGetter))] + }; - gestureElement.GestureRecognizers.Add(gestureRecognizer); + return BindTapGesture( + gestureElement, + getterFunc, + [ + (b => b, GetMemberName(getter)) + ], + setter, + source, + commandBindingMode, + parameterGetterFunc, + parameterGetterHandlers, + parameterSetter, + parameterBindingMode, + parameterSource, + numberOfTapsRequired); + } - return gestureElement; + /// Adds a and bind its Command + public static TGestureElement BindTapGesture( + this TGestureElement gestureElement, + Func getter, + (Func, string)[] handlers, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode mode = BindingMode.Default, + int? numberOfTapsRequired = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class + { + return gestureElement.BindTapGesture( + getter, + handlers, + setter, + source, + mode, + numberOfTapsRequired: numberOfTapsRequired); } + /// Adds a and bind its Command and (optionally) CommandParameter properties + public static TGestureElement BindTapGesture( + this TGestureElement gestureElement, + Func getter, + (Func, string)[] handlers, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode commandBindingMode = BindingMode.Default, + Func? parameterGetter = null, + (Func, string)[]? parameterHandlers = null, + Action? parameterSetter = null, + BindingMode parameterBindingMode = BindingMode.Default, + TParameterBindingContext? parameterSource = default, + int? numberOfTapsRequired = null) + where TGestureElement : BindableObject, IGestureRecognizers + where TCommandBindingContext : class + where TParameterBindingContext : class + { + var tapGesture = gestureElement.BindGesture( + getter, + handlers, + setter, + source, + commandBindingMode, + parameterGetter, + parameterHandlers, + parameterSetter, + parameterBindingMode, + parameterSource); + + return gestureElement.ConfigureTapGesture(tapGesture, numberOfTapsRequired); + } + /// /// Adds a /// @@ -205,24 +364,52 @@ public static TGestureElement TapGesture(this TGestureElement g return gestureElement; } + + static Func ConvertExpressionToFunc(in Expression> expression) => expression.Compile(); - [Obsolete($"{nameof(BindClickGesture)} is deprecated; please use ${nameof(BindTapGesture)} instead.")] - static TGestureElement ConfigureClickGesture(this TGestureElement gestureElement, - ClickGestureRecognizer clickGesture, - int? numberOfClicksRequired = null) where TGestureElement : IGestureRecognizers + static string GetMemberName(in Expression expression) => expression.Body switch { - if (numberOfClicksRequired is not null) - { - clickGesture.NumberOfClicksRequired = numberOfClicksRequired.Value; - } + MemberExpression m => m.Member.Name, + UnaryExpression { Operand: MemberExpression m } => m.Member.Name, + _ => throw new InvalidOperationException("Invalid getter. The `getter` parameter must point directly to a property in the ViewModel and cannot add additional logic") + }; - return gestureElement; - } + static TGestureRecognizer BindGesture( + this TGestureElement gestureElement, + Func getter, + (Func, string)[] handlers, + Action? setter = null, + TCommandBindingContext? source = default, + BindingMode commandBindingMode = BindingMode.Default, + Func? parameterGetter = null, + (Func, string)[]? parameterHandlers = null, + Action? parameterSetter = null, + BindingMode parameterBindingMode = BindingMode.Default, + TParameterBindingContext? parameterSource = default) + where TGestureElement : IGestureRecognizers + where TGestureRecognizer : BindableObject, IGestureRecognizer, new() + where TCommandBindingContext : class + where TParameterBindingContext : class + { + var gestureRecognizer = new TGestureRecognizer().BindCommand(getter, + handlers, + setter, + source, + commandBindingMode, + parameterGetter, + parameterHandlers, + parameterSetter, + parameterSource, + parameterBindingMode); + gestureElement.GestureRecognizers.Add(gestureRecognizer); + return gestureRecognizer; + } + static TGestureElement ConfigureSwipeGesture(this TGestureElement gestureElement, - SwipeGestureRecognizer swipeGesture, - SwipeDirection? direction = null, - uint? threshold = null) where TGestureElement : IGestureRecognizers + SwipeGestureRecognizer swipeGesture, + SwipeDirection? direction = null, + uint? threshold = null) where TGestureElement : IGestureRecognizers { if (direction is not null) { @@ -238,8 +425,8 @@ static TGestureElement ConfigureSwipeGesture(this TGestureEleme } static TGestureElement ConfigureTapGesture(this TGestureElement gestureElement, - TapGestureRecognizer tapGesture, - int? numberOfTapsRequired = null) where TGestureElement : IGestureRecognizers + TapGestureRecognizer tapGesture, + int? numberOfTapsRequired = null) where TGestureElement : IGestureRecognizers { if (numberOfTapsRequired is not null) { @@ -248,18 +435,4 @@ static TGestureElement ConfigureTapGesture(this TGestureElement return gestureElement; } - - static TGestureRecognizer BindGesture( - this TGestureElement gestureElement, - string commandPath, - object? commandSource = null, - string? parameterPath = null, - object? parameterSource = null) where TGestureElement : IGestureRecognizers - where TGestureRecognizer : BindableObject, IGestureRecognizer, new() - { - var gestureRecognizer = new TGestureRecognizer().BindCommand(commandPath, commandSource, parameterPath, parameterSource); - gestureElement.GestureRecognizers.Add(gestureRecognizer); - - return gestureRecognizer; - } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs b/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs index ac05ba92..9b832ead 100644 --- a/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs +++ b/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs @@ -33,7 +33,7 @@ public static class Columns /// /// /// - public static ColumnDefinitionCollection Define(params GridLength[] widths) + public static ColumnDefinitionCollection Define(params List widths) { var columnDefinitions = new ColumnDefinitionCollection(); @@ -52,20 +52,20 @@ public static ColumnDefinitionCollection Define(params GridLength[] widths) /// /// /// - public static ColumnDefinitionCollection Define(params (TEnum name, GridLength width)[] columns) where TEnum : Enum + public static ColumnDefinitionCollection Define(params List<(TEnum Name, GridLength Width)> columns) where TEnum : Enum { var columnDefinitions = new ColumnDefinitionCollection(); - for (int i = 0; i < columns.Length; i++) + for (var i = 0; i < columns.Count; i++) { - if (i != columns[i].name.ToInt()) + if (i != columns[i].Name.ToInt()) { throw new ArgumentException( - $"Value of column name {columns[i].name} is not {i}. " + + $"Value of column name {columns[i].Name} is not {i}. " + "Columns must be defined with enum names whose values form the sequence 0,1,2,..."); } - columnDefinitions.Add(new ColumnDefinition { Width = columns[i].width }); + columnDefinitions.Add(new ColumnDefinition { Width = columns[i].Width }); } return columnDefinitions; @@ -82,7 +82,7 @@ public static class Rows /// /// /// - public static RowDefinitionCollection Define(params GridLength[] heights) + public static RowDefinitionCollection Define(params List heights) { var rowDefinitions = new RowDefinitionCollection(); @@ -101,19 +101,19 @@ public static RowDefinitionCollection Define(params GridLength[] heights) /// /// /// - public static RowDefinitionCollection Define(params (TEnum name, GridLength height)[] rows) where TEnum : Enum + public static RowDefinitionCollection Define(params List<(TEnum Name, GridLength Height)> rows) where TEnum : Enum { var rowDefinitions = new RowDefinitionCollection(); - for (int i = 0; i < rows.Length; i++) + for (var i = 0; i < rows.Count; i++) { - if (i != rows[i].name.ToInt()) + if (i != rows[i].Name.ToInt()) { throw new ArgumentException( - $"Value of row name {rows[i].name} is not {i}. " + + $"Value of row name {rows[i].Name} is not {i}. " + "Rows must be defined with enum names whose values form the sequence 0,1,2,..."); } - rowDefinitions.Add(new RowDefinition { Height = rows[i].height }); + rowDefinitions.Add(new RowDefinition { Height = rows[i].Height }); } return rowDefinitions; } diff --git a/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs b/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs index 448702cf..3efa6016 100644 --- a/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs @@ -12,7 +12,7 @@ public static class LabelExtensions /// /// /// Label with added FormattedText - public static TLabel FormattedText(this TLabel label, params Span[] spans) where TLabel : Label + public static TLabel FormattedText(this TLabel label, params List spans) where TLabel : Label { label.FormattedText = new FormattedString(); diff --git a/src/CommunityToolkit.Maui.Markup/Style.cs b/src/CommunityToolkit.Maui.Markup/Style.cs index 825b0b86..ea2473d5 100644 --- a/src/CommunityToolkit.Maui.Markup/Style.cs +++ b/src/CommunityToolkit.Maui.Markup/Style.cs @@ -33,7 +33,7 @@ public Style(BindableProperty property, object value) : this((property, value)) /// Initialize Style /// /// - public Style(params (BindableProperty Property, object Value)[] setters) + public Style(params List<(BindableProperty Property, object Value)> setters) { MauiStyle = new Style(typeof(T)); Add(setters); @@ -97,7 +97,7 @@ public Style Add(BindableProperty property, object value) /// /// /// Style with added setters - public Style Add(params (BindableProperty Property, object Value)[] setters) + public Style Add(params List<(BindableProperty Property, object Value)> setters) { foreach (var (property, value) in setters) { @@ -129,7 +129,7 @@ public Style AddAppThemeBinding(BindableProperty property, object light, obje /// /// A set of , and value for light and dark theme. /// Style with added setters - public Style AddAppThemeBindings(params (BindableProperty Property, object Light, object Dark)[] setters) + public Style AddAppThemeBindings(params List<(BindableProperty Property, object Light, object Dark)> setters) { foreach (var (property, light, dark) in setters) { @@ -144,7 +144,7 @@ public Style AddAppThemeBindings(params (BindableProperty Property, object Li /// /// /// Style with added behaviors - public Style Add(params Behavior[] behaviors) + public Style Add(params List behaviors) { foreach (var behavior in behaviors) { @@ -159,7 +159,7 @@ public Style Add(params Behavior[] behaviors) /// /// /// Style with added Triggers - public Style Add(params TriggerBase[] triggers) + public Style Add(params List triggers) { foreach (var trigger in triggers) { diff --git a/src/CommunityToolkit.Maui.Markup/TypedBinding.cs b/src/CommunityToolkit.Maui.Markup/TypedBinding.cs index d4c624a7..8e410d66 100644 --- a/src/CommunityToolkit.Maui.Markup/TypedBinding.cs +++ b/src/CommunityToolkit.Maui.Markup/TypedBinding.cs @@ -1,19 +1,22 @@ using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.Xaml.Diagnostics; namespace CommunityToolkit.Maui.Markup; -sealed class TypedBinding : TypedBindingBase +sealed class TypedBinding : TypedBindingBase where TSource : class? { - readonly WeakReference weakSource = new(null); + readonly WeakReference weakSource = new(null); readonly WeakReference weakTarget = new(null); readonly Func getter; readonly Action? setter; readonly PropertyChangedProxy[] handlers; + readonly List> ancestryChain = []; + bool isBindingContextRelativeSource; SetterSpecificity? specificity; BindableProperty? targetProperty; @@ -51,14 +54,20 @@ internal override void Apply(bool fromTarget = false) } // Applies the binding to a new source or target. - internal override void Apply(object context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged, SetterSpecificity specificity) + [MemberNotNull(nameof(targetProperty)), MemberNotNull(nameof(specificity))] + internal override void Apply(object? context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged, SetterSpecificity specificity) { + if (context is not TSource sourceContext) + { + throw new InvalidOperationException($"Invalid context. Expected type {typeof(TSource).FullName}, but received {context?.GetType()?.FullName ?? "null"}"); + } + this.targetProperty = targetProperty; this.specificity = specificity; var source = Source ?? Context ?? context; var isApplied = IsApplied; - if (Source != null && isApplied && fromBindingContextChanged) + if (Source is not null && isApplied && fromBindingContextChanged) { return; } @@ -75,10 +84,10 @@ internal override void Apply(object context, BindableObject bindObj, BindablePro throw new InvalidOperationException("Binding instances cannot be reused"); } - weakSource.SetTarget(source); + weakSource.SetTarget(sourceContext); weakTarget.SetTarget(bindObj); - ApplyCore(source, bindObj, targetProperty, false, specificity); + ApplyCore(sourceContext, bindObj, targetProperty, false, specificity); } internal override BindingBase Clone() @@ -111,6 +120,7 @@ internal override object GetSourceValue(object? value, Type targetPropertyType) return base.GetSourceValue(value, targetPropertyType); } + [return: NotNullIfNotNull(nameof(value))] internal override object? GetTargetValue(object? value, Type sourcePropertyType) { if (Converter is not null) @@ -137,12 +147,73 @@ internal override void Unapply(bool fromBindingContextChanged = false) weakTarget.SetTarget(null); } + internal override void ApplyToResolvedSource(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget, SetterSpecificity specificity) + { + if (sourceObject is not TSource source) + { + throw new InvalidOperationException($"Invalid context. Expected type {typeof(TSource).FullName}, but received {sourceObject.GetType()?.FullName ?? "null"}"); + } + + weakTarget.SetTarget(target); + weakSource.SetTarget(source); + + ApplyCore(source, target, property, fromTarget, specificity); + } + + internal override void SubscribeToAncestryChanges(List chain, bool includeBindingContext, bool rootIsSource) + { + ArgumentNullException.ThrowIfNull(chain); + + ClearAncestryChangeSubscriptions(); + + isBindingContextRelativeSource = includeBindingContext; + ancestryChain.Clear(); + + for (int i = 0; i < chain.Count; i++) + { + var elem = chain[i]; + if (i != chain.Count - 1 || !rootIsSource) + { + // don't care about a successfully resolved source's parents + elem.ParentSet += OnElementParentSet; + } + if (isBindingContextRelativeSource) + { + elem.BindingContextChanged += OnElementBindingContextChanged; + } + + ancestryChain.Add(new WeakReference(elem)); + } + } + + void ClearAncestryChangeSubscriptions(int beginningWith = 0) + { + if (ancestryChain.Count is 0) + { + return; + } + + var count = ancestryChain.Count; + for (var i = beginningWith; i < count; i++) + { + var weakElement = ancestryChain.Last(); + if (weakElement.TryGetTarget(out var elem)) + { + elem.ParentSet -= OnElementParentSet; + if (isBindingContextRelativeSource) + { + elem.BindingContextChanged -= OnElementBindingContextChanged; + } + } + ancestryChain.RemoveAt(ancestryChain.Count - 1); + } + } + // ApplyCore is as slim as it should be: // Setting 100000 values : 17ms. // ApplyCore 100000 (w/o INPC, w/o unapply) : 20ms. - void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget, SetterSpecificity specificity) + void ApplyCore(TSource? sourceObject, BindableObject target, BindableProperty property, bool fromTarget, SetterSpecificity specificity) { - var isTSource = sourceObject is TSource; var mode = this.GetRealizedMode(property); if (mode is BindingMode.OneWay or BindingMode.OneTime && fromTarget) { @@ -151,26 +222,26 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop var needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime; - if (isTSource && mode is BindingMode.OneWay or BindingMode.TwoWay) + if (sourceObject is not null && mode is BindingMode.OneWay or BindingMode.TwoWay) { - Subscribe((TSource)sourceObject); + Subscribe(sourceObject); } if (needsGetter) { var value = FallbackValue ?? property.GetDefaultValue(target); - if (isTSource) + if (sourceObject is not null) { try { - var returnValue = getter((TSource)sourceObject); + var returnValue = getter(sourceObject); value = GetSourceValue(returnValue, property.ReturnType); } catch (Exception ex) when (ex is NullReferenceException or KeyNotFoundException or IndexOutOfRangeException or ArgumentOutOfRangeException) { } } - if (!BindingExpression.TryConvert(ref value, property, property.ReturnType, true)) + if (!BindingExpressionHelper.TryConvert(ref value, property, property.ReturnType, true)) { BindingDiagnostics.SendBindingFailure(this, sourceObject, target, property, "Binding", BindingExpression.CannotConvertTypeErrorMessage, value, property.ReturnType); return; @@ -180,15 +251,101 @@ void ApplyCore(object sourceObject, BindableObject target, BindableProperty prop } var needsSetter = (mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource; - if (needsSetter && isTSource) + if (needsSetter && sourceObject is not null) { - var value = GetTargetValue(target.GetValue(property), typeof(TProperty)); - if (!BindingExpression.TryConvert(ref value, property, typeof(TProperty), false)) + var value = GetTargetValue(target.GetValue(property), typeof(TProperty)) ?? throw new InvalidOperationException("Unable to find target value"); + + if (!BindingExpressionHelper.TryConvert(ref value, property, typeof(TProperty), false)) { BindingDiagnostics.SendBindingFailure(this, sourceObject, target, property, "Binding", BindingExpression.CannotConvertTypeErrorMessage, value, typeof(TProperty)); return; } - setter?.Invoke((TSource)sourceObject, (TProperty)value); + + setter?.Invoke(sourceObject, (TProperty)value); + } + } + + // Returns null if the member is not in the chain or the + // chain is no longer valid. + int? FindAncestryIndex(Element elem) + { + for (var i = 0; i < ancestryChain.Count; i++) + { + var weak = ancestryChain[i]; + if (!weak.TryGetTarget(out var chainMember)) + { + return -1; + } + + if (Equals(elem, chainMember)) + { + return i; + } + } + + return null; + } + + void OnElementBindingContextChanged(object? sender, EventArgs e) + { + if (sender is not Element elem) + { + return; + } + + if (!weakTarget.TryGetTarget(out var target) || targetProperty is null) + { + return; + } + + if (weakSource.TryGetTarget(out var currentSource)) + { + // make sure that this isn't just a repeat notice + // from someone else in the chain about our already-resolved + // binding source + if (ReferenceEquals(currentSource, elem.BindingContext)) + { + return; + } + } + + Unapply(); + Apply(null, target, targetProperty, false, SetterSpecificity.FromBinding); + } + + void OnElementParentSet(object? sender, EventArgs e) + { + if (sender is not Element element || !weakTarget.TryGetTarget(out var target) || targetProperty is null) + { + return; + } + + if (element.Parent is null) + { + // Remove anything further up in the chain + // than the element with the null parent + var index = FindAncestryIndex(element); + + if (index is null) + { + Unapply(); + return; + } + + if (index + 1 < ancestryChain.Count) + { + ClearAncestryChangeSubscriptions(index.Value + 1); + } + + // Force the binding expression to resolve to null + // for now, until someone in the chain gets a new + // non-null parent. + ApplyCore(null, target, targetProperty, false, specificity ?? default); + } + else + { + Unapply(); + Apply(null, target, targetProperty, false, specificity ?? default); } } @@ -216,10 +373,7 @@ public PropertyChangedProxy(Func partGetter, string propertyNa public INotifyPropertyChanged? Part { - get - { - return Listener?.TryGetSource(out var target) is true ? target : null; - } + get => Listener?.TryGetSource(out var target) is true ? target : null; set { //Already subscribed diff --git a/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs b/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs index 3b50a06b..3274f82a 100644 --- a/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs @@ -188,7 +188,7 @@ public static TVisualElement Style(this TVisualElement element, /// This element to add the to. /// The s to add. /// The supplied with the supplied added. - public static TVisualElement Behaviors(this TVisualElement element, params Behavior[] behaviors) where TVisualElement : VisualElement + public static TVisualElement Behaviors(this TVisualElement element, params List behaviors) where TVisualElement : VisualElement { foreach (var behavior in behaviors) { From 5c0af699493d4f56843687c306e3fcfddf0599cf Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:33:54 -0700 Subject: [PATCH 03/59] Update BindableObjectMultiBindExtensionsTests.cs --- .../BindableObjectMultiBindExtensionsTests.cs | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs index e4d9c6d1..a3fe4df6 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs @@ -7,7 +7,6 @@ namespace CommunityToolkit.Maui.Markup.UnitTests; [TestFixture] class BindableObjectMultiBindExtensionsTests : BaseMarkupTestFixture { - ViewModel? viewModel; List? testBindings; List? testConvertValues; @@ -16,17 +15,17 @@ public override void Setup() { base.Setup(); - viewModel = new ViewModel(); + new ViewModel(); testBindings = [ - new Binding(nameof(viewModel.Text)), - new Binding(nameof(viewModel.Id)), + BindingBase.Create((ViewModel vm) => vm.Text), + BindingBase.Create((ViewModel vm) => vm.Id), - new Binding(nameof(viewModel.IsDone)), - new Binding(nameof(viewModel.Fraction)), + BindingBase.Create((ViewModel vm) => vm.IsDone), + BindingBase.Create((ViewModel vm) => vm.Fraction), - new Binding(nameof(viewModel.Count)) + BindingBase.Create((ViewModel vm) => vm.Count) ]; testConvertValues = @@ -44,7 +43,6 @@ public override void Setup() [TearDown] public override void TearDown() { - viewModel = null; testBindings = null; testConvertValues = null; base.TearDown(); @@ -238,24 +236,24 @@ public void BindSpecifiedPropertyWith3BindingsAndInlineConvertAndParameter(bool if (testConvert && testConvertBack) { label.Bind( - Label.TextProperty, - testBindings[0], testBindings[1], testBindings[2], - ((string? text, Guid id, bool isDone) v, int? parameter) => - { - ArgumentNullException.ThrowIfNull(parameter); - - return Format(parameter.Value, v.text, v.id, v.isDone); - }, - (string? formatted, int? parameter) => - { - ArgumentNullException.ThrowIfNull(parameter); - ArgumentNullException.ThrowIfNull(formatted); - - var unformattedResult = Unformat(parameter.Value, formatted); - return (unformattedResult.Text ?? throw new NullReferenceException(), unformattedResult.Id, unformattedResult.IsDone); - }, - converterParameter: 2 - ); + Label.TextProperty, + testBindings[0], testBindings[1], testBindings[2], + ((string? text, Guid id, bool isDone) v, int? parameter) => + { + ArgumentNullException.ThrowIfNull(parameter); + + return Format(parameter.Value, v.text, v.id, v.isDone); + }, + (string? formatted, int? parameter) => + { + ArgumentNullException.ThrowIfNull(parameter); + ArgumentNullException.ThrowIfNull(formatted); + + var unformattedResult = Unformat(parameter.Value, formatted); + return (unformattedResult.Text ?? throw new NullReferenceException(), unformattedResult.Id, unformattedResult.IsDone); + }, + converterParameter: 2 + ); } else if (testConvert && !testConvertBack) { @@ -435,7 +433,9 @@ public void BindSpecifiedPropertyWithMultipleBindings(bool testConvert, bool tes } var converter = new FuncMultiConverter(convert, convertBack); - var label = new Label { }.Bind(Label.TextProperty, GetTestBindings(5), converter); + var label = new Label + { + }.Bind(Label.TextProperty, GetTestBindings(5), converter); AssertLabelTextMultiBound(label, 5, testConvert, testConvertBack, converter: converter); } @@ -449,7 +449,7 @@ public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConve if (testConvert) { convert = (object[] v, int parameter) => Format(parameter, - v[0], v[1], v[2], v[3], v[4]); + v[0], v[1], v[2], v[3], v[4]); } Func? convertBack = null; @@ -465,7 +465,9 @@ public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConve } var converter = new FuncMultiConverter(convert, convertBack); - var label = new Label { }.Bind(Label.TextProperty, GetTestBindings(5), converter, 2); + var label = new Label + { + }.Bind(Label.TextProperty, GetTestBindings(5), converter, 2); AssertLabelTextMultiBound(label, 5, testConvert, testConvertBack, 2, converter); } @@ -480,7 +482,7 @@ public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConve static string Format(int parameter, params List values) { var stringBuilder = new StringBuilder(); - + stringBuilder.Append($"'{PrefixDots(values[0], parameter)}'"); for (var i = 1; i < values.Count; i++) { From 7386996b72e48dc471ca07c3edddc92ed2ac9a41 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:41:30 -0700 Subject: [PATCH 04/59] Update azure-pipelines.yml --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8f7c66a5..cf3df351 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ variables: PreviewNumber: $[counter(variables['CurrentSemanticVersionBase'], 1001)] CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)' NugetPackageVersion: '$(CurrentSemanticVersion)' - NET_VERSION: '9.0.x' + NET_VERSION: '9.0.100-rc.1.24452.12' RunPoliCheck: false PathToLibrarySolution: 'src/CommunityToolkit.Maui.Markup.sln' PathToSamplesSolution: 'samples/CommunityToolkit.Maui.Markup.Sample.sln' From 49a36703035dde124e28da90d5e341571a15dc7f Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:43:51 -0700 Subject: [PATCH 05/59] Update azure-pipelines.yml --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cf3df351..72882b4f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ variables: PreviewNumber: $[counter(variables['CurrentSemanticVersionBase'], 1001)] CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)' NugetPackageVersion: '$(CurrentSemanticVersion)' - NET_VERSION: '9.0.100-rc.1.24452.12' + NET_VERSION: '9.0.0-rc.2.24473.5' RunPoliCheck: false PathToLibrarySolution: 'src/CommunityToolkit.Maui.Markup.sln' PathToSamplesSolution: 'samples/CommunityToolkit.Maui.Markup.Sample.sln' From 438947e24ff8b5ae44a7aee3cc002995a997c82a Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:53:09 -0700 Subject: [PATCH 06/59] Remove `string` path bindings --- .../Benchmarks/ExecuteBindingsBase.cs | 11 +++---- .../BindableObjectMultiBindExtensionsTests.cs | 30 ++++++++----------- .../PaddingElementExtensionsTests.cs | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs index ac2bd519..216a9ada 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs @@ -12,14 +12,15 @@ protected ExecuteBindingsBase() { BindingContext = DefaultBindingsLabelViewModel }; - DefaultBindingsLabel.SetBinding(Label.TextProperty, nameof(LabelViewModel.Text), mode: BindingMode.TwoWay); - DefaultBindingsLabel.SetBinding(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); + + DefaultBindingsLabel.SetBinding(Label.TextProperty, BindingBase.Create((LabelViewModel vm) => vm.Text, mode: BindingMode.TwoWay)); + DefaultBindingsLabel.SetBinding(Label.TextColorProperty, BindingBase.Create((LabelViewModel vm) => vm.TextColor, mode: BindingMode.TwoWay)); DefaultBindingsLabel.EnableAnimations(); TypedMarkupBindingsLabel = new Label - { - BindingContext = TypedMarkupBindingsLabelViewModel - }.Bind(Label.TextProperty, + { + BindingContext = TypedMarkupBindingsLabelViewModel + }.Bind(Label.TextProperty, getter: (LabelViewModel vm) => vm.Text, setter: (vm, text) => vm.Text = text, mode: BindingMode.TwoWay) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs index a3fe4df6..c420e306 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs @@ -15,17 +15,13 @@ public override void Setup() { base.Setup(); - new ViewModel(); - testBindings = [ - BindingBase.Create((ViewModel vm) => vm.Text), - BindingBase.Create((ViewModel vm) => vm.Id), - - BindingBase.Create((ViewModel vm) => vm.IsDone), - BindingBase.Create((ViewModel vm) => vm.Fraction), - - BindingBase.Create((ViewModel vm) => vm.Count) + BindingBase.Create(static (MultiBindViewModel vm) => vm.Text), + BindingBase.Create(static (MultiBindViewModel vm) => vm.Id), + BindingBase.Create(static (MultiBindViewModel vm) => vm.IsDone), + BindingBase.Create(static (MultiBindViewModel vm) => vm.Fraction), + BindingBase.Create(static (MultiBindViewModel vm) => vm.Count) ]; testConvertValues = @@ -533,17 +529,17 @@ void AssertLabelTextMultiBound(Label label, int nBindings, bool testConvert, boo assertConverterInstanceIsAnyNotNull: converter is null, assertConvert: c => c.AssertConvert(values, expected, twoWay: testConvert && testConvertBack, backOnly: !testConvert && testConvertBack)); } +} - class ViewModel - { - public Guid Id { get; set; } +sealed class MultiBindViewModel +{ + public Guid Id { get; set; } - public string Text { get; set; } = string.Empty; + public string Text { get; set; } = string.Empty; - public bool IsDone { get; set; } + public bool IsDone { get; set; } - public double Fraction { get; set; } + public double Fraction { get; set; } - public int Count { get; set; } - } + public int Count { get; set; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs index d1d2314f..52afb4a3 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs @@ -3,7 +3,7 @@ namespace CommunityToolkit.Maui.Markup.UnitTests; [TestFixture(typeof(Button))] -[TestFixture(typeof(Frame))] +[TestFixture(typeof(Border))] [TestFixture(typeof(ImageButton))] [TestFixture(typeof(Label))] [TestFixture(typeof(Page))] From 6c86ddb57362a142bc893f58fbfa98d5aca77b0a Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:55:03 -0700 Subject: [PATCH 07/59] Update azure-pipelines.yml --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 72882b4f..3e018746 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ variables: PreviewNumber: $[counter(variables['CurrentSemanticVersionBase'], 1001)] CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)' NugetPackageVersion: '$(CurrentSemanticVersion)' - NET_VERSION: '9.0.0-rc.2.24473.5' + NET_VERSION: '9.0.100-rc.2.24474.11' RunPoliCheck: false PathToLibrarySolution: 'src/CommunityToolkit.Maui.Markup.sln' PathToSamplesSolution: 'samples/CommunityToolkit.Maui.Markup.Sample.sln' From 0eecf212770cba17c6bfa3580c34fb57a63cfda9 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:01:36 -0700 Subject: [PATCH 08/59] Update BindingHelpers.cs --- .../BindingHelpers.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs index 4dcd3426..03d00b63 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs @@ -214,17 +214,8 @@ internal static void AssertBindingExists( { getContextMethodInfo ??= typeof(BindableObject).GetMethod("GetContext", BindingFlags.NonPublic | BindingFlags.Instance); - var context = getContextMethodInfo?.Invoke(bindable, [property]); - if (context is null) - { - return null; - } - - bindingsFieldInfo ??= context.GetType().GetField("Bindings"); - - var bindingsList = bindingsFieldInfo?.GetValue(context) as SortedList; - - return (TBinding?)bindingsList?.First().Value; + var context = (BindableObject.BindablePropertyContext?)getContextMethodInfo?.Invoke(bindable, [property]); + return (TBinding?)context?.Bindings.GetValue(); } internal static IValueConverter AssertConvert(this IValueConverter converter, TValue value, object? parameter, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo? culture = null) From ea5a7870f8456326f7056f352303c8fb61b53256 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:22:10 -0700 Subject: [PATCH 09/59] Fix TypedBinding --- src/CommunityToolkit.Maui.Markup/TypedBinding.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup/TypedBinding.cs b/src/CommunityToolkit.Maui.Markup/TypedBinding.cs index 8e410d66..264f4528 100644 --- a/src/CommunityToolkit.Maui.Markup/TypedBinding.cs +++ b/src/CommunityToolkit.Maui.Markup/TypedBinding.cs @@ -57,11 +57,6 @@ internal override void Apply(bool fromTarget = false) [MemberNotNull(nameof(targetProperty)), MemberNotNull(nameof(specificity))] internal override void Apply(object? context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged, SetterSpecificity specificity) { - if (context is not TSource sourceContext) - { - throw new InvalidOperationException($"Invalid context. Expected type {typeof(TSource).FullName}, but received {context?.GetType()?.FullName ?? "null"}"); - } - this.targetProperty = targetProperty; this.specificity = specificity; var source = Source ?? Context ?? context; @@ -84,10 +79,10 @@ internal override void Apply(object? context, BindableObject bindObj, BindablePr throw new InvalidOperationException("Binding instances cannot be reused"); } - weakSource.SetTarget(sourceContext); + weakSource.SetTarget(source as TSource); weakTarget.SetTarget(bindObj); - ApplyCore(sourceContext, bindObj, targetProperty, false, specificity); + ApplyCore(source as TSource, bindObj, targetProperty, false, specificity); } internal override BindingBase Clone() From 9bcfd435d152c5ebf9d0dba2a173801d7c44ee7f Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:24:50 -0700 Subject: [PATCH 10/59] Update GesturesExtensionsTests.cs --- .../GesturesExtensionsTests.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs index da7345a9..f5c9bbd7 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs @@ -367,22 +367,20 @@ public void BindSwipeGesturePositionalParametersWithNestedBindings() } [Test] - [Obsolete] public void MultipleGestureBindings() { var gestureElement = new TGestureElement { BindingContext = new ViewModel() - }.BindSwipeGesture(static (ViewModel vm) => vm.SetGuidCommand) - .BindTapGesture(static (ViewModel vm) => vm.SetGuidCommand) + } + .BindSwipeGesture(static (ViewModel vm) => vm.SetGuidCommand) .BindTapGesture(static (ViewModel vm) => vm.SetGuidCommand); Assert.Multiple(() => { - Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(3)); + Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(2)); Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); Assert.That(gestureElement.GestureRecognizers[1], Is.InstanceOf()); - Assert.That(gestureElement.GestureRecognizers[2], Is.InstanceOf()); }); } } From 5be6fb699433e32bf0a5de916be107bb87c8c281 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:27:33 -0700 Subject: [PATCH 11/59] Revert `Format` method --- .../BindableObjectMultiBindExtensionsTests.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs index c420e306..5f4b62cd 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs @@ -1,5 +1,4 @@ -using System.Text; -using CommunityToolkit.Maui.Markup.UnitTests.Base; +using CommunityToolkit.Maui.Markup.UnitTests.Base; using NUnit.Framework; namespace CommunityToolkit.Maui.Markup.UnitTests; @@ -429,9 +428,7 @@ public void BindSpecifiedPropertyWithMultipleBindings(bool testConvert, bool tes } var converter = new FuncMultiConverter(convert, convertBack); - var label = new Label - { - }.Bind(Label.TextProperty, GetTestBindings(5), converter); + var label = new Label().Bind(Label.TextProperty, GetTestBindings(5), converter); AssertLabelTextMultiBound(label, 5, testConvert, testConvertBack, converter: converter); } @@ -461,31 +458,27 @@ public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConve } var converter = new FuncMultiConverter(convert, convertBack); - var label = new Label - { - }.Bind(Label.TextProperty, GetTestBindings(5), converter, 2); + var label = new Label().Bind(Label.TextProperty, GetTestBindings(5), converter, 2); AssertLabelTextMultiBound(label, 5, testConvert, testConvertBack, 2, converter); } - List GetTestBindings(int count) => testBindings?.Take(count).ToList() ?? Enumerable.Empty().ToList(); + List GetTestBindings(int count) => testBindings?.Take(count).ToList() ?? []; - object[] GetTestConvertValues(int count) => testConvertValues?.Take(count).ToArray() ?? Array.Empty(); + object[] GetTestConvertValues(int count) => testConvertValues?.Take(count).ToArray() ?? []; static string PrefixDots(object? value, int count) => $"{new string('.', count)}{value}"; - static string RemoveDots(string text, int count) => text.Substring(count); + static string RemoveDots(string text, int count) => text[count..]; static string Format(int parameter, params List values) { - var stringBuilder = new StringBuilder(); - - stringBuilder.Append($"'{PrefixDots(values[0], parameter)}'"); + var formatted = $"'{PrefixDots(values[0], parameter)}'"; for (var i = 1; i < values.Count; i++) { - stringBuilder.Append($", '{values[i]}'"); + formatted += $", '{values[i]}'"; } - return stringBuilder.ToString(); + return formatted; } static (string? Text, Guid Id, bool IsDone, double Fraction, int Count) Unformat(int parameter, string formatted) From 1e2a9caa118e52313417b2711b37dce2526a4616 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:34:54 -0700 Subject: [PATCH 12/59] Revert `Format(int, params object[])` --- .../BindableObjectMultiBindExtensionsTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs index 5f4b62cd..e5169041 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs @@ -470,10 +470,10 @@ public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConve static string RemoveDots(string text, int count) => text[count..]; - static string Format(int parameter, params List values) + static string Format(int parameter, params object?[] values) { var formatted = $"'{PrefixDots(values[0], parameter)}'"; - for (var i = 1; i < values.Count; i++) + for (var i = 1; i < values.Length; i++) { formatted += $", '{values[i]}'"; } From 66b64fe7970286149004ee749050b464a4d4d758 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:59:22 -0700 Subject: [PATCH 13/59] Allow unsafe code blocks in sample --- .../CommunityToolkit.Maui.Markup.Sample.csproj | 3 ++- .../BindingHelpers.cs | 1 - .../FuncConverterTests.cs | 10 ---------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index d0a5cd1e..ea3af7d3 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -1,4 +1,4 @@ - + $(NetVersion)-android;$(NetVersion)-ios;$(NetVersion)-maccatalyst @@ -28,6 +28,7 @@ 10.0.17763.0 10.0.17763.0 + True 10.0.19041.41 diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs index 03d00b63..3913bfed 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs @@ -7,7 +7,6 @@ namespace CommunityToolkit.Maui.Markup.UnitTests; static class BindingHelpers { static MethodInfo? getContextMethodInfo; - static FieldInfo? bindingsFieldInfo; internal static void AssertBindingExists( BindableObject bindable, diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/FuncConverterTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/FuncConverterTests.cs index 89b261d9..0bd26658 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/FuncConverterTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/FuncConverterTests.cs @@ -34,11 +34,6 @@ public void TwoWayMultiWithParamAndCulture() text?.Length > 0 ? text[0] : '\0', (text?.Length ?? 0) - (addOne ? 1 : 0) ]; - return - [ - text?.Length > 0 ? text[0] : '\0', - (text?.Length ?? 0) - (addOne ? 1 : 0) - ]; }) .AssertConvert(['a', 2], true, "aaa", twoWay: true, culture: expectedCulture) .AssertConvert(['b', 4], false, "bbbb", twoWay: true, culture: expectedCulture); @@ -83,11 +78,6 @@ public void TwoWayMultiWithParam() text?.Length > 0 ? text[0] : '\0', (text?.Length ?? 0) - (addOne ? 1 : 0) ]; - return - [ - text?.Length > 0 ? text[0] : '\0', - (text?.Length ?? 0) - (addOne ? 1 : 0) - ]; }) .AssertConvert(['a', 2], true, "aaa", twoWay: true) .AssertConvert(['b', 4], false, "bbbb", twoWay: true); From 6293ac09b7fb88a9546025d8846b9401795473f1 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:10:59 -0700 Subject: [PATCH 14/59] Enable AOT on Android + Windows Sample --- .../CommunityToolkit.Maui.Markup.Sample.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index ea3af7d3..5f14409c 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -33,12 +33,8 @@ + AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'tizen'"> - - true From b20dfc8dac07ceb3d552537560fe135506a3ea0b Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:18:40 -0700 Subject: [PATCH 15/59] Update CommunityToolkit.Maui.Markup.Sample.csproj --- .../CommunityToolkit.Maui.Markup.Sample.csproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index 5f14409c..f284f4df 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -33,8 +33,10 @@ + AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'tizen' + AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'"> + true From b3b3b9a0f6fc7f7baf42cdbeb4a4f95bcd08fc2d Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:51:59 -0700 Subject: [PATCH 16/59] Revert "Update CommunityToolkit.Maui.Markup.Sample.csproj" This reverts commit b20dfc8dac07ceb3d552537560fe135506a3ea0b. --- .../CommunityToolkit.Maui.Markup.Sample.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index f284f4df..5f14409c 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -33,10 +33,8 @@ + AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'tizen'"> - true From d6305079b3ddc49faa07e52918756454f910b2ac Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:52:18 -0700 Subject: [PATCH 17/59] Revert "Enable AOT on Android + Windows Sample" This reverts commit 6293ac09b7fb88a9546025d8846b9401795473f1. --- .../CommunityToolkit.Maui.Markup.Sample.csproj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index 5f14409c..ea3af7d3 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -33,8 +33,12 @@ + AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'tizen' + AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'android' + AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'"> + + true From bf733d2965a3c40e35c9bf56c2a8d277137b9440 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:46:21 -0700 Subject: [PATCH 18/59] Use Syntax Highlighting for code blocks --- .../SourceGenerators/TextAlignmentExtensionsGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs index 606d6b65..9eef6c40 100644 --- a/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs +++ b/src/CommunityToolkit.Maui.Markup.SourceGenerators/SourceGenerators/TextAlignmentExtensionsGenerator.cs @@ -89,7 +89,7 @@ static void Execute(SourceProductionContext context, [NotNull] TextAlignmentClas var genericTypeParameters = GetGenericTypeParametersDeclarationString(textAlignmentClassMetadata.GenericArguments); var genericArguments = GetGenericArgumentsString(textAlignmentClassMetadata.GenericArguments); - var source = $$""" + var source = /* language=C#-test */$$""" // // See: CommunityToolkit.Maui.Markup.SourceGenerators.TextAlignmentGenerator From 749fe98290d7f14cc850869a58b58c08b818b47c Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:51:28 -0700 Subject: [PATCH 19/59] Update `` --- Directory.Build.props | 8 ++++++++ .../Views/News/StoryDataTemplate.cs | 7 +------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index d8322ec3..24f3ee52 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,6 +15,8 @@ false true true + true + true false @@ -42,6 +44,10 @@ CS1712: Type parameter has no matching typeparam tag in the XML comment CS1723: XML comment has cref attribute that refers to a type parameter CS1734: XML comment has a paramref tag, but there is no parameter by that name + CsWinRT1028: Class implements WinRT interfaces but isn't marked partial + CsWinRT1030: Class implements WinRT interfaces that require unsafe code + XC0045: Binding: Property not found + XC0103: Consider attributing the markup extension with [RequireService] or [AcceptEmptyServiceProvider] if it doesn't require any NU1900 Error communicating with package source, while getting vulnerability information. NU1901 Package with low severity detected NU1902 Package with moderate severity detected @@ -54,6 +60,8 @@ nullable, CS0419,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1587,CS1589,CS1590,CS1591,CS1592,CS1598,CS1658,CS1710,CS1711,CS1712,CS1723,CS1734, + CsWinRT1028,CsWinRT1030, + XC0045,XC0103, NU1900,NU1901,NU1902,NU1903,NU1904,NU1905, NUnit1001,NUnit1002,NUnit1003,NUnit1004,NUnit1005,NUnit1006,NUnit1007,NUnit1008,NUnit1009,NUnit1010,NUnit1011,NUnit1012,NUnit1013,NUnit1014,NUnit1015,NUnit1016,NUnit1017,NUnit1018,NUnit1019,NUnit1020,NUnit1021,NUnit1022,NUnit1023,NUnit1024,NUnit1025,NUnit1026,NUnit1027,NUnit1028,NUnit1029,NUnit1030,NUnit1031,NUnit1032,NUnit1033, NUnit2001,NUnit2002,NUnit2003,NUnit2004,NUnit2005,NUnit2006,NUnit2007,NUnit2008,NUnit2009,NUnit2010,NUnit2011,NUnit2012,NUnit2013,NUnit2014,NUnit2015,NUnit2016,NUnit2017,NUnit2018,NUnit2019,NUnit2020,NUnit2021,NUnit2022,NUnit2023,NUnit2024,NUnit2025,NUnit2026,NUnit2027,NUnit2028,NUnit2029,NUnit2030,NUnit2031,NUnit2032,NUnit2033,NUnit2034,NUnit2035,NUnit2036,NUnit2037,NUnit2038,NUnit2039,NUnit2040,NUnit2041,NUnit2042,NUnit2043,NUnit2044,NUnit2045,NUnit2046,NUnit2047,NUnit2048,NUnit2049,NUnit2050, diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Views/News/StoryDataTemplate.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Views/News/StoryDataTemplate.cs index c7a3b262..f6c91ee6 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Views/News/StoryDataTemplate.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Views/News/StoryDataTemplate.cs @@ -2,13 +2,8 @@ namespace CommunityToolkit.Maui.Markup.Sample.Views.News; -class StoryDataTemplate : DataTemplate +class StoryDataTemplate() : DataTemplate(CreateGrid) { - public StoryDataTemplate() : base(CreateGrid) - { - - } - static Grid CreateGrid() => new() { RowSpacing = 1, From e70a347785802ae3bb343d4b7a540aa391467577 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:06:32 -0700 Subject: [PATCH 20/59] Resolve WinRt errors --- Directory.Build.props | 3 ++- samples/CommunityToolkit.Maui.Markup.Sample/App.cs | 2 +- .../AppShell.cs | 2 +- .../CommunityToolkit.Maui.Markup.Sample.csproj | 14 +++++++------- .../Pages/NewsDetailPage.cs | 2 +- .../Pages/NewsPage.cs | 2 +- .../Pages/SettingsPage.cs | 2 +- .../Resources/AppStyles.cs | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 24f3ee52..c1d4b127 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -24,6 +24,7 @@ nullable, - CS0419,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1587,CS1589,CS1590,CS1591,CS1592,CS1598,CS1658,CS1710,CS1711,CS1712,CS1723,CS1734, + CS0419,CS0618,CS1570,CS1571,CS1572,CS1573,CS1574,CS1580,CS1581,CS1584,CS1587,CS1589,CS1590,CS1591,CS1592,CS1598,CS1658,CS1710,CS1711,CS1712,CS1723,CS1734, CsWinRT1028,CsWinRT1030, XC0045,XC0103, NU1900,NU1901,NU1902,NU1903,NU1904,NU1905, diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/App.cs b/samples/CommunityToolkit.Maui.Markup.Sample/App.cs index eecc138f..2670cf4c 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/App.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/App.cs @@ -1,6 +1,6 @@ namespace CommunityToolkit.Maui.Markup.Sample; -class App : Application +partial class App : Application { readonly AppShell appShell; diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs b/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs index bb46b1d2..b5f05a29 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs @@ -2,7 +2,7 @@ namespace CommunityToolkit.Maui.Markup.Sample; -class AppShell : Shell +partial class AppShell : Shell { static readonly IReadOnlyDictionary pageRouteMappingDictionary = new Dictionary( [ diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index ea3af7d3..d1515eed 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -28,8 +28,11 @@ 10.0.17763.0 10.0.17763.0 - True + true 10.0.19041.41 + + + CsWinRT1028 - + + @@ -71,10 +74,7 @@ - + diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsDetailPage.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsDetailPage.cs index c0bd9d6c..be3c4b62 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsDetailPage.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsDetailPage.cs @@ -1,7 +1,7 @@ using Microsoft.Maui.Layouts; namespace CommunityToolkit.Maui.Markup.Sample.Pages; -sealed class NewsDetailPage : BaseContentPage +sealed partial class NewsDetailPage : BaseContentPage { public NewsDetailPage(NewsDetailViewModel newsDetailViewModel) : base(newsDetailViewModel, newsDetailViewModel.Title) { diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsPage.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsPage.cs index 8e44ee60..b83921cc 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsPage.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/NewsPage.cs @@ -1,6 +1,6 @@ namespace CommunityToolkit.Maui.Markup.Sample.Pages; -sealed class NewsPage : BaseContentPage +sealed partial class NewsPage : BaseContentPage { readonly IDispatcher dispatcher; readonly RefreshView refreshView; diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs index 7ed991f8..261c2247 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs @@ -2,7 +2,7 @@ namespace CommunityToolkit.Maui.Markup.Sample.Pages; -sealed class SettingsPage : BaseContentPage +sealed partial class SettingsPage : BaseContentPage { public SettingsPage(SettingsViewModel settingsViewModel) : base(settingsViewModel, "Settings") { diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Resources/AppStyles.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Resources/AppStyles.cs index c698ba8a..622ef186 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Resources/AppStyles.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Resources/AppStyles.cs @@ -1,6 +1,6 @@ namespace CommunityToolkit.Maui.Markup.Sample.Resources; -public class AppStyles : ResourceDictionary +public partial class AppStyles : ResourceDictionary { static readonly Color browserNavigationBarTextColorDark = Colors.White, browserNavigationBarTextColorLight = Color.FromArgb("3F3F3F"), From 5307712c713c7731cbd47ea6478abcb5563b297d Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:08:46 -0700 Subject: [PATCH 21/59] Remove `Application.Current.MainPage` --- .../ApplicationTestHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs index b5454e7f..4b9d2946 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs @@ -28,7 +28,7 @@ public static void PerformAppThemeBasedTest( ArgumentNullException.ThrowIfNull(Application.Current); - Application.Current.MainPage = new ContentPage + Application.Current.Windows[0].Page = new ContentPage { Content = bindable }; From 5ba730c20faf2fc7427c85378f4cf2929f7af6e4 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:30:15 -0700 Subject: [PATCH 22/59] Create BindableObjectExtensionsTests.cs --- .../BindableObjectExtensionsTests.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs new file mode 100644 index 00000000..ec9b45e1 --- /dev/null +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs @@ -0,0 +1,81 @@ +using System.Windows.Input; +using BindableObjectViews; +using CommunityToolkit.Maui.Markup.UnitTests.Base; +using NUnit.Framework; +namespace CommunityToolkit.Maui.Markup.UnitTests +{ + [TestFixture] + class BindableObjectExtensionsTests : BaseMarkupTestFixture + { + ViewModel? viewModel; + + [SetUp] + public override void Setup() + { + base.Setup(); + viewModel = new ViewModel(); + } + + [TearDown] + public override void TearDown() + { + viewModel = null; + base.TearDown(); + } + + [TestCase(AppTheme.Light)] + [TestCase(AppTheme.Dark)] + [TestCase(AppTheme.Unspecified)] + public void AppThemeColorBindingCorrectlySetsPropertyToChangeBasedOnApplicationsAppTheme(AppTheme appTheme) + { + var expectedColor = appTheme == AppTheme.Dark ? Colors.Orange : Colors.Purple; + + ApplicationTestHelpers.PerformAppThemeBasedTest( + appTheme, + () => new Label().AppThemeColorBinding(Label.TextColorProperty, Colors.Purple, Colors.Orange), + label => Assert.That(label.TextColor, Is.EqualTo(expectedColor))); + } + + [TestCase(AppTheme.Light)] + [TestCase(AppTheme.Dark)] + [TestCase(AppTheme.Unspecified)] + public void AppThemeBindingCorrectlySetsPropertyToChangeBasedOnApplicationsAppTheme(AppTheme appTheme) + { + const string dark = nameof(AppTheme.Dark); + const string light = nameof(AppTheme.Light); + + var expectedText = appTheme == AppTheme.Dark ? dark : light; + + ApplicationTestHelpers.PerformAppThemeBasedTest( + appTheme, + () => new Label().AppThemeBinding(Label.TextProperty, light, dark), + label => Assert.That(label.Text, Is.EqualTo(expectedText))); + } + + sealed class ViewModel + { + public Guid Id { get; set; } + + public ICommand? Command { get; set; } + + public string? Text { get; set; } + + public Color TextColor { get; set; } = Colors.Transparent; + + public bool IsRed { get; set; } + + public double HeightRequest { get; set; } + } + } +} + +namespace BindableObjectViews // This namespace simulates derived controls defined in a separate app, for use in the tests in this file only +{ + class DerivedFromLabel : Label + { + } + + class DerivedFromTextCell : TextCell + { + } +} \ No newline at end of file From 1dd391485791e3c32d4f22a103b0fa8be3c8700c Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:53:15 -0700 Subject: [PATCH 23/59] Fix ElementExtensionsTests and DynamicResourceHandlerTests --- .../ApplicationTestHelpers.cs | 18 +++---- .../Base/BaseTestFixture.cs | 50 ++++++++++++++++++- .../DynamicResourceHandlerTests.cs | 15 +++++- .../ElementExtensionsTests.cs | 10 ++++ .../Mocks/HandlerContextStub.cs | 18 +++++++ .../Mocks/MockApplication.cs | 10 ++++ .../Mocks/MockShell.cs | 25 ++++++++++ 7 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/HandlerContextStub.cs create mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockShell.cs diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs index 4b9d2946..18d7e7b3 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/ApplicationTestHelpers.cs @@ -16,22 +16,16 @@ public static void PerformAppThemeBasedTest( { try { - var appBuilder = MauiApp.CreateBuilder() - .UseMauiCommunityToolkit() - .UseMauiApp(); - - var mauiApp = appBuilder.Build(); - - var application = mauiApp.Services.GetRequiredService(); - var bindable = setAppThemeValue(); ArgumentNullException.ThrowIfNull(Application.Current); - Application.Current.Windows[0].Page = new ContentPage - { - Content = bindable - }; + Application.Current.ActivateWindow(new Window(new MockShell([ + new ContentPage + { + Content = bindable + } + ]))); Application.Current.UserAppTheme = appTheme; diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseTestFixture.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseTestFixture.cs index 2fc6cf86..ac23072a 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseTestFixture.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseTestFixture.cs @@ -1,6 +1,7 @@ using System.Globalization; using CommunityToolkit.Maui.Markup.UnitTests.Mocks; using NUnit.Framework; + namespace CommunityToolkit.Maui.Markup.UnitTests.Base; abstract class BaseTestFixture @@ -8,12 +9,18 @@ abstract class BaseTestFixture CultureInfo? defaultCulture; CultureInfo? defaultUICulture; + protected BaseTestFixture() + { + CreateAndSetMockApplication(out var serviceProvider); + ServiceProvider = serviceProvider; + } + + [SetUp] public virtual void Setup() { defaultCulture = Thread.CurrentThread.CurrentCulture; defaultUICulture = Thread.CurrentThread.CurrentUICulture; - DispatcherProvider.SetCurrent(new MockDispatcherProvider()); } @@ -22,7 +29,46 @@ public virtual void TearDown() { Thread.CurrentThread.CurrentCulture = defaultCulture ?? throw new NullReferenceException(); Thread.CurrentThread.CurrentUICulture = defaultUICulture ?? throw new NullReferenceException(); - DispatcherProvider.SetCurrent(null); } + + protected IServiceProvider ServiceProvider { get; } + + protected static TElementHandler CreateElementHandler(IElement view, bool hasMauiContext = true) + where TElementHandler : IElementHandler, new() + { + var mockElementHandler = new TElementHandler(); + mockElementHandler.SetVirtualView(view); + if (hasMauiContext) + { + mockElementHandler.SetMauiContext(Application.Current?.Handler?.MauiContext ?? throw new NullReferenceException()); + } + + return mockElementHandler; + } + + protected static TViewHandler CreateViewHandler(IView view, bool hasMauiContext = true) + where TViewHandler : IViewHandler, new() + { + var mockViewHandler = new TViewHandler(); + mockViewHandler.SetVirtualView(view); + if (hasMauiContext) + { + mockViewHandler.SetMauiContext(Application.Current?.Handler?.MauiContext ?? throw new NullReferenceException()); + } + + return mockViewHandler; + } + + static void CreateAndSetMockApplication(out IServiceProvider serviceProvider) + { + var appBuilder = MauiApp.CreateBuilder().UseMauiApp().UseMauiCommunityToolkit(); + appBuilder.Services.AddSingleton(_ => new MockDispatcherProvider().GetForCurrentThread()); + var mauiApp = appBuilder.Build(); + var application = mauiApp.Services.GetRequiredService(); + serviceProvider = mauiApp.Services; + IPlatformApplication.Current = (IPlatformApplication)application; + application.Handler = new ApplicationHandlerStub(); + application.Handler.SetMauiContext(new HandlersContextStub(mauiApp.Services)); + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/DynamicResourceHandlerTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/DynamicResourceHandlerTests.cs index 6ac059d6..38cf5429 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/DynamicResourceHandlerTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/DynamicResourceHandlerTests.cs @@ -1,7 +1,9 @@ -using NUnit.Framework; +using CommunityToolkit.Maui.Markup.UnitTests.Base; +using CommunityToolkit.Maui.Markup.UnitTests.Mocks; +using NUnit.Framework; namespace CommunityToolkit.Maui.Markup.UnitTests; -public class DynamicResourceHandlerTests +class DynamicResourceHandlerTests : BaseTestFixture { [Test] public void DynamicResource() @@ -19,6 +21,15 @@ public void DynamicResource() static Label AssertDynamicResources() { var label = new Label { Resources = new ResourceDictionary { { "TextKey", "TextValue" }, { "ColorKey", Colors.Green } } }; + + ArgumentNullException.ThrowIfNull(Application.Current); + + Application.Current.ActivateWindow(new Window(new MockShell([ + new ContentPage + { + Content = label + } + ]))); Assert.Multiple(() => { diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs index b8e0795d..190b8c36 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Maui.Markup.UnitTests.Base; +using CommunityToolkit.Maui.Markup.UnitTests.Mocks; using NUnit.Framework; namespace CommunityToolkit.Maui.Markup.UnitTests; @@ -8,7 +9,16 @@ class ElementExtensionsTests : BaseMarkupTestFixture From 641b7efac39fa7be8738cb15ba1ceda84cbb1623 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:11:44 -0800 Subject: [PATCH 30/59] Add Support for BindingBase.Create --- ...ndableObjectExtensions.BindingBaseTests.cs | 467 ++++++++++++++++++ .../BindableObjectExtensionsTests.cs | 4 + .../BindableObjectExtensions.BindingBase.cs | 15 + 3 files changed, 486 insertions(+) create mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs create mode 100644 src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs new file mode 100644 index 00000000..d4dd43e1 --- /dev/null +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs @@ -0,0 +1,467 @@ +using System.ComponentModel; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Windows.Input; +using CommunityToolkit.Maui.Converters; +using CommunityToolkit.Maui.Markup.UnitTests.Base; +using NUnit.Framework; +namespace CommunityToolkit.Maui.Markup.UnitTests; + +class BindingBaseTests : BaseTestFixture +{ + static readonly IReadOnlyDictionary colors = typeof(Colors) + .GetFields(BindingFlags.Static | BindingFlags.Public) + .ToDictionary(c => c.Name, c => (Color)(c.GetValue(null) ?? throw new InvalidOperationException())); + + [Test] + public async Task ConfirmStringFormat() + { + var viewModel = new ViewModel(); + + const string stringFormat = "{0}%"; + bool didPropertyChangeFire = false; + TaskCompletionSource propertyChangedEventArgsTCS = new(); + + var label = new Label + { + BindingContext = viewModel + }.Bind(Label.TextProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.Percentage, stringFormat: stringFormat)); + + viewModel.PropertyChanged += HandlePropertyChanged; + + BindingHelpers.AssertTypedBindingExists(label, Label.TextProperty, BindingMode.Default, viewModel, stringFormat); + Assert.That(label.Text, Is.EqualTo(string.Format(stringFormat, ViewModel.DefaultPercentage))); + + label.Text = string.Format(stringFormat, 0.1); + + Assert.Multiple(() => + { + Assert.That(label.Text, Is.EqualTo("0.1%")); + Assert.That(viewModel.Percentage, Is.EqualTo(ViewModel.DefaultPercentage)); + }); + + viewModel.Percentage = 0.6; + var propertyName = await propertyChangedEventArgsTCS.Task; + + Assert.Multiple(() => + { + Assert.That(didPropertyChangeFire, Is.True); + Assert.That(propertyName, Is.EqualTo(nameof(ViewModel.Percentage))); + }); + + void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didPropertyChangeFire = true; + propertyChangedEventArgsTCS.SetResult(e.PropertyName); + } + } + + [Test] + public async Task ConfirmReadOnlyTypedBindings() + { + var viewModel = new ViewModel(); + + bool didViewModelPropertyChangeFire = false; + TaskCompletionSource viewModelPropertyChangedEventArgsTCS = new(); + + bool didLabelPropertyChangeFire = false; + TaskCompletionSource labelPropertyChangedEventArgsTCS = new(); + + var label = new Label + { + BindingContext = viewModel + }.Bind(Label.TextColorProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.TextColor)); + + viewModel.PropertyChanged += HandleViewModelPropertyChanged; + label.PropertyChanged += HandleLabelPropertyChanged; + + BindingHelpers.AssertTypedBindingExists(label, Label.TextColorProperty, BindingMode.Default, viewModel); + Assert.That(label.TextColor, Is.EqualTo(ViewModel.DefaultColor)); + + viewModel.TextColor = Colors.Green; + var viewModelPropertyName = await viewModelPropertyChangedEventArgsTCS.Task; + var labelPropertyName = await labelPropertyChangedEventArgsTCS.Task; + + Assert.Multiple(() => + { + Assert.That(didViewModelPropertyChangeFire, Is.True); + Assert.That(viewModelPropertyName, Is.EqualTo(nameof(ViewModel.TextColor))); + + Assert.That(didLabelPropertyChangeFire, Is.True); + Assert.That(labelPropertyName, Is.EqualTo(nameof(Label.TextColor))); + + Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); + Assert.That(label.GetValue(Label.TextColorProperty), Is.EqualTo(Colors.Green)); + }); + + label.TextColor = Colors.Red; + + Assert.Multiple(() => + { + Assert.That(label.TextColor, Is.EqualTo(Colors.Red)); + Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); + }); + + void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didViewModelPropertyChangeFire = true; + viewModelPropertyChangedEventArgsTCS.SetResult(e.PropertyName); + } + + void HandleLabelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didLabelPropertyChangeFire = true; + labelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); + } + } + + [Test] + public void ConfirmOneTimeBinding() + { + var viewModel = new ViewModel(); + + bool didLabelPropertyChangeFire = false; + bool didViewModelPropertyChangeFire = false; + + var label = new Label + { + BindingContext = viewModel + }.Bind(Label.TextColorProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.TextColor, BindingMode.OneTime)); + + viewModel.PropertyChanged += HandleViewModelPropertyChanged; + label.PropertyChanged += HandleLabelPropertyChanged; + + BindingHelpers.AssertTypedBindingExists(label, Label.TextColorProperty, BindingMode.OneTime, viewModel); + Assert.That(label.TextColor, Is.EqualTo(ViewModel.DefaultColor)); + + viewModel.TextColor = Colors.Green; + Assert.Multiple(() => + { + Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); + Assert.That(label.GetValue(Label.TextColorProperty), Is.EqualTo(ViewModel.DefaultColor)); + }); + + label.TextColor = Colors.Red; + + Assert.Multiple(() => + { + Assert.That(label.TextColor, Is.EqualTo(Colors.Red)); + Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); + + Assert.That(didViewModelPropertyChangeFire, Is.True); + Assert.That(didLabelPropertyChangeFire, Is.True); + }); + + void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didViewModelPropertyChangeFire = true; + } + + void HandleLabelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didLabelPropertyChangeFire = true; + } + } + + [Test] + public async Task ConfirmReadWriteTypedBinding() + { + var viewModel = new ViewModel(); + + bool didViewModelPropertyChangeFire = false; + int viewModelPropertyChangedEventCount = 0; + TaskCompletionSource viewModelPropertyChangedEventArgsTCS = new(); + + bool didSliderPropertyChangeFire = false; + int sliderPropertyChangedEventCount = 0; + TaskCompletionSource sliderPropertyChangedEventArgsTCS = new(); + + var slider = new Slider + { + BindingContext = viewModel + }.Bind(Slider.ValueProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.Percentage)); + + slider.PropertyChanged += HandleSliderPropertyChanged; + viewModel.PropertyChanged += HandleViewModelPropertyChanged; + + BindingHelpers.AssertTypedBindingExists(slider, Slider.ValueProperty, BindingMode.Default, viewModel); + Assert.That(slider.Value, Is.EqualTo(ViewModel.DefaultPercentage)); + + slider.Value = 1; + + Assert.Multiple(() => + { + Assert.That(slider.Value, Is.EqualTo(1)); + Assert.That(viewModel.Percentage, Is.EqualTo(1)); + }); + + viewModel.Percentage = 0.6; + var viewModelPropertyName = await viewModelPropertyChangedEventArgsTCS.Task; + var sliderPropertyName = await sliderPropertyChangedEventArgsTCS.Task; + + Assert.Multiple(() => + { + Assert.That(didViewModelPropertyChangeFire, Is.True); + Assert.That(viewModelPropertyName, Is.EqualTo(nameof(ViewModel.Percentage))); + Assert.That(viewModelPropertyChangedEventCount, Is.EqualTo(2)); + + Assert.That(didSliderPropertyChangeFire, Is.True); + Assert.That(sliderPropertyName, Is.EqualTo(nameof(Slider.Value))); + Assert.That(sliderPropertyChangedEventCount, Is.EqualTo(2)); + + Assert.That(slider.Value, Is.EqualTo(0.6)); + Assert.That(viewModel.Percentage, Is.EqualTo(0.6)); + }); + + void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didViewModelPropertyChangeFire = true; + viewModelPropertyChangedEventCount++; + viewModelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); + } + + void HandleSliderPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didSliderPropertyChangeFire = true; + sliderPropertyChangedEventCount++; + sliderPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); + } + } + + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public void ValueSetOnOneWayWithNestedPathBinding(bool setContextFirst, bool isDefault) + { + var entry = new Entry(); + var bindingMode = isDefault ? BindingMode.Default : BindingMode.OneWay; + + var viewmodel = new NestedViewModel + { + Model = new NestedViewModel + { + Model = new NestedViewModel() + } + }; + + if (setContextFirst) + { + entry.BindingContext = viewmodel; + entry.Bind(Entry.TextColorProperty, BindingBase.Create( + static vm => vm.Model?.Model?.TextColor, + mode: bindingMode)); + } + else + { + entry.Bind(Entry.TextColorProperty, BindingBase.Create( + static vm => vm.Model?.Model?.TextColor, + mode: bindingMode)); + + entry.BindingContext = viewmodel; + } + + Assert.Multiple(() => + { + Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(ViewModel.DefaultColor)); + Assert.That(entry.GetValue(Entry.TextColorProperty), Is.EqualTo(ViewModel.DefaultColor)); + }); + + var textColor = Colors.Pink; + + viewmodel.Model.Model.TextColor = textColor; + + Assert.Multiple(() => + { + Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(textColor)); + Assert.That(entry.GetValue(Entry.TextColorProperty), Is.EqualTo(textColor)); + }); + } + + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public void ValueSetOnOneWayWithNestedPathBindingWithIValueConverter(bool setContextFirst, bool isDefault) + { + var label = new Label(); + var bindingMode = isDefault ? BindingMode.Default : BindingMode.OneWay; + + var viewmodel = new NestedViewModel + { + Model = new NestedViewModel + { + Model = new NestedViewModel() + } + }; + + var colorToHexRgbStringConverter = new ColorToHexRgbStringConverter(); + + if (setContextFirst) + { + label.BindingContext = viewmodel; + label.Bind( + Label.TextProperty, + BindingBase.Create(static (NestedViewModel vm) => vm.Model?.Model?.TextColor, + bindingMode, + converter: colorToHexRgbStringConverter)); + } + else + { + label.Bind( + Label.TextProperty, + BindingBase.Create(static (NestedViewModel vm) => vm.Model?.Model?.TextColor, + bindingMode, + converter: colorToHexRgbStringConverter)); + + label.BindingContext = viewmodel; + } + + Assert.Multiple(() => + { + Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(ViewModel.DefaultColor)); + Assert.That(label.GetValue(Label.TextProperty), Is.EqualTo(colorToHexRgbStringConverter.ConvertFrom(ViewModel.DefaultColor))); + }); + + var textColor = Colors.Pink; + + viewmodel.Model.Model.TextColor = textColor; + Assert.Multiple(() => + { + Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(textColor)); + Assert.That(label.GetValue(Label.TextProperty), Is.EqualTo(colorToHexRgbStringConverter.ConvertFrom(textColor))); + }); + } + + [Test] + public async Task ConfirmReadOnlyTypedBindingWithIValueConverter() + { + var viewModel = new ViewModel(); + + var colorToHexRgbStringConverter = new ColorToHexRgbStringConverter(); + var updatedTextColor = Colors.Pink; + + bool didViewModelPropertyChangeFire = false; + int viewModelPropertyChangedEventCount = 0; + TaskCompletionSource viewModelPropertyChangedEventArgsTCS = new(); + + bool didLabelPropertyChangeFire = false; + int labelPropertyChangedEventCount = 0; + TaskCompletionSource labelPropertyChangedEventArgsTCS = new(); + + var label = new Label + { + BindingContext = viewModel + }.Bind(Label.TextProperty, + BindingBase.Create(static (ViewModel viewModel) => viewModel.TextColor, + converter: colorToHexRgbStringConverter)); + + label.PropertyChanged += HandleLabelPropertyChanged; + viewModel.PropertyChanged += HandleViewModelPropertyChanged; + + BindingHelpers.AssertTypedBindingExists( + label, + Label.TextProperty, + BindingMode.Default, + viewModel, + expectedConverter: colorToHexRgbStringConverter); + + viewModel.TextColor = updatedTextColor; + var viewModelPropertyName = await viewModelPropertyChangedEventArgsTCS.Task; + var labelPropertyName = await labelPropertyChangedEventArgsTCS.Task; + + Assert.Multiple(() => + { + Assert.That(didViewModelPropertyChangeFire, Is.True); + Assert.That(viewModelPropertyName, Is.EqualTo(nameof(ViewModel.TextColor))); + Assert.That(viewModelPropertyChangedEventCount, Is.EqualTo(1)); + + Assert.That(didLabelPropertyChangeFire, Is.True); + Assert.That(labelPropertyName, Is.EqualTo(nameof(Label.Text))); + Assert.That(labelPropertyChangedEventCount, Is.EqualTo(1)); + Assert.That(colorToHexRgbStringConverter.ConvertFrom(updatedTextColor), Is.EqualTo(label.Text)); + }); + + void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didViewModelPropertyChangeFire = true; + viewModelPropertyChangedEventCount++; + viewModelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); + } + + void HandleLabelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + didLabelPropertyChangeFire = true; + labelPropertyChangedEventCount++; + labelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); + } + } + + internal class ViewModel : INotifyPropertyChanged + { + public const double DefaultPercentage = 0.5; + public const double DefaultHeightRequest = 500; + + double percentage = DefaultPercentage, heightRequest = DefaultHeightRequest; + Color textColor = DefaultColor; + + public ViewModel() + { + Command = new Command(() => TextColor = colors.Skip(Random.Shared.Next(colors.Keys.Count())).First().Value); + } + + public static Color DefaultColor { get; } = Colors.Transparent; + + public bool IsRed => Equals(TextColor, Colors.Red); + + public Guid Id { get; } = Guid.NewGuid(); + + public ICommand Command { get; } + + public event PropertyChangedEventHandler? PropertyChanged; + + public double HeightRequest + { + get => heightRequest; + set => SetProperty(ref heightRequest, value); + } + + public double Percentage + { + get => percentage; + set => SetProperty(ref percentage, value); + } + + public Color TextColor + { + get => textColor; + set => SetProperty(ref textColor, value); + } + + protected void SetProperty(ref T backingStore, in T value, [CallerMemberName] in string propertyname = "") + { + if (EqualityComparer.Default.Equals(backingStore, value)) + { + return; + } + + backingStore = value; + + OnPropertyChanged(propertyname); + } + + void OnPropertyChanged([CallerMemberName] string propertyName = "") => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + internal class NestedViewModel : ViewModel + { + NestedViewModel? model; + + public NestedViewModel? Model + { + get => model; + set => SetProperty(ref model, value); + } + } +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs index c1d1f6a2..094d8783 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs @@ -1,5 +1,9 @@ +using System.ComponentModel; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Windows.Input; using BindableObjectViews; +using CommunityToolkit.Maui.Converters; using CommunityToolkit.Maui.Markup.UnitTests.Base; using NUnit.Framework; namespace CommunityToolkit.Maui.Markup.UnitTests diff --git a/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs b/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs new file mode 100644 index 00000000..6bd76411 --- /dev/null +++ b/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs @@ -0,0 +1,15 @@ +namespace CommunityToolkit.Maui.Markup; + +public partial class BindableObjectExtensions +{ + /// Bind to a specified property with inline conversion and conversion parameter + public static TBindable Bind( + this TBindable bindable, + BindableProperty targetProperty, + BindingBase binding) + where TBindable : BindableObject + { + bindable.SetBinding(targetProperty, binding); + return bindable; + } +} \ No newline at end of file From d326f04edc4ba1a7348d03425c5639dc942bc5a2 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:40:42 -0800 Subject: [PATCH 31/59] Update Sample --- .../CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs index 261c2247..18a3befe 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs @@ -43,7 +43,7 @@ public SettingsPage(SettingsViewModel settingsViewModel) : base(settingsViewMode }) .TextCenter() .SemanticDescription(topNewsStoriesToFetchLabel.Text) - .Bind(Entry.TextProperty, static (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, static (vm, text) => vm.NumberOfTopStoriesToFetch = text), + .Bind(Entry.TextProperty, BindingBase.Create(static (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch)), new Label() .Text(string.Format(CultureInfo.CurrentUICulture, $"The number must be between {SettingsService.MinimumStoriesToFetch} and {SettingsService.MaximumStoriesToFetch}.")) From 3866804f8cb950c497b56e777a2d3fffcb4fabe6 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:48:00 -0800 Subject: [PATCH 32/59] Revert "Update Sample" This reverts commit d326f04edc4ba1a7348d03425c5639dc942bc5a2. --- .../CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs index 18a3befe..261c2247 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/Pages/SettingsPage.cs @@ -43,7 +43,7 @@ public SettingsPage(SettingsViewModel settingsViewModel) : base(settingsViewMode }) .TextCenter() .SemanticDescription(topNewsStoriesToFetchLabel.Text) - .Bind(Entry.TextProperty, BindingBase.Create(static (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch)), + .Bind(Entry.TextProperty, static (SettingsViewModel vm) => vm.NumberOfTopStoriesToFetch, static (vm, text) => vm.NumberOfTopStoriesToFetch = text), new Label() .Text(string.Format(CultureInfo.CurrentUICulture, $"The number must be between {SettingsService.MinimumStoriesToFetch} and {SettingsService.MaximumStoriesToFetch}.")) From 72b78bef8f8302babe756c4c2339750b75dfe1d6 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:48:03 -0800 Subject: [PATCH 33/59] Revert "Add Support for BindingBase.Create" This reverts commit 641b7efac39fa7be8738cb15ba1ceda84cbb1623. --- ...ndableObjectExtensions.BindingBaseTests.cs | 467 ------------------ .../BindableObjectExtensionsTests.cs | 4 - .../BindableObjectExtensions.BindingBase.cs | 15 - 3 files changed, 486 deletions(-) delete mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs delete mode 100644 src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs deleted file mode 100644 index d4dd43e1..00000000 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensions.BindingBaseTests.cs +++ /dev/null @@ -1,467 +0,0 @@ -using System.ComponentModel; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Windows.Input; -using CommunityToolkit.Maui.Converters; -using CommunityToolkit.Maui.Markup.UnitTests.Base; -using NUnit.Framework; -namespace CommunityToolkit.Maui.Markup.UnitTests; - -class BindingBaseTests : BaseTestFixture -{ - static readonly IReadOnlyDictionary colors = typeof(Colors) - .GetFields(BindingFlags.Static | BindingFlags.Public) - .ToDictionary(c => c.Name, c => (Color)(c.GetValue(null) ?? throw new InvalidOperationException())); - - [Test] - public async Task ConfirmStringFormat() - { - var viewModel = new ViewModel(); - - const string stringFormat = "{0}%"; - bool didPropertyChangeFire = false; - TaskCompletionSource propertyChangedEventArgsTCS = new(); - - var label = new Label - { - BindingContext = viewModel - }.Bind(Label.TextProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.Percentage, stringFormat: stringFormat)); - - viewModel.PropertyChanged += HandlePropertyChanged; - - BindingHelpers.AssertTypedBindingExists(label, Label.TextProperty, BindingMode.Default, viewModel, stringFormat); - Assert.That(label.Text, Is.EqualTo(string.Format(stringFormat, ViewModel.DefaultPercentage))); - - label.Text = string.Format(stringFormat, 0.1); - - Assert.Multiple(() => - { - Assert.That(label.Text, Is.EqualTo("0.1%")); - Assert.That(viewModel.Percentage, Is.EqualTo(ViewModel.DefaultPercentage)); - }); - - viewModel.Percentage = 0.6; - var propertyName = await propertyChangedEventArgsTCS.Task; - - Assert.Multiple(() => - { - Assert.That(didPropertyChangeFire, Is.True); - Assert.That(propertyName, Is.EqualTo(nameof(ViewModel.Percentage))); - }); - - void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didPropertyChangeFire = true; - propertyChangedEventArgsTCS.SetResult(e.PropertyName); - } - } - - [Test] - public async Task ConfirmReadOnlyTypedBindings() - { - var viewModel = new ViewModel(); - - bool didViewModelPropertyChangeFire = false; - TaskCompletionSource viewModelPropertyChangedEventArgsTCS = new(); - - bool didLabelPropertyChangeFire = false; - TaskCompletionSource labelPropertyChangedEventArgsTCS = new(); - - var label = new Label - { - BindingContext = viewModel - }.Bind(Label.TextColorProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.TextColor)); - - viewModel.PropertyChanged += HandleViewModelPropertyChanged; - label.PropertyChanged += HandleLabelPropertyChanged; - - BindingHelpers.AssertTypedBindingExists(label, Label.TextColorProperty, BindingMode.Default, viewModel); - Assert.That(label.TextColor, Is.EqualTo(ViewModel.DefaultColor)); - - viewModel.TextColor = Colors.Green; - var viewModelPropertyName = await viewModelPropertyChangedEventArgsTCS.Task; - var labelPropertyName = await labelPropertyChangedEventArgsTCS.Task; - - Assert.Multiple(() => - { - Assert.That(didViewModelPropertyChangeFire, Is.True); - Assert.That(viewModelPropertyName, Is.EqualTo(nameof(ViewModel.TextColor))); - - Assert.That(didLabelPropertyChangeFire, Is.True); - Assert.That(labelPropertyName, Is.EqualTo(nameof(Label.TextColor))); - - Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); - Assert.That(label.GetValue(Label.TextColorProperty), Is.EqualTo(Colors.Green)); - }); - - label.TextColor = Colors.Red; - - Assert.Multiple(() => - { - Assert.That(label.TextColor, Is.EqualTo(Colors.Red)); - Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); - }); - - void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didViewModelPropertyChangeFire = true; - viewModelPropertyChangedEventArgsTCS.SetResult(e.PropertyName); - } - - void HandleLabelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didLabelPropertyChangeFire = true; - labelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); - } - } - - [Test] - public void ConfirmOneTimeBinding() - { - var viewModel = new ViewModel(); - - bool didLabelPropertyChangeFire = false; - bool didViewModelPropertyChangeFire = false; - - var label = new Label - { - BindingContext = viewModel - }.Bind(Label.TextColorProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.TextColor, BindingMode.OneTime)); - - viewModel.PropertyChanged += HandleViewModelPropertyChanged; - label.PropertyChanged += HandleLabelPropertyChanged; - - BindingHelpers.AssertTypedBindingExists(label, Label.TextColorProperty, BindingMode.OneTime, viewModel); - Assert.That(label.TextColor, Is.EqualTo(ViewModel.DefaultColor)); - - viewModel.TextColor = Colors.Green; - Assert.Multiple(() => - { - Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); - Assert.That(label.GetValue(Label.TextColorProperty), Is.EqualTo(ViewModel.DefaultColor)); - }); - - label.TextColor = Colors.Red; - - Assert.Multiple(() => - { - Assert.That(label.TextColor, Is.EqualTo(Colors.Red)); - Assert.That(viewModel.TextColor, Is.EqualTo(Colors.Green)); - - Assert.That(didViewModelPropertyChangeFire, Is.True); - Assert.That(didLabelPropertyChangeFire, Is.True); - }); - - void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didViewModelPropertyChangeFire = true; - } - - void HandleLabelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didLabelPropertyChangeFire = true; - } - } - - [Test] - public async Task ConfirmReadWriteTypedBinding() - { - var viewModel = new ViewModel(); - - bool didViewModelPropertyChangeFire = false; - int viewModelPropertyChangedEventCount = 0; - TaskCompletionSource viewModelPropertyChangedEventArgsTCS = new(); - - bool didSliderPropertyChangeFire = false; - int sliderPropertyChangedEventCount = 0; - TaskCompletionSource sliderPropertyChangedEventArgsTCS = new(); - - var slider = new Slider - { - BindingContext = viewModel - }.Bind(Slider.ValueProperty, BindingBase.Create(static (ViewModel viewModel) => viewModel.Percentage)); - - slider.PropertyChanged += HandleSliderPropertyChanged; - viewModel.PropertyChanged += HandleViewModelPropertyChanged; - - BindingHelpers.AssertTypedBindingExists(slider, Slider.ValueProperty, BindingMode.Default, viewModel); - Assert.That(slider.Value, Is.EqualTo(ViewModel.DefaultPercentage)); - - slider.Value = 1; - - Assert.Multiple(() => - { - Assert.That(slider.Value, Is.EqualTo(1)); - Assert.That(viewModel.Percentage, Is.EqualTo(1)); - }); - - viewModel.Percentage = 0.6; - var viewModelPropertyName = await viewModelPropertyChangedEventArgsTCS.Task; - var sliderPropertyName = await sliderPropertyChangedEventArgsTCS.Task; - - Assert.Multiple(() => - { - Assert.That(didViewModelPropertyChangeFire, Is.True); - Assert.That(viewModelPropertyName, Is.EqualTo(nameof(ViewModel.Percentage))); - Assert.That(viewModelPropertyChangedEventCount, Is.EqualTo(2)); - - Assert.That(didSliderPropertyChangeFire, Is.True); - Assert.That(sliderPropertyName, Is.EqualTo(nameof(Slider.Value))); - Assert.That(sliderPropertyChangedEventCount, Is.EqualTo(2)); - - Assert.That(slider.Value, Is.EqualTo(0.6)); - Assert.That(viewModel.Percentage, Is.EqualTo(0.6)); - }); - - void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didViewModelPropertyChangeFire = true; - viewModelPropertyChangedEventCount++; - viewModelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); - } - - void HandleSliderPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didSliderPropertyChangeFire = true; - sliderPropertyChangedEventCount++; - sliderPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); - } - } - - [TestCase(false, false)] - [TestCase(false, true)] - [TestCase(true, false)] - [TestCase(true, true)] - public void ValueSetOnOneWayWithNestedPathBinding(bool setContextFirst, bool isDefault) - { - var entry = new Entry(); - var bindingMode = isDefault ? BindingMode.Default : BindingMode.OneWay; - - var viewmodel = new NestedViewModel - { - Model = new NestedViewModel - { - Model = new NestedViewModel() - } - }; - - if (setContextFirst) - { - entry.BindingContext = viewmodel; - entry.Bind(Entry.TextColorProperty, BindingBase.Create( - static vm => vm.Model?.Model?.TextColor, - mode: bindingMode)); - } - else - { - entry.Bind(Entry.TextColorProperty, BindingBase.Create( - static vm => vm.Model?.Model?.TextColor, - mode: bindingMode)); - - entry.BindingContext = viewmodel; - } - - Assert.Multiple(() => - { - Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(ViewModel.DefaultColor)); - Assert.That(entry.GetValue(Entry.TextColorProperty), Is.EqualTo(ViewModel.DefaultColor)); - }); - - var textColor = Colors.Pink; - - viewmodel.Model.Model.TextColor = textColor; - - Assert.Multiple(() => - { - Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(textColor)); - Assert.That(entry.GetValue(Entry.TextColorProperty), Is.EqualTo(textColor)); - }); - } - - [TestCase(false, false)] - [TestCase(false, true)] - [TestCase(true, false)] - [TestCase(true, true)] - public void ValueSetOnOneWayWithNestedPathBindingWithIValueConverter(bool setContextFirst, bool isDefault) - { - var label = new Label(); - var bindingMode = isDefault ? BindingMode.Default : BindingMode.OneWay; - - var viewmodel = new NestedViewModel - { - Model = new NestedViewModel - { - Model = new NestedViewModel() - } - }; - - var colorToHexRgbStringConverter = new ColorToHexRgbStringConverter(); - - if (setContextFirst) - { - label.BindingContext = viewmodel; - label.Bind( - Label.TextProperty, - BindingBase.Create(static (NestedViewModel vm) => vm.Model?.Model?.TextColor, - bindingMode, - converter: colorToHexRgbStringConverter)); - } - else - { - label.Bind( - Label.TextProperty, - BindingBase.Create(static (NestedViewModel vm) => vm.Model?.Model?.TextColor, - bindingMode, - converter: colorToHexRgbStringConverter)); - - label.BindingContext = viewmodel; - } - - Assert.Multiple(() => - { - Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(ViewModel.DefaultColor)); - Assert.That(label.GetValue(Label.TextProperty), Is.EqualTo(colorToHexRgbStringConverter.ConvertFrom(ViewModel.DefaultColor))); - }); - - var textColor = Colors.Pink; - - viewmodel.Model.Model.TextColor = textColor; - Assert.Multiple(() => - { - Assert.That(viewmodel.Model.Model.TextColor, Is.EqualTo(textColor)); - Assert.That(label.GetValue(Label.TextProperty), Is.EqualTo(colorToHexRgbStringConverter.ConvertFrom(textColor))); - }); - } - - [Test] - public async Task ConfirmReadOnlyTypedBindingWithIValueConverter() - { - var viewModel = new ViewModel(); - - var colorToHexRgbStringConverter = new ColorToHexRgbStringConverter(); - var updatedTextColor = Colors.Pink; - - bool didViewModelPropertyChangeFire = false; - int viewModelPropertyChangedEventCount = 0; - TaskCompletionSource viewModelPropertyChangedEventArgsTCS = new(); - - bool didLabelPropertyChangeFire = false; - int labelPropertyChangedEventCount = 0; - TaskCompletionSource labelPropertyChangedEventArgsTCS = new(); - - var label = new Label - { - BindingContext = viewModel - }.Bind(Label.TextProperty, - BindingBase.Create(static (ViewModel viewModel) => viewModel.TextColor, - converter: colorToHexRgbStringConverter)); - - label.PropertyChanged += HandleLabelPropertyChanged; - viewModel.PropertyChanged += HandleViewModelPropertyChanged; - - BindingHelpers.AssertTypedBindingExists( - label, - Label.TextProperty, - BindingMode.Default, - viewModel, - expectedConverter: colorToHexRgbStringConverter); - - viewModel.TextColor = updatedTextColor; - var viewModelPropertyName = await viewModelPropertyChangedEventArgsTCS.Task; - var labelPropertyName = await labelPropertyChangedEventArgsTCS.Task; - - Assert.Multiple(() => - { - Assert.That(didViewModelPropertyChangeFire, Is.True); - Assert.That(viewModelPropertyName, Is.EqualTo(nameof(ViewModel.TextColor))); - Assert.That(viewModelPropertyChangedEventCount, Is.EqualTo(1)); - - Assert.That(didLabelPropertyChangeFire, Is.True); - Assert.That(labelPropertyName, Is.EqualTo(nameof(Label.Text))); - Assert.That(labelPropertyChangedEventCount, Is.EqualTo(1)); - Assert.That(colorToHexRgbStringConverter.ConvertFrom(updatedTextColor), Is.EqualTo(label.Text)); - }); - - void HandleViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didViewModelPropertyChangeFire = true; - viewModelPropertyChangedEventCount++; - viewModelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); - } - - void HandleLabelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - didLabelPropertyChangeFire = true; - labelPropertyChangedEventCount++; - labelPropertyChangedEventArgsTCS.TrySetResult(e.PropertyName); - } - } - - internal class ViewModel : INotifyPropertyChanged - { - public const double DefaultPercentage = 0.5; - public const double DefaultHeightRequest = 500; - - double percentage = DefaultPercentage, heightRequest = DefaultHeightRequest; - Color textColor = DefaultColor; - - public ViewModel() - { - Command = new Command(() => TextColor = colors.Skip(Random.Shared.Next(colors.Keys.Count())).First().Value); - } - - public static Color DefaultColor { get; } = Colors.Transparent; - - public bool IsRed => Equals(TextColor, Colors.Red); - - public Guid Id { get; } = Guid.NewGuid(); - - public ICommand Command { get; } - - public event PropertyChangedEventHandler? PropertyChanged; - - public double HeightRequest - { - get => heightRequest; - set => SetProperty(ref heightRequest, value); - } - - public double Percentage - { - get => percentage; - set => SetProperty(ref percentage, value); - } - - public Color TextColor - { - get => textColor; - set => SetProperty(ref textColor, value); - } - - protected void SetProperty(ref T backingStore, in T value, [CallerMemberName] in string propertyname = "") - { - if (EqualityComparer.Default.Equals(backingStore, value)) - { - return; - } - - backingStore = value; - - OnPropertyChanged(propertyname); - } - - void OnPropertyChanged([CallerMemberName] string propertyName = "") => - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - internal class NestedViewModel : ViewModel - { - NestedViewModel? model; - - public NestedViewModel? Model - { - get => model; - set => SetProperty(ref model, value); - } - } -} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs index 094d8783..c1d1f6a2 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindableObjectExtensionsTests.cs @@ -1,9 +1,5 @@ -using System.ComponentModel; -using System.Reflection; -using System.Runtime.CompilerServices; using System.Windows.Input; using BindableObjectViews; -using CommunityToolkit.Maui.Converters; using CommunityToolkit.Maui.Markup.UnitTests.Base; using NUnit.Framework; namespace CommunityToolkit.Maui.Markup.UnitTests diff --git a/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs b/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs deleted file mode 100644 index 6bd76411..00000000 --- a/src/CommunityToolkit.Maui.Markup/BindableObjectExtensions.BindingBase.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CommunityToolkit.Maui.Markup; - -public partial class BindableObjectExtensions -{ - /// Bind to a specified property with inline conversion and conversion parameter - public static TBindable Bind( - this TBindable bindable, - BindableProperty targetProperty, - BindingBase binding) - where TBindable : BindableObject - { - bindable.SetBinding(targetProperty, binding); - return bindable; - } -} \ No newline at end of file From 2c9c914b008bfd450794af7681d86cb60d227789 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:46:46 -0800 Subject: [PATCH 34/59] Update MVVM Toolkit --- ...CommunityToolkit.Maui.Markup.Sample.csproj | 4 +-- .../ViewModels/NewsDetailViewModel.cs | 23 +++++++---------- .../ViewModels/NewsViewModel.cs | 25 +++++++------------ .../ViewModels/SettingsViewModel.cs | 16 ++++-------- 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index 67dea726..ff6a0e5d 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -60,8 +60,8 @@ - - + + diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs index 01b5607c..ebc0393e 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs @@ -1,24 +1,19 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class NewsDetailViewModel : BaseViewModel, IQueryAttributable +sealed partial class NewsDetailViewModel(IBrowser browser) : BaseViewModel, IQueryAttributable { - readonly IBrowser browser; + readonly IBrowser browser = browser; - [ObservableProperty] - Uri? uri; + [ObservableProperty] + public partial Uri? Uri { get; set; } - [ObservableProperty] - string title = string.Empty; + [ObservableProperty] + public partial string Title { get; set; } = string.Empty; - [ObservableProperty] - string scoreDescription = string.Empty; + [ObservableProperty] + public partial string ScoreDescription { get; set; } = string.Empty; - public NewsDetailViewModel(IBrowser browser) - { - this.browser = browser; - } - - [RelayCommand] + [RelayCommand] Task OpenBrowser() { ArgumentNullException.ThrowIfNull(Uri); diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs index 7850b13e..569e6379 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs @@ -1,26 +1,19 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class NewsViewModel : BaseViewModel, IDisposable +sealed partial class NewsViewModel(IDispatcher dispatcher, + SettingsService settingsService, + HackerNewsAPIService hackerNewsAPIService) : BaseViewModel, IDisposable { - readonly IDispatcher dispatcher; - readonly SettingsService settingsService; - readonly HackerNewsAPIService hackerNewsAPIService; + readonly IDispatcher dispatcher = dispatcher; + readonly SettingsService settingsService = settingsService; + readonly HackerNewsAPIService hackerNewsAPIService = hackerNewsAPIService; readonly WeakEventManager pullToRefreshEventManager = new(); readonly SemaphoreSlim insertIntoSortedCollectionSemaphore = new(1, 1); - [ObservableProperty] - bool isListRefreshing; + [ObservableProperty] + public partial bool IsListRefreshing { get; set; } - public NewsViewModel(IDispatcher dispatcher, - SettingsService settingsService, - HackerNewsAPIService hackerNewsAPIService) - { - this.dispatcher = dispatcher; - this.settingsService = settingsService; - this.hackerNewsAPIService = hackerNewsAPIService; - } - - public event EventHandler PullToRefreshFailed + public event EventHandler PullToRefreshFailed { add => pullToRefreshEventManager.AddEventHandler(value); remove => pullToRefreshEventManager.RemoveEventHandler(value); diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs index e2fb5be4..505990be 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs @@ -1,19 +1,13 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class SettingsViewModel : BaseViewModel +sealed partial class SettingsViewModel(SettingsService settingsService) : BaseViewModel { - readonly SettingsService settingsService; + readonly SettingsService settingsService = settingsService; - [ObservableProperty] - int numberOfTopStoriesToFetch; + [ObservableProperty] + public partial int NumberOfTopStoriesToFetch { get; set; } = settingsService.NumberOfTopStoriesToFetch; - public SettingsViewModel(SettingsService settingsService) - { - this.settingsService = settingsService; - NumberOfTopStoriesToFetch = settingsService.NumberOfTopStoriesToFetch; - } - - partial void OnNumberOfTopStoriesToFetchChanged(int value) + partial void OnNumberOfTopStoriesToFetchChanged(int value) { settingsService.NumberOfTopStoriesToFetch = value; } From 5d0c904c11cddde314c2e173f77cff83f63e1fc3 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:58:07 -0800 Subject: [PATCH 35/59] Revert "Update MVVM Toolkit" This reverts commit 2c9c914b008bfd450794af7681d86cb60d227789. --- ...CommunityToolkit.Maui.Markup.Sample.csproj | 4 +-- .../ViewModels/NewsDetailViewModel.cs | 23 ++++++++++------- .../ViewModels/NewsViewModel.cs | 25 ++++++++++++------- .../ViewModels/SettingsViewModel.cs | 16 ++++++++---- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index ff6a0e5d..67dea726 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -60,8 +60,8 @@ - - + + diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs index ebc0393e..01b5607c 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs @@ -1,19 +1,24 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class NewsDetailViewModel(IBrowser browser) : BaseViewModel, IQueryAttributable +sealed partial class NewsDetailViewModel : BaseViewModel, IQueryAttributable { - readonly IBrowser browser = browser; + readonly IBrowser browser; - [ObservableProperty] - public partial Uri? Uri { get; set; } + [ObservableProperty] + Uri? uri; - [ObservableProperty] - public partial string Title { get; set; } = string.Empty; + [ObservableProperty] + string title = string.Empty; - [ObservableProperty] - public partial string ScoreDescription { get; set; } = string.Empty; + [ObservableProperty] + string scoreDescription = string.Empty; - [RelayCommand] + public NewsDetailViewModel(IBrowser browser) + { + this.browser = browser; + } + + [RelayCommand] Task OpenBrowser() { ArgumentNullException.ThrowIfNull(Uri); diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs index 569e6379..7850b13e 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs @@ -1,19 +1,26 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class NewsViewModel(IDispatcher dispatcher, - SettingsService settingsService, - HackerNewsAPIService hackerNewsAPIService) : BaseViewModel, IDisposable +sealed partial class NewsViewModel : BaseViewModel, IDisposable { - readonly IDispatcher dispatcher = dispatcher; - readonly SettingsService settingsService = settingsService; - readonly HackerNewsAPIService hackerNewsAPIService = hackerNewsAPIService; + readonly IDispatcher dispatcher; + readonly SettingsService settingsService; + readonly HackerNewsAPIService hackerNewsAPIService; readonly WeakEventManager pullToRefreshEventManager = new(); readonly SemaphoreSlim insertIntoSortedCollectionSemaphore = new(1, 1); - [ObservableProperty] - public partial bool IsListRefreshing { get; set; } + [ObservableProperty] + bool isListRefreshing; - public event EventHandler PullToRefreshFailed + public NewsViewModel(IDispatcher dispatcher, + SettingsService settingsService, + HackerNewsAPIService hackerNewsAPIService) + { + this.dispatcher = dispatcher; + this.settingsService = settingsService; + this.hackerNewsAPIService = hackerNewsAPIService; + } + + public event EventHandler PullToRefreshFailed { add => pullToRefreshEventManager.AddEventHandler(value); remove => pullToRefreshEventManager.RemoveEventHandler(value); diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs index 505990be..e2fb5be4 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/SettingsViewModel.cs @@ -1,13 +1,19 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class SettingsViewModel(SettingsService settingsService) : BaseViewModel +sealed partial class SettingsViewModel : BaseViewModel { - readonly SettingsService settingsService = settingsService; + readonly SettingsService settingsService; - [ObservableProperty] - public partial int NumberOfTopStoriesToFetch { get; set; } = settingsService.NumberOfTopStoriesToFetch; + [ObservableProperty] + int numberOfTopStoriesToFetch; - partial void OnNumberOfTopStoriesToFetchChanged(int value) + public SettingsViewModel(SettingsService settingsService) + { + this.settingsService = settingsService; + NumberOfTopStoriesToFetch = settingsService.NumberOfTopStoriesToFetch; + } + + partial void OnNumberOfTopStoriesToFetchChanged(int value) { settingsService.NumberOfTopStoriesToFetch = value; } From 76dc565504516b5ac04092f7e7d336f4b325d2c7 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:59:25 -0800 Subject: [PATCH 36/59] Revert --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c1d4b127..8da76ba1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net9.0 9.0.0-rc.2.24503.2 - 9.1.0 + 9.1.1 preview enable enable From 51fc161316c189c8b8383da6f1e6da94df159775 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:58:24 -0800 Subject: [PATCH 37/59] Use `params ReadOnlySpan` --- .../Base/BaseMarkupTestFixture.cs | 10 +++++----- .../AbsoluteLayoutExtensions.cs | 2 +- .../DefaultBindableProperties.cs | 4 ++-- .../DynamicResourceHandlerExtensions.cs | 2 +- .../ElementExtensions.cs | 4 ++-- src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs | 12 ++++++------ src/CommunityToolkit.Maui.Markup/LabelExtensions.cs | 2 +- src/CommunityToolkit.Maui.Markup/Style.cs | 10 +++++----- .../VisualElementExtensions.cs | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs index bd8adf4b..fd0154b2 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/Base/BaseMarkupTestFixture.cs @@ -23,17 +23,17 @@ public override void TearDown() protected void TestPropertiesSet( Action modify, - params List<(BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)> propertyChanges) + params ReadOnlySpan<(BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)> propertyChanges) => TestPropertiesSet(Bindable, modify, propertyChanges); protected void TestPropertiesSet( Action modify, - params List<(BindableProperty property, object? beforeValue, object? expectedValue)> propertyChanges) + params ReadOnlySpan<(BindableProperty property, object? beforeValue, object? expectedValue)> propertyChanges) => TestPropertiesSet(Bindable, modify, propertyChanges); protected void TestPropertiesSet( Action modify, - params List<(BindableProperty property, object? expectedValue)> propertyChanges) + params ReadOnlySpan<(BindableProperty property, object? expectedValue)> propertyChanges) => TestPropertiesSet(Bindable, modify, propertyChanges); } @@ -42,7 +42,7 @@ abstract class BaseMarkupTestFixture : BaseTestFixture protected static void TestPropertiesSet( TBindable bindable, Action modify, - params List<(BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)> propertyChanges) where TBindable : BindableObject + params ReadOnlySpan<(BindableProperty property, TPropertyValue beforeValue, TPropertyValue expectedValue)> propertyChanges) where TBindable : BindableObject { foreach (var (property, beforeValue, expectedValue) in propertyChanges) { @@ -61,7 +61,7 @@ protected static void TestPropertiesSet( protected static void TestPropertiesSet( TBindable bindable, Action modify, - params List<(BindableProperty property, TPropertyValue expectedValue)> propertyChanges) where TBindable : BindableObject + params ReadOnlySpan<(BindableProperty property, TPropertyValue expectedValue)> propertyChanges) where TBindable : BindableObject { foreach (var (property, expectedValue) in propertyChanges) { diff --git a/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs b/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs index 7f993141..fbf00900 100644 --- a/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/AbsoluteLayoutExtensions.cs @@ -45,7 +45,7 @@ public static TBindable LayoutFlags(this TBindable bindable, Absolute /// /// /// - public static TBindable LayoutFlags(this TBindable bindable, params List flags) where TBindable : BindableObject + public static TBindable LayoutFlags(this TBindable bindable, params ReadOnlySpan flags) where TBindable : BindableObject { var newFlags = AbsoluteLayoutFlags.None; diff --git a/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs b/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs index 458ab7b8..b3a79554 100644 --- a/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs +++ b/src/CommunityToolkit.Maui.Markup/DefaultBindableProperties.cs @@ -101,7 +101,7 @@ public static class DefaultBindableProperties /// Registers Bindable Properties /// /// - public static void Register(params List properties) + public static void Register(params ReadOnlySpan properties) { foreach (var property in properties) { @@ -116,7 +116,7 @@ public static void Register(params List properties) /// Registers Command and CommandParameter Properties /// /// - public static void RegisterForCommand(params List<(BindableProperty commandProperty, BindableProperty parameterProperty)> propertyPairs) + public static void RegisterForCommand(params ReadOnlySpan<(BindableProperty commandProperty, BindableProperty parameterProperty)> propertyPairs) { foreach (var propertyPair in propertyPairs) { diff --git a/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs b/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs index 80ded52e..7bac838b 100644 --- a/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/DynamicResourceHandlerExtensions.cs @@ -29,7 +29,7 @@ public static TDynamicResourceHandler DynamicResource(t /// /// /// Layout with added Dynamic Resource - public static TDynamicResourceHandler DynamicResources(this TDynamicResourceHandler dynamicResourceHandler, params List<(BindableProperty property, string key)> resources) + public static TDynamicResourceHandler DynamicResources(this TDynamicResourceHandler dynamicResourceHandler, params ReadOnlySpan<(BindableProperty property, string key)> resources) where TDynamicResourceHandler : IDynamicResourceHandler { foreach (var (property, key) in resources) diff --git a/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs b/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs index 1f6ffdc8..bd3a91e6 100644 --- a/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/ElementExtensions.cs @@ -59,7 +59,7 @@ public static TLayout Paddings(this TLayout paddingElement, double left /// /// /// Layout without Dynamic Resource - public static TBindable RemoveDynamicResources(this TBindable bindable, params List properties) where TBindable : BindableObject + public static TBindable RemoveDynamicResources(this TBindable bindable, params ReadOnlySpan properties) where TBindable : BindableObject { foreach (var property in properties) { @@ -76,7 +76,7 @@ public static TBindable RemoveDynamicResources(this TBindable bindabl /// /// /// Element with added Effects - public static TElement Effects(this TElement element, params List effects) where TElement : Element + public static TElement Effects(this TElement element, params ReadOnlySpan effects) where TElement : Element { foreach (var effect in effects) { diff --git a/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs b/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs index 9b832ead..cac2db7f 100644 --- a/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs +++ b/src/CommunityToolkit.Maui.Markup/GridRowsColumns.cs @@ -33,7 +33,7 @@ public static class Columns /// /// /// - public static ColumnDefinitionCollection Define(params List widths) + public static ColumnDefinitionCollection Define(params ReadOnlySpan widths) { var columnDefinitions = new ColumnDefinitionCollection(); @@ -52,11 +52,11 @@ public static ColumnDefinitionCollection Define(params List widths) /// /// /// - public static ColumnDefinitionCollection Define(params List<(TEnum Name, GridLength Width)> columns) where TEnum : Enum + public static ColumnDefinitionCollection Define(params ReadOnlySpan<(TEnum Name, GridLength Width)> columns) where TEnum : Enum { var columnDefinitions = new ColumnDefinitionCollection(); - for (var i = 0; i < columns.Count; i++) + for (var i = 0; i < columns.Length; i++) { if (i != columns[i].Name.ToInt()) { @@ -82,7 +82,7 @@ public static class Rows /// /// /// - public static RowDefinitionCollection Define(params List heights) + public static RowDefinitionCollection Define(params ReadOnlySpan heights) { var rowDefinitions = new RowDefinitionCollection(); @@ -101,10 +101,10 @@ public static RowDefinitionCollection Define(params List heights) /// /// /// - public static RowDefinitionCollection Define(params List<(TEnum Name, GridLength Height)> rows) where TEnum : Enum + public static RowDefinitionCollection Define(params ReadOnlySpan<(TEnum Name, GridLength Height)> rows) where TEnum : Enum { var rowDefinitions = new RowDefinitionCollection(); - for (var i = 0; i < rows.Count; i++) + for (var i = 0; i < rows.Length; i++) { if (i != rows[i].Name.ToInt()) { diff --git a/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs b/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs index 3efa6016..30afa932 100644 --- a/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/LabelExtensions.cs @@ -12,7 +12,7 @@ public static class LabelExtensions /// /// /// Label with added FormattedText - public static TLabel FormattedText(this TLabel label, params List spans) where TLabel : Label + public static TLabel FormattedText(this TLabel label, params ReadOnlySpan spans) where TLabel : Label { label.FormattedText = new FormattedString(); diff --git a/src/CommunityToolkit.Maui.Markup/Style.cs b/src/CommunityToolkit.Maui.Markup/Style.cs index ea2473d5..84996403 100644 --- a/src/CommunityToolkit.Maui.Markup/Style.cs +++ b/src/CommunityToolkit.Maui.Markup/Style.cs @@ -33,7 +33,7 @@ public Style(BindableProperty property, object value) : this((property, value)) /// Initialize Style /// /// - public Style(params List<(BindableProperty Property, object Value)> setters) + public Style(params ReadOnlySpan<(BindableProperty Property, object Value)> setters) { MauiStyle = new Style(typeof(T)); Add(setters); @@ -97,7 +97,7 @@ public Style Add(BindableProperty property, object value) /// /// /// Style with added setters - public Style Add(params List<(BindableProperty Property, object Value)> setters) + public Style Add(params ReadOnlySpan<(BindableProperty Property, object Value)> setters) { foreach (var (property, value) in setters) { @@ -129,7 +129,7 @@ public Style AddAppThemeBinding(BindableProperty property, object light, obje /// /// A set of , and value for light and dark theme. /// Style with added setters - public Style AddAppThemeBindings(params List<(BindableProperty Property, object Light, object Dark)> setters) + public Style AddAppThemeBindings(params ReadOnlySpan<(BindableProperty Property, object Light, object Dark)> setters) { foreach (var (property, light, dark) in setters) { @@ -144,7 +144,7 @@ public Style AddAppThemeBindings(params List<(BindableProperty Property, obje /// /// /// Style with added behaviors - public Style Add(params List behaviors) + public Style Add(params ReadOnlySpan behaviors) { foreach (var behavior in behaviors) { @@ -159,7 +159,7 @@ public Style Add(params List behaviors) /// /// /// Style with added Triggers - public Style Add(params List triggers) + public Style Add(params ReadOnlySpan triggers) { foreach (var trigger in triggers) { diff --git a/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs b/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs index 3274f82a..b683d757 100644 --- a/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs +++ b/src/CommunityToolkit.Maui.Markup/VisualElementExtensions.cs @@ -188,7 +188,7 @@ public static TVisualElement Style(this TVisualElement element, /// This element to add the to. /// The s to add. /// The supplied with the supplied added. - public static TVisualElement Behaviors(this TVisualElement element, params List behaviors) where TVisualElement : VisualElement + public static TVisualElement Behaviors(this TVisualElement element, params ReadOnlySpan behaviors) where TVisualElement : VisualElement { foreach (var behavior in behaviors) { From b5e5f0b201ee9868a24075b8ce32ebb5fbba5fd3 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Sun, 10 Nov 2024 12:36:48 -0800 Subject: [PATCH 38/59] Update ExecuteBindingsBase.cs --- .../Benchmarks/ExecuteBindingsBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs index 216a9ada..bb6a7704 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs @@ -13,8 +13,8 @@ protected ExecuteBindingsBase() BindingContext = DefaultBindingsLabelViewModel }; - DefaultBindingsLabel.SetBinding(Label.TextProperty, BindingBase.Create((LabelViewModel vm) => vm.Text, mode: BindingMode.TwoWay)); - DefaultBindingsLabel.SetBinding(Label.TextColorProperty, BindingBase.Create((LabelViewModel vm) => vm.TextColor, mode: BindingMode.TwoWay)); + DefaultBindingsLabel.SetBinding(Label.TextProperty, nameof(LabelViewModel.Text), mode: BindingMode.TwoWay); + DefaultBindingsLabel.SetBinding(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); DefaultBindingsLabel.EnableAnimations(); TypedMarkupBindingsLabel = new Label From cd4f88e2007239ffcc764f7d7a88b4b87eb86fb4 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Sun, 10 Nov 2024 12:47:19 -0800 Subject: [PATCH 39/59] Revert Benchmarks --- .../Benchmarks/ExecuteBindingsBase.cs | 9 +++++++++ .../Benchmarks/ExecuteBindings_ViewModelToView.cs | 7 +++++++ .../Benchmarks/ExecuteBindings_ViewToViewModel.cs | 7 +++++++ .../Benchmarks/InitializeBindings.cs | 8 ++++++++ 4 files changed, 31 insertions(+) diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs index bb6a7704..304f896e 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindingsBase.cs @@ -17,6 +17,13 @@ protected ExecuteBindingsBase() DefaultBindingsLabel.SetBinding(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); DefaultBindingsLabel.EnableAnimations(); + DefaultMarkupBindingsLabel = new Label + { + BindingContext = DefaultMarkupBindingsLabelViewModel + }.Bind(Label.TextProperty, nameof(LabelViewModel.Text), mode: BindingMode.TwoWay) + .Bind(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); + DefaultMarkupBindingsLabel.EnableAnimations(); + TypedMarkupBindingsLabel = new Label { BindingContext = TypedMarkupBindingsLabelViewModel @@ -32,8 +39,10 @@ protected ExecuteBindingsBase() } protected LabelViewModel DefaultBindingsLabelViewModel { get; } = new(); + protected LabelViewModel DefaultMarkupBindingsLabelViewModel { get; } = new(); protected LabelViewModel TypedMarkupBindingsLabelViewModel { get; } = new(); protected Label DefaultBindingsLabel { get; } + protected Label DefaultMarkupBindingsLabel { get; } protected Label TypedMarkupBindingsLabel { get; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs index 4e9b1741..d93d85a0 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewModelToView.cs @@ -10,6 +10,13 @@ public void ExecuteDefaultBindings_ViewModelToView() DefaultBindingsLabelViewModel.TextColor = Colors.Green; DefaultBindingsLabelViewModel.Text = helloWorldText; } + + [Benchmark] + public void ExecuteDefaultBindingsMarkup_ViewToViewModel() + { + DefaultMarkupBindingsLabel.TextColor = Colors.Green; + DefaultMarkupBindingsLabel.Text = helloWorldText; + } [Benchmark] public void ExecuteTypedBindingsMarkup_ViewModelToView() diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs index 2936ad73..027e9907 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/ExecuteBindings_ViewToViewModel.cs @@ -10,6 +10,13 @@ public void ExecuteDefaultBindings_ViewToViewModel() DefaultBindingsLabel.TextColor = Colors.Green; DefaultBindingsLabel.Text = helloWorldText; } + + [Benchmark] + public void ExecuteDefaultBindingsMarkup_ViewModelToView() + { + DefaultMarkupBindingsLabelViewModel.TextColor = Colors.Green; + DefaultMarkupBindingsLabelViewModel.Text = helloWorldText; + } [Benchmark] public void ExecuteTypedBindingsMarkup_ViewToViewModel() diff --git a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs index 5ead50e4..b3b55af6 100644 --- a/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs +++ b/src/CommunityToolkit.Maui.Markup.Benchmarks/Benchmarks/InitializeBindings.cs @@ -26,6 +26,14 @@ public void InitializeDefaultBindings() defaultBindingsLabel.SetBinding(Label.TextProperty, nameof(LabelViewModel.Text), mode: BindingMode.TwoWay); defaultBindingsLabel.SetBinding(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); } + + [Benchmark] + public void InitializeDefaultBindingsMarkup() + { + defaultMarkupBindingsLabel + .Bind(Label.TextProperty, nameof(LabelViewModel.Text), mode: BindingMode.TwoWay) + .Bind(Label.TextColorProperty, nameof(LabelViewModel.TextColor), mode: BindingMode.TwoWay); + } [Benchmark] public void InitializeTypedBindingsMarkup() From 72b611a17253e46c3a3b6938cedac58406605eb3 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Sun, 10 Nov 2024 12:49:47 -0800 Subject: [PATCH 40/59] Update Xcode logic --- azure-pipelines.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3e018746..6f66581b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -56,8 +56,8 @@ jobs: vmImage: $(image) steps: - script: | - sudo xcode-select -s /Applications/Xcode_$(CommunityToolkitSampleApp_Xcode_Version).app - sudo xcode-select -p + ls -al | grep Xcode + echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitSampleApp_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitSampleApp_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitSampleApp_Xcode_Version)' condition: eq(variables['Agent.OS'], 'Darwin') # Only run this step on macOS @@ -123,8 +123,8 @@ jobs: condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['build.reason'], 'PullRequest')) - script: | - sudo xcode-select -s /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app - sudo xcode-select -p + ls -al | grep Xcode + echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitLibrary_Xcode_Version)' condition: eq(variables['Agent.OS'], 'Darwin') # Only run this step on macOS @@ -254,8 +254,8 @@ jobs: vmImage: $(image) steps: - script: | - sudo xcode-select -s /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app - sudo xcode-select -p + ls -al | grep Xcode + echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitLibrary_Xcode_Version)' condition: eq(variables['Agent.OS'], 'Darwin') # Only run this step on macOS From 0c5605522eadf091495847fe395efc54bdb68c7e Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Sun, 10 Nov 2024 12:52:14 -0800 Subject: [PATCH 41/59] Update azure-pipelines.yml --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6f66581b..235263fa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -56,7 +56,7 @@ jobs: vmImage: $(image) steps: - script: | - ls -al | grep Xcode + ls -al /Applications | grep Xcode echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitSampleApp_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitSampleApp_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitSampleApp_Xcode_Version)' condition: eq(variables['Agent.OS'], 'Darwin') # Only run this step on macOS @@ -123,7 +123,7 @@ jobs: condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['build.reason'], 'PullRequest')) - script: | - ls -al | grep Xcode + ls -al /Applications | grep Xcode echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitLibrary_Xcode_Version)' condition: eq(variables['Agent.OS'], 'Darwin') # Only run this step on macOS @@ -254,7 +254,7 @@ jobs: vmImage: $(image) steps: - script: | - ls -al | grep Xcode + ls -al /Applications | grep Xcode echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitLibrary_Xcode_Version)' condition: eq(variables['Agent.OS'], 'Darwin') # Only run this step on macOS From 21848d2f5aa096a0db9b464ba982b4519c4a8847 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Sun, 10 Nov 2024 12:56:12 -0800 Subject: [PATCH 42/59] Update azure-pipelines.yml --- azure-pipelines.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 235263fa..1a29cf82 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -56,6 +56,7 @@ jobs: vmImage: $(image) steps: - script: | + echo Installed Xcode Versions: ls -al /Applications | grep Xcode echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitSampleApp_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitSampleApp_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitSampleApp_Xcode_Version)' @@ -123,6 +124,7 @@ jobs: condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['build.reason'], 'PullRequest')) - script: | + echo Installed Xcode Versions: ls -al /Applications | grep Xcode echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitLibrary_Xcode_Version)' @@ -254,6 +256,7 @@ jobs: vmImage: $(image) steps: - script: | + echo Installed Xcode Versions: ls -al /Applications | grep Xcode echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app;sudo xcode-select --switch /Applications/Xcode_$(CommunityToolkitLibrary_Xcode_Version).app/Contents/Developer displayName: 'Set Xcode v$(CommunityToolkitLibrary_Xcode_Version)' From 6906bfaa3227724779a74cbf5bf002066f8b114f Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:01:14 -0800 Subject: [PATCH 43/59] Increase to macOS-15 --- azure-pipelines.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1a29cf82..06b4e6c9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,6 +4,8 @@ variables: CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)' NugetPackageVersion: '$(CurrentSemanticVersion)' NET_VERSION: '9.0.100-rc.2.24474.11' + BuildHostImage_Windows: 'windows-latest' + BuildHostImage_macOS: 'macos-15' RunPoliCheck: false PathToLibrarySolution: 'src/CommunityToolkit.Maui.Markup.sln' PathToSamplesSolution: 'samples/CommunityToolkit.Maui.Markup.Sample.sln' @@ -49,9 +51,9 @@ jobs: strategy: matrix: 'Windows': - image: 'windows-latest' + image: '$(BuildHostImage_Windows)' 'macOS': - image: 'macos-14' + image: '$(BuildHostImage_macOS)' pool: vmImage: $(image) steps: @@ -97,9 +99,9 @@ jobs: strategy: matrix: 'Windows': - image: 'windows-latest' + image: '$(BuildHostImage_Windows)' 'macOS': - image: 'macos-14' + image: '$(BuildHostImage_macOS)' pool: vmImage: $(image) steps: @@ -249,9 +251,9 @@ jobs: strategy: matrix: 'Windows': - image: 'windows-latest' + image: '$(BuildHostImage_Windows)' 'macOS': - image: 'macos-14' + image: '$(BuildHostImage_macOS)' pool: vmImage: $(image) steps: From f941773935ec28a910b7003edcd4c657fd832e3b Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:09:39 -0800 Subject: [PATCH 44/59] Fix Hosted Agent Image --- azure-pipelines.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 06b4e6c9..5a64f006 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,8 +4,6 @@ variables: CurrentSemanticVersion: '$(CurrentSemanticVersionBase)-preview$(PreviewNumber)' NugetPackageVersion: '$(CurrentSemanticVersion)' NET_VERSION: '9.0.100-rc.2.24474.11' - BuildHostImage_Windows: 'windows-latest' - BuildHostImage_macOS: 'macos-15' RunPoliCheck: false PathToLibrarySolution: 'src/CommunityToolkit.Maui.Markup.sln' PathToSamplesSolution: 'samples/CommunityToolkit.Maui.Markup.Sample.sln' @@ -51,9 +49,9 @@ jobs: strategy: matrix: 'Windows': - image: '$(BuildHostImage_Windows)' + image: 'windows-latest' 'macOS': - image: '$(BuildHostImage_macOS)' + image: 'macos-15' pool: vmImage: $(image) steps: @@ -99,9 +97,9 @@ jobs: strategy: matrix: 'Windows': - image: '$(BuildHostImage_Windows)' + image: 'windows-latest' 'macOS': - image: '$(BuildHostImage_macOS)' + image: 'macos-15' pool: vmImage: $(image) steps: From e8364aa0b491e809003db9b3cedd399789c8691c Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:13:09 -0800 Subject: [PATCH 45/59] Update azure-pipelines.yml --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5a64f006..77317644 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -249,9 +249,9 @@ jobs: strategy: matrix: 'Windows': - image: '$(BuildHostImage_Windows)' + image: 'windows-latest' 'macOS': - image: '$(BuildHostImage_macOS)' + image: 'macos-15' pool: vmImage: $(image) steps: From 5d7a4a6029d5edc60f770089a59f450964d5d67e Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:25:36 -0800 Subject: [PATCH 46/59] Use `CollectionViewHandler2` and `CarouselViewHandler2` --- .../MauiProgram.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/MauiProgram.cs b/samples/CommunityToolkit.Maui.Markup.Sample/MauiProgram.cs index 59142159..7646a4a1 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/MauiProgram.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/MauiProgram.cs @@ -8,12 +8,17 @@ public class MauiProgram public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder() - .UseMauiApp() - .UseMauiCommunityToolkit() - .UseMauiCommunityToolkitMarkup(); - - // Fonts - builder.ConfigureFonts(fonts => fonts.AddFont("FontAwesome.otf", "FontAwesome")); + .UseMauiApp() + .UseMauiCommunityToolkit() + .UseMauiCommunityToolkitMarkup() + .ConfigureFonts(fonts => fonts.AddFont("FontAwesome.otf", "FontAwesome")) + .ConfigureMauiHandlers(handlers => + { +#if IOS || MACCATALYST + handlers.AddHandler(); + handlers.AddHandler(); +#endif + }); // App Shell builder.Services.AddTransient(); @@ -25,8 +30,8 @@ public static MauiApp CreateMauiApp() builder.Services.AddSingleton(Preferences.Default); builder.Services.AddSingleton(); builder.Services.AddRefitClient() - .ConfigureHttpClient(client => client.BaseAddress = new Uri("https://hacker-news.firebaseio.com/v0")) - .AddStandardResilienceHandler(options => options.Retry = new MobileHttpRetryStrategyOptions()); + .ConfigureHttpClient(client => client.BaseAddress = new Uri("https://hacker-news.firebaseio.com/v0")) + .AddStandardResilienceHandler(options => options.Retry = new MobileHttpRetryStrategyOptions()); // Pages + View Models builder.Services.AddTransient(); From 0411cdd2772cd7e40323727597ba20c02e995c81 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:25:50 -0800 Subject: [PATCH 47/59] Use Primary Constructors --- .../ViewModels/NewsDetailViewModel.cs | 9 ++------- .../ViewModels/NewsViewModel.cs | 20 +++++++------------ 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs index 01b5607c..3a5129b2 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs @@ -1,8 +1,8 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class NewsDetailViewModel : BaseViewModel, IQueryAttributable +sealed partial class NewsDetailViewModel(IBrowser browser) : BaseViewModel, IQueryAttributable { - readonly IBrowser browser; + readonly IBrowser browser = browser; [ObservableProperty] Uri? uri; @@ -13,11 +13,6 @@ sealed partial class NewsDetailViewModel : BaseViewModel, IQueryAttributable [ObservableProperty] string scoreDescription = string.Empty; - public NewsDetailViewModel(IBrowser browser) - { - this.browser = browser; - } - [RelayCommand] Task OpenBrowser() { diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs index 7850b13e..9cb84d59 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsViewModel.cs @@ -1,25 +1,19 @@ namespace CommunityToolkit.Maui.Markup.Sample.ViewModels; -sealed partial class NewsViewModel : BaseViewModel, IDisposable +sealed partial class NewsViewModel( + IDispatcher dispatcher, + SettingsService settingsService, + HackerNewsAPIService hackerNewsApiService) : BaseViewModel, IDisposable { - readonly IDispatcher dispatcher; - readonly SettingsService settingsService; - readonly HackerNewsAPIService hackerNewsAPIService; + readonly IDispatcher dispatcher = dispatcher; + readonly SettingsService settingsService = settingsService; + readonly HackerNewsAPIService hackerNewsAPIService = hackerNewsApiService; readonly WeakEventManager pullToRefreshEventManager = new(); readonly SemaphoreSlim insertIntoSortedCollectionSemaphore = new(1, 1); [ObservableProperty] bool isListRefreshing; - public NewsViewModel(IDispatcher dispatcher, - SettingsService settingsService, - HackerNewsAPIService hackerNewsAPIService) - { - this.dispatcher = dispatcher; - this.settingsService = settingsService; - this.hackerNewsAPIService = hackerNewsAPIService; - } - public event EventHandler PullToRefreshFailed { add => pullToRefreshEventManager.AddEventHandler(value); From 1027ebbf8757715adbcb8521fd3cc244a843c9a0 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:42:32 -0800 Subject: [PATCH 48/59] Use partial properties --- .../CommunityToolkit.Maui.Markup.Sample.csproj | 7 ++----- .../ViewModels/NewsDetailViewModel.cs | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj index 67dea726..36190c8b 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj +++ b/samples/CommunityToolkit.Maui.Markup.Sample/CommunityToolkit.Maui.Markup.Sample.csproj @@ -35,10 +35,7 @@ CsWinRT1028 - + @@ -61,7 +58,7 @@ - + diff --git a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs index 3a5129b2..864eb8d2 100644 --- a/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs +++ b/samples/CommunityToolkit.Maui.Markup.Sample/ViewModels/NewsDetailViewModel.cs @@ -5,13 +5,13 @@ sealed partial class NewsDetailViewModel(IBrowser browser) : BaseViewModel, IQue readonly IBrowser browser = browser; [ObservableProperty] - Uri? uri; + public partial Uri? Uri { get; private set; } [ObservableProperty] - string title = string.Empty; + public partial string Title { get; set; } = string.Empty; [ObservableProperty] - string scoreDescription = string.Empty; + public partial string ScoreDescription { get; set; } = string.Empty; [RelayCommand] Task OpenBrowser() From 3e9a1441daeac5b87f14f4d958ceaa51d3394541 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:42:39 -0800 Subject: [PATCH 49/59] Organize properties --- Directory.Build.props | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8da76ba1..75b381aa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,20 +8,24 @@ enable enable NETSDK1023 - true - all - true true false true true - true - true - + false false + + enable + all + + + true + true + true + + CsWinRT1028 From f54775fc2348ee6d293acfa54b89ff2b90bc4831 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:49:04 -0800 Subject: [PATCH 55/59] Update azure-pipelines.yml --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 57631358..88046569 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -67,7 +67,7 @@ jobs: inputs: packageType: 'sdk' version: '$(NET_VERSION)' - includePreviewVersions: true + includePreviewVersions: false - task: CmdLine@2 displayName: 'Install .NET MAUI Workload' From f306f8d8be793125f297b3db598c0c17fbb9b592 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:03:14 -0800 Subject: [PATCH 56/59] Update azure-pipelines.yml --- azure-pipelines.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 88046569..a948a1d1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -69,6 +69,13 @@ jobs: version: '$(NET_VERSION)' includePreviewVersions: false + - task: JavaToolInstaller@0 + displayName: "Install Java" + inputs: + versionSpec: "17" + jdkArchitectureOption: "x64" + jdkSourceOption: "PreInstalled" + - task: CmdLine@2 displayName: 'Install .NET MAUI Workload' inputs: @@ -137,6 +144,13 @@ jobs: version: '$(NET_VERSION)' includePreviewVersions: false + - task: JavaToolInstaller@0 + displayName: "Install Java" + inputs: + versionSpec: "17" + jdkArchitectureOption: "x64" + jdkSourceOption: "PreInstalled" + - task: CmdLine@2 displayName: 'Install .NET MAUI Workload' inputs: From 8a11480edd141fb8373c750d45422c632a32d208 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:02:27 -0800 Subject: [PATCH 57/59] Revert to macOS-14 --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a948a1d1..fe1e2a5e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,7 +51,7 @@ jobs: 'Windows': image: 'windows-latest' 'macOS': - image: 'macos-15' + image: 'macos-14' pool: vmImage: $(image) steps: @@ -106,7 +106,7 @@ jobs: 'Windows': image: 'windows-latest' 'macOS': - image: 'macos-15' + image: 'macos-14' pool: vmImage: $(image) steps: From 3d9ae6e6ffe927e356a35c4531689c0d298e5e38 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:59:59 -0800 Subject: [PATCH 58/59] Revert "Revert to macOS-14" This reverts commit 8a11480edd141fb8373c750d45422c632a32d208. --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fe1e2a5e..a948a1d1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,7 +51,7 @@ jobs: 'Windows': image: 'windows-latest' 'macOS': - image: 'macos-14' + image: 'macos-15' pool: vmImage: $(image) steps: @@ -106,7 +106,7 @@ jobs: 'Windows': image: 'windows-latest' 'macOS': - image: 'macos-14' + image: 'macos-15' pool: vmImage: $(image) steps: From 19b67adf744d8145fdbfbc10d9df8564389206c3 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:00:38 -0800 Subject: [PATCH 59/59] Update azure-pipelines.yml --- azure-pipelines.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a948a1d1..16fbcd89 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -79,7 +79,9 @@ jobs: - task: CmdLine@2 displayName: 'Install .NET MAUI Workload' inputs: - script : 'dotnet workload install maui' + script : | + dotnet workload install maui + dotnet workload update - pwsh: | Invoke-WebRequest 'https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.ps1' -OutFile 'workload-install.ps1' @@ -154,7 +156,9 @@ jobs: - task: CmdLine@2 displayName: 'Install .NET MAUI Workload' inputs: - script : 'dotnet workload install maui' + script : | + dotnet workload install maui + dotnet workload update - pwsh: | Invoke-WebRequest 'https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.ps1' -OutFile 'workload-install.ps1'