diff --git a/.gitignore b/.gitignore
index d82850c71066..8f457824a432 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ templatesTest/
*.user
*.userosscache
*.sln.docstates
+/src/Controls/tests/TestCases.HostApp/MauiProgram.user.cs
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/docs/design/UITesting.md b/docs/design/UITesting.md
index a605ff4daac9..e9055b912dae 100644
--- a/docs/design/UITesting.md
+++ b/docs/design/UITesting.md
@@ -26,6 +26,25 @@ This will be the majority of new tests added which will be primarily for testing
You will need to create some kind of UI to test against, which will go in the Controls.TestCases.HostApp project. This will be an actual MAUI app under test and has no dependency on Appium. Create a new class within `src/Controls/tests/TestCases.HostApp/Issues` and attribute it with `[Issue]`. Create it like any normal page you would make in app to reproduce the issue. This could be just in XAML or just code, along with a screenshot.
This project requires a rebuild after making changes because the `Controls.TestCases.Shared.Tests` is loosely coupled to this project. It initiates a previously compiled executable from `src/Controls/tests/TestCases.HostApp`.
+When working on a single test case it may be convenient to start the app by using the issue page as the main one.
+This can be easily achieved by creating a `MauiProgram.user.cs` file in the `src/Controls/tests/TestCases.HostApp` folder and adding the following code:
+
+```csharp
+using Controls.TestCases.HostApp.Issues;
+
+namespace Maui.Controls.Sample;
+
+public partial class MauiProgram
+{
+ static partial void OverrideMainPage(ref Page mainPage)
+ {
+ mainPage = new Issue99999(); // My issue number here
+ }
+}
+```
+
+That file is not tracked by git, so it won't be committed by mistake.
+
## Adding a Test to Interact with the Reproduction
Next you will need to create the appium test in the `Controls.TestCases.Shared.Tests` project, which is a library project that runs NUnit tests via Appium. Add a new class with the same name as the Reproduction within this folder: `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues`. Have the class derive from `_IssuesUITest` and add your test(s) as methods.
diff --git a/eng/Publishing.props b/eng/Publishing.props
index b31e222bdfda..96b340af297c 100644
--- a/eng/Publishing.props
+++ b/eng/Publishing.props
@@ -1,21 +1,21 @@
-
- 3
- true
- $(PublishDependsOnTargets);_PublishBlobItems
-
+
+ 3
+ true
+
-
- <_InstallerManifestFilesToPublish Include="$(ArtifactsShippingPackagesDir)\**\*.zip" />
+
+ <_InstallersToPublish Include="$(ArtifactsShippingPackagesDir)\**\*.zip" />
-
-
-
- true
- true
- $(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension)
-
-
-
+
+ <_UploadPathRoot>maui-sdk
+
+
+
+
+ true
+ false
+
+
\ No newline at end of file
diff --git a/eng/Signing.props b/eng/Signing.props
index 6fa82b8d6dcc..d4f40599ae49 100644
--- a/eng/Signing.props
+++ b/eng/Signing.props
@@ -19,10 +19,10 @@
-
-
-
-
+
+
+
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index d225b72ed2af..c8ee07b24f42 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -149,37 +149,37 @@
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
-
+
https://github.com/dotnet/arcade
- f33d9e642f0e68a61312164cd9e0baf4e142a999
+ 5ba9ca776c1d0bb72b2791591e54cf51fc52dfee
diff --git a/eng/Versions.props b/eng/Versions.props
index aea0c799a7cc..b0c5db2301b0 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -134,13 +134,13 @@
0.9.0
4.2.3
9.0.0
- 9.0.0-beta.25161.4
- 9.0.0-beta.25161.4
- 9.0.0-beta.25161.4
- 9.0.0-beta.25161.4
+ 9.0.0-beta.25164.2
+ 9.0.0-beta.25164.2
+ 9.0.0-beta.25164.2
+ 9.0.0-beta.25164.2
1.1.87-gba258badda
- 9.0.0-beta.25161.4
- 9.0.0-beta.25161.4
+ 9.0.0-beta.25164.2
+ 9.0.0-beta.25164.2
17.6.0
diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1
index a46b6deb7598..22b49e09d09b 100644
--- a/eng/common/tools.ps1
+++ b/eng/common/tools.ps1
@@ -42,7 +42,7 @@
[bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true }
# Enable repos to use a particular version of the on-line dotnet-install scripts.
-# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1
+# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1
[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' }
# True to use global NuGet cache instead of restoring packages to repository-local directory.
@@ -262,7 +262,7 @@ function GetDotNetInstallScript([string] $dotnetRoot) {
if (!(Test-Path $installScript)) {
Create-Directory $dotnetRoot
$ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit
- $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1"
+ $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1"
Retry({
Write-Host "GET $uri"
diff --git a/eng/common/tools.sh b/eng/common/tools.sh
index 1159726a10fd..01b09b65796c 100755
--- a/eng/common/tools.sh
+++ b/eng/common/tools.sh
@@ -54,7 +54,7 @@ warn_as_error=${warn_as_error:-true}
use_installed_dotnet_cli=${use_installed_dotnet_cli:-true}
# Enable repos to use a particular version of the on-line dotnet-install scripts.
-# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh
+# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh
dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'}
# True to use global NuGet cache instead of restoring packages to repository-local directory.
@@ -295,7 +295,7 @@ function with_retries {
function GetDotNetInstallScript {
local root=$1
local install_script="$root/dotnet-install.sh"
- local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh"
+ local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh"
if [[ ! -a "$install_script" ]]; then
mkdir -p "$root"
diff --git a/eng/pipelines/arcade/setup-test-env.yml b/eng/pipelines/arcade/setup-test-env.yml
index 45a26baa8041..655ca1cf7b15 100644
--- a/eng/pipelines/arcade/setup-test-env.yml
+++ b/eng/pipelines/arcade/setup-test-env.yml
@@ -11,6 +11,18 @@ steps:
fetchDepth: 1
clean: true
+- template: /eng/pipelines/common/provision.yml@self
+ parameters:
+ checkoutDirectory: '$(System.DefaultWorkingDirectory)'
+ skipJdk: false
+ skipAndroidCommonSdks: false
+ skipAndroidPlatformApis: false
+ onlyAndroidPlatformDefaultApis: true
+ skipAndroidEmulatorImages: true
+ skipAndroidCreateAvds: true
+ skipProvisioning: true
+ skipXcode: true
+
- script: |
sudo xcode-select -s /Applications/Xcode_$(REQUIRED_XCODE).app
xcrun xcode-select --print-path
diff --git a/eng/pipelines/arcade/stage-pack.yml b/eng/pipelines/arcade/stage-pack.yml
index b7bdecf9a6de..93ba36c2fbac 100644
--- a/eng/pipelines/arcade/stage-pack.yml
+++ b/eng/pipelines/arcade/stage-pack.yml
@@ -1,5 +1,11 @@
# Template for build + pack on dnceng
parameters:
+- name: prepareSteps
+ type: stepList
+ default: []
+- name: postSteps
+ type: stepList
+ default: []
- name: pool
type: object
- name: enableSourceBuild
@@ -44,7 +50,12 @@ stages:
- checkout: self
fetchDepth: 1
clean: true
- steps:
+
+ steps:
+ - ${{ each step in parameters.prepareSteps }}:
+ - ${{ each pair in step }}:
+ ${{ pair.key }}: ${{ pair.value }}
+
- script: $(_buildScript)
-restore -build
-configuration $(_BuildConfig)
@@ -70,11 +81,10 @@ stages:
- script: $(_buildScript)
-restore -pack
-sign $(_SignArgs)
- -publish $(_PublishArgs)
-configuration $(_BuildConfig)
/bl:$(Build.Arcade.LogsPath)pack.binlog
$(_OfficialBuildIdArgs)
- displayName: Pack, Sign & Publish
+ displayName: Pack, Sign
# only for workloads
- script: $(_buildScript)
@@ -85,10 +95,4 @@ stages:
/bl:$(Build.Arcade.LogsPath)/build-workloads.binlog
-projects src/Workload/workloads.csproj
$(_OfficialBuildIdArgs)
- displayName: Build Workloads
-
- - task: 1ES.PublishPipelineArtifact@1
- displayName: Publish VSDrop MSIs
- inputs:
- targetPath: '$(Build.SourcesDirectory)/artifacts/VSSetup/$(_BuildConfig)'
- artifactName: VSDropInsertion
+ displayName: Build Workloads, Sign & Publish
diff --git a/eng/pipelines/azure-pipelines-internal.yml b/eng/pipelines/azure-pipelines-internal.yml
index 6d8f1dfc07cd..1d8a593952d0 100644
--- a/eng/pipelines/azure-pipelines-internal.yml
+++ b/eng/pipelines/azure-pipelines-internal.yml
@@ -2,7 +2,6 @@ trigger:
branches:
include:
- main
- - net9.0
- net10.0
- release/*
tags:
@@ -27,7 +26,6 @@ schedules:
branches:
include:
- main
- - net9.0
- net10.0
variables:
@@ -75,14 +73,27 @@ extends:
suppression:
suppressionFile: $(Build.SourcesDirectory)\eng\automation\guardian\source.gdnsuppress
stages:
+
- template: /eng/pipelines/arcade/stage-pack.yml@self
parameters:
pool: ${{ parameters.VM_IMAGE_HOST }}
- enableSourceIndex: true
+ enableSourceIndex: false
runAsPublic: false
sourceIndexParams:
sourceIndexBuildCommand: build.cmd -restore -build -ci /bl:$(Build.Arcade.LogsPath)sourceIndexBuild.binlog /p:OfficialBuildId=$(_BuildOfficalId) /p:_SkipUpdateBuildNumber=true
binlogPath: $(Build.Arcade.LogsPath)sourceIndexBuild.binlog
+ prepareSteps:
+ - template: /eng/pipelines/common/provision.yml@self
+ parameters:
+ checkoutDirectory: '$(System.DefaultWorkingDirectory)'
+ skipJdk: false
+ skipAndroidCommonSdks: false
+ skipAndroidPlatformApis: false
+ onlyAndroidPlatformDefaultApis: true
+ skipAndroidEmulatorImages: true
+ skipAndroidCreateAvds: true
+ skipProvisioning: true
+ skipXcode: true
# Publish and validation steps. Only run in official builds
- template: /eng/common/templates-official/post-build/post-build.yml@self
diff --git a/eng/pipelines/azure-pipelines-public.yml b/eng/pipelines/azure-pipelines-public.yml
index 58a2176a8f40..3ffcbc533721 100644
--- a/eng/pipelines/azure-pipelines-public.yml
+++ b/eng/pipelines/azure-pipelines-public.yml
@@ -43,8 +43,8 @@ parameters:
- name: VM_IMAGE_HOST
type: object
default:
- name: Azure Pipelines
- vmImage: $(HostedWindowsImage)
+ name: NetCore-Public
+ image: 1es-windows-2022
os: windows
stages:
@@ -53,6 +53,18 @@ stages:
parameters:
pool: ${{ parameters.VM_IMAGE_HOST }}
runAsPublic: true
+ prepareSteps:
+ - template: /eng/pipelines/common/provision.yml@self
+ parameters:
+ checkoutDirectory: '$(System.DefaultWorkingDirectory)'
+ skipJdk: false
+ skipAndroidCommonSdks: false
+ skipAndroidPlatformApis: false
+ onlyAndroidPlatformDefaultApis: true
+ skipAndroidEmulatorImages: true
+ skipAndroidCreateAvds: true
+ skipProvisioning: true
+ skipXcode: true
- template: /eng/pipelines/arcade/stage-unit-tests.yml@self
parameters:
diff --git a/eng/pipelines/common/device-tests-steps.yml b/eng/pipelines/common/device-tests-steps.yml
index 62d095c8321f..40b296025be1 100644
--- a/eng/pipelines/common/device-tests-steps.yml
+++ b/eng/pipelines/common/device-tests-steps.yml
@@ -44,7 +44,7 @@ steps:
displayName: Enable KVM
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- # Provision the variaous SDKs that are needed
+ # Provision the various SDKs that are needed
- template: provision.yml
parameters:
skipXcode: ${{ or(eq(parameters.platform, 'android'), eq(parameters.platform, 'windows')) }}
diff --git a/eng/pipelines/common/provision.yml b/eng/pipelines/common/provision.yml
index e31ccd211d92..5f41bec602d9 100644
--- a/eng/pipelines/common/provision.yml
+++ b/eng/pipelines/common/provision.yml
@@ -36,13 +36,14 @@ steps:
##################################################
# Provisioning macOS - Agent cleanser
- - template: agent-cleanser/v1.yml@yaml-templates
- parameters:
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin'))
- UninstallMono: false
- UninstallXamarinMac: true
- CleanseAgentToolsDotNet: true
- SelfHealPowerShell: false
+ - ${{ if ne(parameters.skipProvisionator, true) }}:
+ - template: agent-cleanser/v1.yml@yaml-templates
+ parameters:
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin'))
+ UninstallMono: false
+ UninstallXamarinMac: true
+ CleanseAgentToolsDotNet: true
+ SelfHealPowerShell: false
# Provisioning macOS - Xcode
- ${{ if ne(parameters.skipXcode, 'true') }}:
diff --git a/eng/pipelines/common/ui-tests-steps.yml b/eng/pipelines/common/ui-tests-steps.yml
index c0e0b8f46df6..aeeae13db741 100644
--- a/eng/pipelines/common/ui-tests-steps.yml
+++ b/eng/pipelines/common/ui-tests-steps.yml
@@ -36,6 +36,15 @@ steps:
displayName: 'Clean bot'
continueOnError: true
timeoutInMinutes: 60
+
+ # Enable KVM for Android builds on Linux
+ - ${{ if and(ne(parameters.buildType, 'buildOnly'), eq(parameters.platform, 'android')) }}:
+ - bash: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
+ displayName: Enable KVM
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- ${{ if eq(parameters.platform, 'catalyst')}}:
- bash: |
@@ -50,9 +59,11 @@ steps:
skipProvisioning: true
skipJdk: ${{ ne(parameters.platform, 'android') }}
skipAndroidCommonSdks: ${{ ne(parameters.platform, 'android') }}
- skipAndroidPlatformApis: ${{ ne(parameters.platform, 'android') }}
+ skipAndroidPlatformApis: true
+ onlyAndroidPlatformDefaultApis: true
skipAndroidEmulatorImages: ${{ ne(parameters.platform, 'android') }}
- skipAndroidCreateAvds: ${{ ne(parameters.platform, 'android') }}
+ skipAndroidCreateAvds: true
+ androidEmulatorApiLevel: ${{ parameters.version }}
skipXcode: ${{ or(eq(parameters.platform, 'android'), eq(parameters.platform, 'windows')) }}
provisionatorChannel: ${{ parameters.provisionatorChannel }}
${{ if parameters.skipProvisioning }}:
@@ -62,7 +73,7 @@ steps:
gitHubToken: $(github--pat--vs-mobiletools-engineering-service2)
- task: PowerShell@2
- condition: ne('${{ parameters.platform }}' , 'windows')
+ condition: and(ne('${{ parameters.platform }}', 'windows'), ne('${{ parameters.platform }}', 'android'))
inputs:
targetType: 'inline'
script: |
diff --git a/eng/pipelines/common/ui-tests.yml b/eng/pipelines/common/ui-tests.yml
index 23ac76bde0a6..4712098c7e5d 100644
--- a/eng/pipelines/common/ui-tests.yml
+++ b/eng/pipelines/common/ui-tests.yml
@@ -1,5 +1,6 @@
parameters:
androidPool: { }
+ androidLinuxPool: { }
iosPool: { }
windowsPool: { }
windowsBuildPool: { }
@@ -108,7 +109,7 @@ stages:
workspace:
clean: all
displayName: ${{ coalesce(project.desc, project.name) }} (API ${{ api }})
- pool: ${{ parameters.androidPool }}
+ pool: ${{ parameters.androidLinuxPool }}
variables:
REQUIRED_XCODE: $(DEVICETESTS_REQUIRED_XCODE)
APPIUM_HOME: $(System.DefaultWorkingDirectory)/.appium/
diff --git a/eng/pipelines/ui-tests.yml b/eng/pipelines/ui-tests.yml
index 58f9ab6319a3..c9db6cf73888 100644
--- a/eng/pipelines/ui-tests.yml
+++ b/eng/pipelines/ui-tests.yml
@@ -73,6 +73,14 @@ parameters:
demands:
- macOS.Name -equals Sequoia
- macOS.Architecture -equals x64
+
+ - name: androidPoolLinux
+ type: object
+ default:
+ name: $(1ESPTPool)
+ vmImage: $(androidTestsVmImage)
+ demands:
+ - ImageOverride -equals 1ESPT-Ubuntu22.04
- name: iosPool
type: object
@@ -132,6 +140,7 @@ stages:
- template: common/ui-tests.yml
parameters:
androidPool: ${{ parameters.androidPool }}
+ androidLinuxPool: ${{ parameters.androidPoolLinux }}
iosPool: ${{ parameters.iosPool }}
windowsPool: ${{ parameters.windowsPool }}
windowsBuildPool: ${{ parameters.windowsBuildPool }}
diff --git a/global.json b/global.json
index 12412bd3ddd3..679feeee8d3e 100644
--- a/global.json
+++ b/global.json
@@ -5,7 +5,7 @@
"msbuild-sdks": {
"MSBuild.Sdk.Extras": "3.0.44",
"Microsoft.Build.NoTargets": "3.7.0",
- "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25161.4"
+ "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25164.2"
},
"sdk": {
"allowPrerelease": false
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs
index 43245bf020d9..07ecdcd1badc 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTabbedPageSwipePage.xaml.cs
@@ -8,20 +8,11 @@ namespace Maui.Controls.Sample.Pages
{
public partial class AndroidTabbedPageSwipePage : Microsoft.Maui.Controls.TabbedPage
{
- ICommand? _returnToPlatformSpecificsPage;
-
public AndroidTabbedPageSwipePage()
{
InitializeComponent();
}
- public AndroidTabbedPageSwipePage(ICommand restore)
- {
- InitializeComponent();
-
- _returnToPlatformSpecificsPage = restore;
- }
-
void OnSwipePagingButtonClicked(object sender, EventArgs e)
{
On().SetIsSwipePagingEnabled(!On().IsSwipePagingEnabled());
@@ -34,7 +25,7 @@ void OnSmoothScrollButtonClicked(object sender, EventArgs e)
void OnReturnButtonClicked(object sender, EventArgs e)
{
- _returnToPlatformSpecificsPage?.Execute(null);
+ Navigation.PopAsync();
}
}
}
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs
index 1211992f5167..2c681cd7bdb8 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/Android/AndroidTitleViewPage.xaml.cs
@@ -6,25 +6,14 @@ namespace Maui.Controls.Sample.Pages
{
public partial class AndroidTitleViewPage : ContentPage
{
- readonly ICommand? _returnToPlatformSpecificsPage;
-
public AndroidTitleViewPage()
{
InitializeComponent();
}
- public AndroidTitleViewPage(ICommand restore)
- {
- InitializeComponent();
- _returnToPlatformSpecificsPage = restore;
- }
-
void OnReturnButtonClicked(object sender, EventArgs e)
{
- if (_returnToPlatformSpecificsPage == null)
- return;
-
- _returnToPlatformSpecificsPage.Execute(null);
+ Navigation.PopAsync();
}
}
}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs
index 1f0abc03c9ff..92cad19ebad7 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSLargeTitlePage.xaml.cs
@@ -8,12 +8,9 @@ namespace Maui.Controls.Sample.Pages
{
public partial class iOSLargeTitlePage : ContentPage
{
- ICommand _returnToPlatformSpecificsPage;
-
- public iOSLargeTitlePage(ICommand restore)
+ public iOSLargeTitlePage()
{
InitializeComponent();
- _returnToPlatformSpecificsPage = restore;
}
void OnButtonClicked(object sender, EventArgs e)
@@ -34,7 +31,7 @@ void OnButtonClicked(object sender, EventArgs e)
void OnReturnButtonClicked(object sender, EventArgs e)
{
- _returnToPlatformSpecificsPage.Execute(null);
+ Navigation.PopAsync();
}
}
}
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs
index 0e8c1d513ac5..6c9a340b2760 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSScrollViewPage.xaml.cs
@@ -7,12 +7,9 @@ namespace Maui.Controls.Sample.Pages
{
public partial class iOSScrollViewPage : Microsoft.Maui.Controls.FlyoutPage
{
- ICommand _returnToPlatformSpecificsPage;
-
- public iOSScrollViewPage(ICommand restore)
+ public iOSScrollViewPage()
{
InitializeComponent();
- _returnToPlatformSpecificsPage = restore;
}
void OnButtonClicked(object sender, EventArgs e)
@@ -22,7 +19,7 @@ void OnButtonClicked(object sender, EventArgs e)
void OnReturnButtonClicked(object sender, EventArgs e)
{
- _returnToPlatformSpecificsPage.Execute(null);
+ Navigation.PopAsync();
}
}
}
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs
index 8f1990190b63..5f370facd790 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTitleViewPage.xaml.cs
@@ -6,19 +6,15 @@ namespace Maui.Controls.Sample.Pages
{
public partial class iOSTitleViewPage : ContentPage
{
- ICommand _returnToPlatformSpecificsPage;
-
- public iOSTitleViewPage(ICommand restore)
+ public iOSTitleViewPage()
{
InitializeComponent();
-
- _returnToPlatformSpecificsPage = restore;
_searchBar.Effects.Add(Effect.Resolve("XamarinDocs.SearchBarEffect"));
}
void OnReturnButtonClicked(object sender, EventArgs e)
{
- _returnToPlatformSpecificsPage.Execute(null);
+ Navigation.PopAsync();
}
}
}
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs
index 1c706d495df3..4a5923a12535 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentNavigationBarPage.xaml.cs
@@ -8,12 +8,9 @@ namespace Maui.Controls.Sample.Pages
{
public partial class iOSTranslucentNavigationBarPage : ContentPage
{
- ICommand _returnToPlatformSpecificsPage;
-
- public iOSTranslucentNavigationBarPage(ICommand restore)
+ public iOSTranslucentNavigationBarPage()
{
InitializeComponent();
- _returnToPlatformSpecificsPage = restore;
}
void OnTranslucentNavigationBarButtonClicked(object sender, EventArgs e)
@@ -23,7 +20,7 @@ void OnTranslucentNavigationBarButtonClicked(object sender, EventArgs e)
void OnReturnButtonClicked(object sender, EventArgs e)
{
- _returnToPlatformSpecificsPage.Execute(null);
+ Navigation.PopAsync();
}
}
}
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs
index d3aecf4a0e21..b65ec80ce919 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSTranslucentTabbedPage.xaml.cs
@@ -2,17 +2,13 @@
using System.Windows.Input;
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
-
namespace Maui.Controls.Sample.Pages
{
public partial class iOSTranslucentTabbedPage : Microsoft.Maui.Controls.TabbedPage
{
- ICommand returnToPlatformSpecificsPage;
-
- public iOSTranslucentTabbedPage(ICommand restore)
+ public iOSTranslucentTabbedPage()
{
InitializeComponent();
- returnToPlatformSpecificsPage = restore;
}
void OnToggleButtonClicked(object sender, EventArgs e)
@@ -33,7 +29,7 @@ void OnToggleButtonClicked(object sender, EventArgs e)
void OnReturnButtonClicked(object sender, EventArgs e)
{
- returnToPlatformSpecificsPage.Execute(null);
+ Navigation.PopAsync();
}
}
}
diff --git a/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs
index 11efd4790074..ffc6c5fa5a65 100644
--- a/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs
+++ b/src/Controls/samples/Controls.Sample/ViewModels/PlatformSpecificsViewModel.cs
@@ -111,7 +111,7 @@ protected override IEnumerable CreateItems()
new SectionModel(typeof(iOSTranslucentNavigationBarPage), "NavigationPage Translucent TabBar",
"This iOS platform-specific is used to set the translucency mode of the tab bar on a NavigationPage."),
- new SectionModel(typeof(MainPage), "TabbedPage Translucent TabBar",
+ new SectionModel(typeof(iOSTranslucentTabbedPage), "TabbedPage Translucent TabBar",
"This iOS platform-specific is used to set the translucency mode of the tab bar on a TabbedPage."),
#if MACCATALYST
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs
index a3ae0cd48fd4..ac7a7ff248f9 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs
@@ -610,6 +610,7 @@ public override void PushViewController(UIViewController viewController, bool an
public override UIViewController PopViewController(bool animated)
{
+ _popRequested = true;
_pendingViewControllers = null;
if (IsInMoreTab && ParentViewController is UITabBarController tabBarController)
{
diff --git a/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs b/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs
index 8e632698b3dc..255278a1b4a4 100644
--- a/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs
+++ b/src/Controls/src/Core/Compatibility/iOS/Extensions/ToolbarItemExtensions.cs
@@ -114,10 +114,6 @@ void UpdateIconAndStyle(ToolbarItem item)
{
Image = result?.Value;
Style = UIBarButtonItemStyle.Plain;
- if (item.IconImageSource is FontImageSource fontImageSource && fontImageSource.Color is not null)
- {
- TintColor = fontImageSource.Color.ToPlatform();
- }
});
}
}
diff --git a/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs b/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs
index 1c88ba1eb9ac..0838765d1d26 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs
@@ -271,6 +271,24 @@ void CollectionItemsSourceChanged(object sender, System.Collections.Specialized.
return;
}
+ // While Modifying the collection we should consider the ItemsUpdatingScrollMode to update the position
+ if (Carousel.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepLastItemInView)
+ {
+ if (count == 0)
+ {
+ carouselPosition = 0;
+ }
+ else
+ {
+ carouselPosition = count - 1;
+ }
+
+ }
+ else if (Carousel.ItemsUpdatingScrollMode == ItemsUpdatingScrollMode.KeepItemsInView)
+ {
+ carouselPosition = 0;
+ }
+
Carousel.
Handler.
MauiContext.
diff --git a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs
index 4098ac3b456b..272eec544592 100644
--- a/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs
+++ b/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Windows.cs
@@ -58,6 +58,7 @@ protected override void DisconnectHandler(ListViewBase platformView)
{
VirtualView.ScrollToRequested -= ScrollToRequested;
CleanUpCollectionViewSource(platformView);
+ _formsEmptyView?.Handler?.DisconnectHandler();
base.DisconnectHandler(platformView);
}
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs
index efdeffbb634a..ca07824e4a11 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs
@@ -334,6 +334,7 @@ void CollectionViewUpdating(object sender, NotifyCollectionChangedEventArgs e)
[UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e)
{
+ int targetPosition;
if (_positionAfterUpdate == -1)
{
return;
@@ -341,7 +342,9 @@ void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e)
_gotoPosition = -1;
- var targetPosition = _positionAfterUpdate;
+ // We need to update the position while modifying the collection.
+ targetPosition = GetTargetPosition();
+
_positionAfterUpdate = -1;
SetPosition(targetPosition);
@@ -354,6 +357,22 @@ int GetPositionWhenAddingItems(int carouselPosition, int currentItemPosition)
return currentItemPosition != -1 ? currentItemPosition : carouselPosition;
}
+ private int GetTargetPosition()
+ {
+
+ if (ItemsSource.ItemCount == 0)
+ {
+ return 0;
+ }
+
+ return ItemsView.ItemsUpdatingScrollMode switch
+ {
+ ItemsUpdatingScrollMode.KeepItemsInView => 0,
+ ItemsUpdatingScrollMode.KeepLastItemInView => ItemsSource.ItemCount - 1,
+ _ => _positionAfterUpdate
+ };
+ }
+
int GetPositionWhenResetItems()
{
//If we are reseting the collection Position should go to 0
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs
index c42f5f3a4aad..4111016b918a 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs
@@ -164,14 +164,7 @@ void CollectionChanged(NotifyCollectionChangedEventArgs args)
// Force UICollectionView to get the internal accounting straight
var collectionView = controller.CollectionView;
- if (!collectionView.Hidden)
- {
- var numberOfSections = collectionView.NumberOfSections();
- for (int section = 0; section < numberOfSections; section++)
- {
- collectionView.NumberOfItemsInSection(section);
- }
- }
+ UpdateSection(collectionView);
switch (args.Action)
{
@@ -193,8 +186,23 @@ void CollectionChanged(NotifyCollectionChangedEventArgs args)
default:
throw new ArgumentOutOfRangeException();
}
+
+ // Calculate section and item counts after processing changes
+ // to ensure UICollectionView reflects the updated state
+ UpdateSection(collectionView);
}
+ void UpdateSection(UICollectionView collectionView)
+ {
+ if (!collectionView.Hidden)
+ {
+ var numberOfSections = collectionView.NumberOfSections();
+ for (int section = 0; section < numberOfSections; section++)
+ {
+ collectionView.NumberOfItemsInSection(section);
+ }
+ }
+ }
void Reload(bool collectionWasReset = false)
{
ResetGroupTracking();
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs
index b7cfd68c3a19..c7b5d7f429e3 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs
@@ -254,6 +254,7 @@ void CollectionViewUpdating(object sender, NotifyCollectionChangedEventArgs e)
[UnconditionalSuppressMessage("Memory", "MEM0003", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e)
{
+ int targetPosition;
if (_positionAfterUpdate == -1)
{
return;
@@ -261,7 +262,9 @@ void CollectionViewUpdated(object sender, NotifyCollectionChangedEventArgs e)
//_gotoPosition = -1;
- var targetPosition = _positionAfterUpdate;
+ // We need to update the position while modifying the collection.
+ targetPosition = GetTargetPosition();
+
_positionAfterUpdate = -1;
SetPosition(targetPosition);
@@ -291,6 +294,21 @@ int GetPositionWhenAddingItems(int carouselPosition, int currentItemPosition)
return currentItemPosition != -1 ? currentItemPosition : carouselPosition;
}
+ private int GetTargetPosition()
+ {
+ if (ItemsSource.ItemCount == 0)
+ {
+ return 0;
+ }
+
+ return ItemsView.ItemsUpdatingScrollMode switch
+ {
+ ItemsUpdatingScrollMode.KeepItemsInView => 0,
+ ItemsUpdatingScrollMode.KeepLastItemInView => ItemsSource.ItemCount - 1,
+ _ => _positionAfterUpdate
+ };
+ }
+
int GetPositionWhenResetItems()
{
//If we are reseting the collection Position should go to 0
diff --git a/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs b/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs
index a6ef8ef550c8..93713f124de9 100644
--- a/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs
+++ b/src/Controls/src/Core/Internals/PropertyPropagationExtensions.cs
@@ -1,4 +1,5 @@
#nullable disable
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -8,6 +9,15 @@ namespace Microsoft.Maui.Controls.Internals
///
public static class PropertyPropagationExtensions
{
+ [Obsolete]
+ internal static void PropagatePropertyChanged(string propertyName, Element element, IEnumerable children)
+ {
+ if (children == null)
+ return;
+
+ PropagatePropertyChanged(propertyName, element, children.OfType().ToList());
+ }
+
internal static void PropagatePropertyChanged(string propertyName, Element element, IReadOnlyList children)
{
if (propertyName == null || propertyName == VisualElement.FlowDirectionProperty.PropertyName)
diff --git a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
index b93f41060075..5d6b9b990f24 100644
--- a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
+++ b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
@@ -10,6 +10,8 @@
using Android.Views;
using AndroidX.AppCompat.Widget;
using AndroidX.CoordinatorLayout.Widget;
+using AndroidX.Core.Content;
+using AndroidX.Core.Graphics;
using AndroidX.Fragment.App;
using AndroidX.ViewPager2.Widget;
using Google.Android.Material.AppBar;
@@ -386,7 +388,7 @@ void TabSelected(TabLayout.Tab tab)
if (Element.Children.Count > selectedIndex && selectedIndex >= 0)
Element.CurrentPage = Element.Children[selectedIndex];
- SetIconColorFilter(tab, true);
+ SetIconColorFilter(Element.CurrentPage, tab, true);
}
void TeardownPage(Page page)
@@ -525,10 +527,10 @@ void UpdateTabIcons()
}
}
- protected virtual void SetTabIconImageSource(TabLayout.Tab tab, Drawable icon)
+ protected virtual void SetTabIconImageSource(Page page, TabLayout.Tab tab, Drawable icon)
{
tab.SetIcon(icon);
- SetIconColorFilter(tab);
+ SetIconColorFilter(page, tab);
}
void SetTabIconImageSource(Page page, TabLayout.Tab tab)
@@ -537,7 +539,7 @@ void SetTabIconImageSource(Page page, TabLayout.Tab tab)
_context,
result =>
{
- SetTabIconImageSource(tab, result?.Value);
+ SetTabIconImageSource(page, tab, result?.Value);
});
}
@@ -655,25 +657,26 @@ protected virtual ColorStateList GetItemTextColorStates()
protected virtual ColorStateList GetItemIconTintColorState()
{
- if (IsBottomTabPlacement)
+ if (_orignalTabIconColors is null)
{
- if (_orignalTabIconColors is null)
- _orignalTabIconColors = _bottomNavigationView.ItemIconTintList;
+ _orignalTabIconColors = IsBottomTabPlacement ? _bottomNavigationView.ItemIconTintList : _tabLayout.TabIconTint;
}
- // this ensures that existing behavior doesn't change
- else if (!IsBottomTabPlacement && BarSelectedItemColor != null && BarItemColor == null)
- return null;
Color barItemColor = BarItemColor;
Color barSelectedItemColor = BarSelectedItemColor;
- if (barItemColor == null && barSelectedItemColor == null)
+ if (barItemColor is null && barSelectedItemColor is null)
+ {
return _orignalTabIconColors;
+ }
- if (_newTabIconColors != null)
+ if (_newTabIconColors is not null)
+ {
return _newTabIconColors;
+ }
int defaultColor;
+ int checkedColor;
if (barItemColor is not null)
{
@@ -681,50 +684,67 @@ protected virtual ColorStateList GetItemIconTintColorState()
}
else
{
-#pragma warning disable XAOBS001 // Obsolete
- var styledAttributes =
- TintTypedArray.ObtainStyledAttributes(_context.Context, null, Resource.Styleable.NavigationBarView, Resource.Attribute.bottomNavigationStyle, 0);
-#pragma warning restore XAOBS001 // Obsolete
+ defaultColor = GetDefaultColor();
+ }
+
+ if (barSelectedItemColor is not null)
+ {
+ checkedColor = barSelectedItemColor.ToPlatform().ToArgb();
+ }
+ else
+ {
+ checkedColor = GetDefaultColor();
+ }
+
+ _newTabIconColors = GetColorStateList(defaultColor, checkedColor);
+ return _newTabIconColors;
+ }
- try
+ int GetDefaultColor()
+ {
+ int defaultColor;
+ var styledAttributes =
+ _context.Context.Theme.ObtainStyledAttributes(
+ null,
+ Resource.Styleable.NavigationBarView,
+ Resource.Attribute.bottomNavigationStyle,
+ 0);
+
+ try
+ {
+ var defaultColors = styledAttributes.GetColorStateList(Resource.Styleable.NavigationBarView_itemIconTint);
+ if (defaultColors is not null)
+ {
+ defaultColor = defaultColors.DefaultColor;
+ }
+ else
{
- var defaultColors = styledAttributes.GetColorStateList(Resource.Styleable.NavigationBarView_itemIconTint);
- if (defaultColors is not null)
+ // These are the defaults currently set inside android
+ // It's very unlikely we'll hit this path because the
+ // NavigationBarView_itemIconTint should always resolve
+ // But just in case, we'll just hard code to some defaults
+ // instead of leaving the application in a broken state
+ if (IsDarkTheme)
{
- defaultColor = defaultColors.DefaultColor;
+ defaultColor = ColorUtils.SetAlphaComponent(
+ ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_light),
+ 153); // 60% opacity
}
else
{
- // These are the defaults currently set inside android
- // It's very unlikely we'll hit this path because the
- // NavigationBarView_itemIconTint should always resolve
- // But just in case, we'll just hard code to some defaults
- // instead of leaving the application in a broken state
- if (IsDarkTheme)
- defaultColor = new Color(1, 1, 1, 0.6f).ToPlatform();
- else
- defaultColor = new Color(0, 0, 0, 0.6f).ToPlatform();
+ defaultColor = ColorUtils.SetAlphaComponent(
+ ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_dark),
+ 153); // 60% opacity
}
}
- finally
- {
- styledAttributes.Recycle();
- }
}
-
- if (barItemColor == null && _orignalTabIconColors != null)
- defaultColor = _orignalTabIconColors.DefaultColor;
-
- int checkedColor = defaultColor;
-
- if (barSelectedItemColor != null)
- checkedColor = barSelectedItemColor.ToPlatform().ToArgb();
-
- _newTabIconColors = GetColorStateList(defaultColor, checkedColor);
- return _newTabIconColors;
+ finally
+ {
+ styledAttributes.Recycle();
+ }
+ return defaultColor;
}
-
void OnMoreSheetDismissed(object sender, EventArgs e)
{
var index = Element.Children.IndexOf(Element.CurrentPage);
@@ -759,11 +779,11 @@ void UpdateItemIconColor()
_bottomNavigationView.ItemIconTintList = GetItemIconTintColorState() ?? _orignalTabIconColors;
else
{
- var colors = GetItemIconTintColorState() ?? _orignalTabIconColors;
for (int i = 0; i < _tabLayout.TabCount; i++)
{
TabLayout.Tab tab = _tabLayout.GetTabAt(i);
- this.SetIconColorFilter(tab);
+ var page = Element.Children[i];
+ this.SetIconColorFilter(page, tab);
}
}
}
@@ -802,18 +822,18 @@ void UpdateBarTextColor()
_tabLayout.TabTextColors = _currentBarTextColorStateList;
}
- void SetIconColorFilter(TabLayout.Tab tab)
+ void SetIconColorFilter(Page page, TabLayout.Tab tab)
{
- SetIconColorFilter(tab, _tabLayout.GetTabAt(_tabLayout.SelectedTabPosition) == tab);
+ SetIconColorFilter(page, tab, _tabLayout.GetTabAt(_tabLayout.SelectedTabPosition) == tab);
}
- void SetIconColorFilter(TabLayout.Tab tab, bool selected)
+ void SetIconColorFilter(Page page, TabLayout.Tab tab, bool selected)
{
var icon = tab.Icon;
if (icon == null)
return;
- var colors = GetItemIconTintColorState();
+ ColorStateList colors = (page.IconImageSource is FontImageSource fontImageSource && fontImageSource.Color is not null) ? null : GetItemIconTintColorState();
if (colors == null)
ADrawableCompat.SetTintList(icon, null);
else
@@ -989,8 +1009,8 @@ void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
{
- _tabbedPageManager.SetIconColorFilter(tab, false);
+ _tabbedPageManager.SetIconColorFilter(_tabbedPageManager.Element.CurrentPage, tab, false);
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs
index a15e776586d1..1fe23332fa72 100644
--- a/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs
+++ b/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs
@@ -13,6 +13,7 @@ internal static class ToolbarExtensions
public static void UpdateIsVisible(this MauiToolbar platformToolbar, Toolbar toolbar)
{
platformToolbar.Visibility = (toolbar.IsVisible) ? UI.Xaml.Visibility.Visible : UI.Xaml.Visibility.Collapsed;
+ UpdateBackButtonVisibility(platformToolbar, toolbar);
}
public static void UpdateTitleIcon(this MauiToolbar platformToolbar, Toolbar toolbar)
@@ -35,8 +36,7 @@ public static void UpdateBackButton(this MauiToolbar platformToolbar, Toolbar to
platformToolbar.IsBackEnabled =
toolbar.BackButtonEnabled && toolbar.BackButtonVisible;
- platformToolbar
- .IsBackButtonVisible = (toolbar.BackButtonVisible) ? NavigationViewBackButtonVisible.Visible : NavigationViewBackButtonVisible.Collapsed;
+ UpdateBackButtonVisibility(platformToolbar, toolbar);
toolbar.Handler?.UpdateValue(nameof(Toolbar.BarBackground));
}
@@ -84,5 +84,13 @@ public static void UpdateToolbarDynamicOverflowEnabled(this MauiToolbar platform
platformToolbar.CommandBar.IsDynamicOverflowEnabled = toolbar.DynamicOverflowEnabled;
}
+
+ private static void UpdateBackButtonVisibility(MauiToolbar platformToolbar, Toolbar toolbar)
+ {
+ platformToolbar.IsBackButtonVisible =
+ toolbar.BackButtonVisible
+ ? NavigationViewBackButtonVisible.Visible
+ : NavigationViewBackButtonVisible.Collapsed;
+ }
}
}
diff --git a/src/Controls/src/Core/Stepper/Stepper.cs b/src/Controls/src/Core/Stepper/Stepper.cs
index f68bc03f2971..4c649b298fc2 100644
--- a/src/Controls/src/Core/Stepper/Stepper.cs
+++ b/src/Controls/src/Core/Stepper/Stepper.cs
@@ -12,7 +12,7 @@ public partial class Stepper : View, IElementConfiguration, IStepper
{
/// Bindable property for .
public static readonly BindableProperty MaximumProperty = BindableProperty.Create(nameof(Maximum), typeof(double), typeof(Stepper), 100.0,
- validateValue: (bindable, value) => (double)value > ((Stepper)bindable).Minimum,
+ validateValue: (bindable, value) => (double)value >= ((Stepper)bindable).Minimum,
coerceValue: (bindable, value) =>
{
var stepper = (Stepper)bindable;
@@ -22,7 +22,7 @@ public partial class Stepper : View, IElementConfiguration, IStepper
/// Bindable property for .
public static readonly BindableProperty MinimumProperty = BindableProperty.Create(nameof(Minimum), typeof(double), typeof(Stepper), 0.0,
- validateValue: (bindable, value) => (double)value < ((Stepper)bindable).Maximum,
+ validateValue: (bindable, value) => (double)value <= ((Stepper)bindable).Maximum,
coerceValue: (bindable, value) =>
{
var stepper = (Stepper)bindable;
diff --git a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs
index 82b35e999e42..025a2d4adff6 100644
--- a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.Windows.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System.ComponentModel;
+using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Graphics;
@@ -60,6 +61,23 @@ await AttachAndRun(border, (handler) =>
await AssertColorAtPoint(border, expected, typeof(BorderHandler), cornerRadius, cornerRadius);
}
+ [Fact]
+ [Description("The IsVisible property of a Border should match with native IsVisible")]
+ public async Task VerifyBorderIsVisibleProperty()
+ {
+ var border = new Border();
+ border.IsVisible = false;
+ var expectedValue = border.IsVisible;
+
+ var handler = await CreateHandlerAsync(border);
+ var nativeView = GetNativeBorder(handler);
+ await InvokeOnMainThreadAsync(() =>
+ {
+ var isVisible = nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible;
+ Assert.Equal(expectedValue, isVisible);
+ });
+ }
+
ContentPanel GetNativeBorder(BorderHandler borderHandler) =>
borderHandler.PlatformView;
diff --git a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs
index 4e3d9cb10525..1c981f97c74b 100644
--- a/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.DeviceTests.ImageAnalysis;
using Microsoft.Maui.Graphics;
@@ -258,6 +259,35 @@ await InvokeOnMainThreadAsync(() =>
await AssertionExtensions.WaitForGC(handlerReference, platformViewReference);
}
+
+ [Fact(DisplayName = "Border With Stroke Shape And Name Does Not Leak")]
+ public async Task DoesNotLeakWithStrokeShape()
+ {
+ SetupBuilder();
+ WeakReference platformViewReference = null;
+ WeakReference handlerReference = null;
+
+ await InvokeOnMainThreadAsync(() =>
+ {
+ var layout = new Grid();
+ var border = new Border();
+ var rect = new RoundRectangle();
+ border.StrokeShape = rect;
+ layout.Add(border);
+
+ var nameScope = new NameScope();
+ ((INameScope)nameScope).RegisterName("Border", border);
+ layout.transientNamescope = nameScope;
+ border.transientNamescope = nameScope;
+ rect.transientNamescope = nameScope;
+
+ var handler = CreateHandler(layout);
+ handlerReference = new WeakReference(border.Handler);
+ platformViewReference = new WeakReference(border.Handler.PlatformView);
+ });
+
+ await AssertionExtensions.WaitForGC(handlerReference, platformViewReference);
+ }
[Fact("Ensures the border renders the expected size - Issue 15339")]
public async Task BorderAndStrokeIsCorrectSize()
diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs
index e087b12bb3fc..0d84d49eb8e3 100644
--- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs
+++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs
@@ -1,15 +1,18 @@
using System;
+using System.ComponentModel;
using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
+using Xunit;
namespace Microsoft.Maui.DeviceTests
{
- public partial class BoxViewTests
- {
- MauiShapeView GetNativeBoxView(ShapeViewHandler boxViewViewHandler) =>
- boxViewViewHandler.PlatformView;
+ public partial class BoxViewTests
+ {
+ MauiShapeView GetNativeBoxView(ShapeViewHandler boxViewViewHandler) =>
+ boxViewViewHandler.PlatformView;
Task GetPlatformOpacity(ShapeViewHandler handler)
{
@@ -19,5 +22,88 @@ Task GetPlatformOpacity(ShapeViewHandler handler)
return nativeView.Alpha;
});
}
+
+ [Fact]
+ [Description("The ScaleX property of a BoxView should match with native ScaleX")]
+ public async Task ScaleXConsistent()
+ {
+ var boxView = new BoxView() { ScaleX = 0.45f };
+ var expected = boxView.ScaleX;
+ var handler = await CreateHandlerAsync(boxView);
+ var platformBoxView = GetNativeBoxView(handler);
+ var platformScaleX = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleX);
+ Assert.Equal(expected, platformScaleX);
+ }
+
+ [Fact]
+ [Description("The ScaleY property of a BoxView should match with native ScaleY")]
+ public async Task ScaleYConsistent()
+ {
+ var boxView = new BoxView() { ScaleY = 1.23f };
+ var expected = boxView.ScaleY;
+ var handler = await CreateHandlerAsync(boxView);
+ var platformBoxView = GetNativeBoxView(handler);
+ var platformScaleY = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleY);
+ Assert.Equal(expected, platformScaleY);
+ }
+
+ [Fact]
+ [Description("The Scale property of a BoxView should match with native Scale")]
+ public async Task ScaleConsistent()
+ {
+ var boxView = new BoxView() { Scale = 2.0f };
+ var expected = boxView.Scale;
+ var handler = await CreateHandlerAsync(boxView);
+ var platformBoxView = GetNativeBoxView(handler);
+ var platformScaleX = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleX);
+ var platformScaleY = await InvokeOnMainThreadAsync(() => platformBoxView.ScaleY);
+ Assert.Equal(expected, platformScaleX);
+ Assert.Equal(expected, platformScaleY);
+ }
+
+ [Fact]
+ [Description("The RotationX property of a BoxView should match with native RotationX")]
+ public async Task RotationXConsistent()
+ {
+ var boxView = new BoxView() { RotationX = 33.0 };
+ var expected = boxView.RotationX;
+ var handler = await CreateHandlerAsync(boxView);
+ var platformBoxView = GetNativeBoxView(handler);
+ var platformRotationX = await InvokeOnMainThreadAsync(() => platformBoxView.RotationX);
+ Assert.Equal(expected, platformRotationX);
+ }
+
+ [Fact]
+ [Description("The RotationY property of a BoxView should match with native RotationY")]
+ public async Task RotationYConsistent()
+ {
+ var boxView = new BoxView() { RotationY = 87.0 };
+ var expected = boxView.RotationY;
+ var handler = await CreateHandlerAsync(boxView);
+ var platformBoxView = GetNativeBoxView(handler);
+ var platformRotationY = await InvokeOnMainThreadAsync(() => platformBoxView.RotationY);
+ Assert.Equal(expected, platformRotationY);
+ }
+
+ [Fact]
+ [Description("The Rotation property of a BoxView should match with native Rotation")]
+ public async Task RotationConsistent()
+ {
+ var boxView = new BoxView() { Rotation = 23.0 };
+ var expected = boxView.Rotation;
+ var handler = await CreateHandlerAsync(boxView);
+ var platformBoxView = GetNativeBoxView(handler);
+ var platformRotation = await InvokeOnMainThreadAsync(() => platformBoxView.Rotation);
+ Assert.Equal(expected, platformRotation);
+ }
+
+ Task GetPlatformIsVisible(ShapeViewHandler boxViewViewHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetNativeBoxView(boxViewViewHandler);
+ return nativeView.Visibility == Android.Views.ViewStates.Visible;
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs
index 994e01829fd3..88e0735833b1 100644
--- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs
+++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs
@@ -19,5 +19,14 @@ Task GetPlatformOpacity(ShapeViewHandler handler)
return (float)nativeView.Opacity;
});
}
+
+ Task GetPlatformIsVisible(ShapeViewHandler boxViewHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetNativeBoxView(boxViewHandler);
+ return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible;
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs
index e6a20ffb1bcb..cf574048faf1 100644
--- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs
+++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs
@@ -6,7 +6,7 @@
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Xunit;
-
+using Microsoft.Maui.Controls.Handlers;
namespace Microsoft.Maui.DeviceTests
{
@@ -64,5 +64,21 @@ await InvokeOnMainThreadAsync(async () =>
Assert.Equal(expectedValue, nativeOpacityValue);
});
}
+
+ [Fact]
+ [Description("The IsVisible property of a BoxView should match with native IsVisible")]
+ public async Task VerifyBoxViewIsVisibleProperty()
+ {
+ var boxView = new BoxView();
+ boxView.IsVisible = false;
+ var expectedValue = boxView.IsVisible;
+
+ var handler = await CreateHandlerAsync(boxView);
+ await InvokeOnMainThreadAsync( async () =>
+ {
+ var isVisible = await GetPlatformIsVisible(handler);
+ Assert.Equal(expectedValue, isVisible);
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs
index 67c5fbc701a5..a5180aec06a2 100644
--- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs
+++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.iOS.cs
@@ -7,6 +7,8 @@
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Xunit;
+using System.ComponentModel;
+using Microsoft.Maui.Controls.Handlers;
namespace Microsoft.Maui.DeviceTests
{
@@ -20,7 +22,16 @@ Task GetPlatformOpacity(ShapeViewHandler handler)
return InvokeOnMainThreadAsync(() =>
{
var nativeView = GetNativeBoxView(handler);
- return (float)nativeView.Alpha;
+ return (float)nativeView.Alpha;
+ });
+ }
+
+ Task GetPlatformIsVisible(ShapeViewHandler boxViewHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetNativeBoxView(boxViewHandler);
+ return !nativeView.Hidden;
});
}
diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs
index b8b71fead772..c0a229238f06 100644
--- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Android.cs
@@ -34,6 +34,15 @@ Task GetPlatformOpacity(ButtonHandler buttonHandler)
});
}
+ Task GetPlatformIsVisible(ButtonHandler buttonHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetPlatformButton(buttonHandler);
+ return nativeView.Visibility == Android.Views.ViewStates.Visible;
+ });
+ }
+
[Theory(DisplayName = "Button Icon has Correct Position"), Category(TestCategory.Layout)]
[InlineData(Button.ButtonContentLayout.ImagePosition.Left)]
[InlineData(Button.ButtonContentLayout.ImagePosition.Top)]
@@ -107,5 +116,79 @@ await InvokeOnMainThreadAsync(async () =>
Assert.Equal(expectedValue, nativeOpacityValue);
});
}
+
+ [Fact]
+ [Description("The ScaleX property of a Button should match with native ScaleX")]
+ public async Task ScaleXConsistent()
+ {
+ var button = new Button() { ScaleX = 0.45f };
+ var expected = button.ScaleX;
+ var handler = await CreateHandlerAsync(button);
+ var platformButton = GetPlatformButton(handler);
+ var platformScaleX = await InvokeOnMainThreadAsync(() => platformButton.ScaleX);
+ Assert.Equal(expected, platformScaleX);
+ }
+
+ [Fact]
+ [Description("The ScaleY property of a Button should match with native ScaleY")]
+ public async Task ScaleYConsistent()
+ {
+ var button = new Button() { ScaleY = 1.23f };
+ var expected = button.ScaleY;
+ var handler = await CreateHandlerAsync(button);
+ var platformButton = GetPlatformButton(handler);
+ var platformScaleY = await InvokeOnMainThreadAsync(() => platformButton.ScaleY);
+ Assert.Equal(expected, platformScaleY);
+ }
+
+ [Fact]
+ [Description("The Scale property of a Button should match with native Scale")]
+ public async Task ScaleConsistent()
+ {
+ var button = new Button() { Scale = 2.0f };
+ var expected = button.Scale;
+ var handler = await CreateHandlerAsync(button);
+ var platformButton = GetPlatformButton(handler);
+ var platformScaleX = await InvokeOnMainThreadAsync(() => platformButton.ScaleX);
+ var platformScaleY = await InvokeOnMainThreadAsync(() => platformButton.ScaleY);
+ Assert.Equal(expected, platformScaleX);
+ Assert.Equal(expected, platformScaleY);
+ }
+
+ [Fact]
+ [Description("The RotationX property of a Button should match with native RotationX")]
+ public async Task RotationXConsistent()
+ {
+ var button = new Button() { RotationX = 33.0 };
+ var expected = button.RotationX;
+ var handler = await CreateHandlerAsync(button);
+ var platformButton = GetPlatformButton(handler);
+ var platformRotationX = await InvokeOnMainThreadAsync(() => platformButton.RotationX);
+ Assert.Equal(expected, platformRotationX);
+ }
+
+ [Fact]
+ [Description("The RotationY property of a Button should match with native RotationY")]
+ public async Task RotationYConsistent()
+ {
+ var button = new Button() { RotationY = 87.0 };
+ var expected = button.RotationY;
+ var handler = await CreateHandlerAsync(button);
+ var platformButton = GetPlatformButton(handler);
+ var platformRotationY = await InvokeOnMainThreadAsync(() => platformButton.RotationY);
+ Assert.Equal(expected, platformRotationY);
+ }
+
+ [Fact]
+ [Description("The Rotation property of a Button should match with native Rotation")]
+ public async Task RotationConsistent()
+ {
+ var button = new Button() { Rotation = 23.0 };
+ var expected = button.Rotation;
+ var handler = await CreateHandlerAsync(button);
+ var platformButton = GetPlatformButton(handler);
+ var platformRotation = await InvokeOnMainThreadAsync(() => platformButton.Rotation);
+ Assert.Equal(expected, platformRotation);
+ }
}
}
diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs
index e3029fe05cd2..c7713d1d4c02 100644
--- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.Windows.cs
@@ -31,6 +31,15 @@ Task GetPlatformOpacity(ButtonHandler buttonHandler)
});
}
+ Task GetPlatformIsVisible(ButtonHandler buttonHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetPlatformButton(buttonHandler);
+ return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible;
+ });
+ }
+
[Fact]
[Description("The Opacity property of a Button should match with native Opacity")]
public async Task VerifyButtonOpacityProperty()
@@ -48,5 +57,6 @@ await InvokeOnMainThreadAsync(async () =>
Assert.Equal(expectedValue, nativeOpacityValue);
});
}
+
}
}
diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs
index d0ca13d84f3d..84cced7c4889 100644
--- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.cs
@@ -70,5 +70,21 @@ public async Task ButtonBackgroundColorConsistent()
await ValidateHasColor(button, expected, typeof(ButtonHandler));
}
+
+ [Fact]
+ [Description("The IsVisible property of a Button should match with native IsVisible")]
+ public async Task VerifyButtonIsVisibleProperty()
+ {
+ var button = new Button();
+ button.IsVisible = false;
+ var expectedValue = button.IsVisible;
+
+ var handler = await CreateHandlerAsync(button);
+ await InvokeOnMainThreadAsync(async () =>
+ {
+ var isVisible = await GetPlatformIsVisible(handler);
+ Assert.Equal(expectedValue, isVisible);
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs
index 61e7b3bbfaba..0b1f27eef54b 100644
--- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs
+++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs
@@ -22,6 +22,15 @@ Task GetPlatformText(ButtonHandler buttonHandler)
UILineBreakMode GetPlatformLineBreakMode(ButtonHandler buttonHandler) =>
GetPlatformButton(buttonHandler).TitleLabel.LineBreakMode;
+ Task GetPlatformIsVisible(ButtonHandler buttonHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetPlatformButton(buttonHandler);
+ return !nativeView.Hidden;
+ });
+ }
+
[Fact("Clicked works after GC")]
public async Task ClickedWorksAfterGC()
{
diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs
index dd8faaa342a8..7403625f6268 100644
--- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs
+++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Threading.Tasks;
using AndroidX.AppCompat.Widget;
using Microsoft.Maui.Controls;
@@ -8,11 +9,11 @@
namespace Microsoft.Maui.DeviceTests
{
- [Collection(RunInNewWindowCollection)]
- public partial class CheckBoxTests
- {
- AppCompatCheckBox GetNativeCheckBox(CheckBoxHandler checkBoxHandler) =>
- checkBoxHandler.PlatformView;
+ [Collection(RunInNewWindowCollection)]
+ public partial class CheckBoxTests
+ {
+ AppCompatCheckBox GetNativeCheckBox(CheckBoxHandler checkBoxHandler) =>
+ checkBoxHandler.PlatformView;
Task GetPlatformOpacity(CheckBoxHandler CheckBoxHandler)
{
@@ -22,5 +23,88 @@ Task GetPlatformOpacity(CheckBoxHandler CheckBoxHandler)
return nativeView.Alpha;
});
}
+
+ [Fact]
+ [Description("The ScaleX property of a CheckBox should match with native ScaleX")]
+ public async Task ScaleXConsistent()
+ {
+ var checkBox = new CheckBox() { ScaleX = 0.45f };
+ var expected = checkBox.ScaleX;
+ var handler = await CreateHandlerAsync(checkBox);
+ var PlatformCheckBox = GetNativeCheckBox(handler);
+ var platformScaleX = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleX);
+ Assert.Equal(expected, platformScaleX);
+ }
+
+ [Fact]
+ [Description("The ScaleY property of a CheckBox should match with native ScaleY")]
+ public async Task ScaleYConsistent()
+ {
+ var checkBox = new CheckBox() { ScaleY = 1.23f };
+ var expected = checkBox.ScaleY;
+ var handler = await CreateHandlerAsync(checkBox);
+ var PlatformCheckBox = GetNativeCheckBox(handler);
+ var platformScaleY = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleY);
+ Assert.Equal(expected, platformScaleY);
+ }
+
+ [Fact]
+ [Description("The Scale property of a CheckBox should match with native Scale")]
+ public async Task ScaleConsistent()
+ {
+ var checkBox = new CheckBox() { Scale = 2.0f };
+ var expected = checkBox.Scale;
+ var handler = await CreateHandlerAsync(checkBox);
+ var PlatformCheckBox = GetNativeCheckBox(handler);
+ var platformScaleX = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleX);
+ var platformScaleY = await InvokeOnMainThreadAsync(() => PlatformCheckBox.ScaleY);
+ Assert.Equal(expected, platformScaleX);
+ Assert.Equal(expected, platformScaleY);
+ }
+
+ [Fact]
+ [Description("The RotationX property of a CheckBox should match with native RotationX")]
+ public async Task RotationXConsistent()
+ {
+ var checkBox = new CheckBox() { RotationX = 33.0 };
+ var expected = checkBox.RotationX;
+ var handler = await CreateHandlerAsync(checkBox);
+ var PlatformCheckBox = GetNativeCheckBox(handler);
+ var platformRotationX = await InvokeOnMainThreadAsync(() => PlatformCheckBox.RotationX);
+ Assert.Equal(expected, platformRotationX);
+ }
+
+ [Fact]
+ [Description("The RotationY property of a CheckBox should match with native RotationY")]
+ public async Task RotationYConsistent()
+ {
+ var checkBox = new CheckBox() { RotationY = 87.0 };
+ var expected = checkBox.RotationY;
+ var handler = await CreateHandlerAsync(checkBox);
+ var PlatformCheckBox = GetNativeCheckBox(handler);
+ var platformRotationY = await InvokeOnMainThreadAsync(() => PlatformCheckBox.RotationY);
+ Assert.Equal(expected, platformRotationY);
+ }
+
+ [Fact]
+ [Description("The Rotation property of a CheckBox should match with native Rotation")]
+ public async Task RotationConsistent()
+ {
+ var checkBox = new CheckBox() { Rotation = 23.0 };
+ var expected = checkBox.Rotation;
+ var handler = await CreateHandlerAsync(checkBox);
+ var PlatformCheckBox = GetNativeCheckBox(handler);
+ var platformRotation = await InvokeOnMainThreadAsync(() => PlatformCheckBox.Rotation);
+ Assert.Equal(expected, platformRotation);
+ }
+
+ Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetNativeCheckBox(checkBoxHandler);
+ return nativeView.Visibility == Android.Views.ViewStates.Visible;
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs
index e291d1855621..b37682a406a4 100644
--- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs
+++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs
@@ -19,5 +19,14 @@ Task GetPlatformOpacity(CheckBoxHandler checkBoxHandler)
return (float)nativeView.Opacity;
});
}
+
+ Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetNativeCheckBox(checkBoxHandler);
+ return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible;
+ });
+ }
}
}
diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs
index be8021a5bc6c..f2213688efe0 100644
--- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs
+++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.cs
@@ -62,5 +62,21 @@ await InvokeOnMainThreadAsync(async () =>
Assert.Equal(expectedValue, nativeOpacityValue);
});
}
+
+ [Fact]
+ [Description("The IsVisible property of a CheckBox should match with native IsVisible")]
+ public async Task VerifyCheckBoxIsVisibleProperty()
+ {
+ var checkBox = new CheckBox();
+ checkBox.IsVisible = false;
+ var expectedValue = checkBox.IsVisible;
+
+ var handler = await CreateHandlerAsync(checkBox);
+ await InvokeOnMainThreadAsync(async () =>
+ {
+ var isVisible = await GetPlatformIsVisible(handler);
+ Assert.Equal(expectedValue, isVisible);
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs
index 879dcfbab6e8..c907dcf606ff 100644
--- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs
+++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs
@@ -21,5 +21,14 @@ Task GetPlatformOpacity(CheckBoxHandler checkBoxHandler)
return (float)nativeView.Alpha;
});
}
+
+ Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler)
+ {
+ return InvokeOnMainThreadAsync(() =>
+ {
+ var nativeView = GetNativeCheckBox(checkBoxHandler);
+ return !nativeView.Hidden;
+ });
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs
index aeca193a7860..92fa952203ba 100644
--- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs
+++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.Android.cs
@@ -5,6 +5,8 @@
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Xunit;
+using System.Collections.ObjectModel;
+using System.Linq;
namespace Microsoft.Maui.DeviceTests
{
@@ -86,6 +88,102 @@ await CreateHandlerAndAddToWindow(contentPage,
});
}
+ //src/Compatibility/Core/tests/Android/RendererTests.cs
+ [Fact(DisplayName = "EmptySource should have a count of zero")]
+ [Trait("Category", "CollectionView")]
+ public void EmptySourceCountIsZero()
+ {
+ var emptySource = new EmptySource();
+ var count = emptySource.Count;
+ Assert.Equal(0, count);
+ }
+
+ //src/Compatibility/Core/tests/Android/ObservableItemsSourceTests.cs#L52
+ [Fact(DisplayName = "CollectionView with SnapPointsType set should not crash")]
+ public async Task SnapPointsDoNotCrashOnOlderAPIs()
+ {
+ SetupBuilder();
+
+ var collectionView = new CollectionView();
+
+ var itemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
+ {
+ SnapPointsType = SnapPointsType.Mandatory
+ };
+ collectionView.ItemsLayout = itemsLayout;
+
+ await InvokeOnMainThreadAsync(() =>
+ {
+ var handler = CreateHandler(collectionView);
+
+ var platformView = handler.PlatformView;
+
+ collectionView.Handler = null;
+ });
+ }
+
+ //src/Compatibility/Core/tests/Android/ObservableItemsSourceTests.cs#L52
+ [Fact(DisplayName = "ObservableCollection modifications are reflected after UI thread processes them")]
+ public async Task ObservableSourceItemsCountConsistent()
+ {
+ SetupBuilder();
+
+ var source = new ObservableCollection();
+ source.Add("Item 1");
+ source.Add("Item 2");
+ var ois = ItemsSourceFactory.Create(source, Application.Current, new MockCollectionChangedNotifier());
+
+ Assert.Equal(2, ois.Count);
+
+ source.Add("Item 3");
+ var count = 0;
+ await InvokeOnMainThreadAsync(() =>
+ {
+ count = ois.Count;
+ Assert.Equal(3, ois.Count);
+ });
+ }
+
+ class MockCollectionChangedNotifier : ICollectionChangedNotifier
+ {
+ public int InsertCount;
+ public int RemoveCount;
+
+ public void NotifyDataSetChanged()
+ {
+ }
+
+ public void NotifyItemChanged(IItemsViewSource source, int startIndex)
+ {
+ }
+
+ public void NotifyItemInserted(IItemsViewSource source, int startIndex)
+ {
+ InsertCount += 1;
+ }
+
+ public void NotifyItemMoved(IItemsViewSource source, int fromPosition, int toPosition)
+ {
+ }
+
+ public void NotifyItemRangeChanged(IItemsViewSource source, int start, int end)
+ {
+ }
+
+ public void NotifyItemRangeInserted(IItemsViewSource source, int startIndex, int count)
+ {
+ }
+
+ public void NotifyItemRangeRemoved(IItemsViewSource source, int startIndex, int count)
+ {
+ }
+
+ public void NotifyItemRemoved(IItemsViewSource source, int startIndex)
+ {
+ RemoveCount += 1;
+ }
+ }
+
Rect GetCollectionViewCellBounds(IView cellContent)
{
if (!cellContent.ToPlatform().IsLoaded())
diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs
index eeb513ac4081..e538b903ce26 100644
--- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs
+++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.iOS.cs
@@ -10,6 +10,8 @@
using UIKit;
using Xunit;
using Xunit.Sdk;
+using Foundation;
+using Microsoft.Maui.Handlers;
namespace Microsoft.Maui.DeviceTests
{
@@ -125,6 +127,73 @@ await InvokeOnMainThreadAsync(() =>
#endif
}
+ //src/Compatibility/Core/tests/iOS/ObservableItemsSourceTests.cs
+ [Fact(DisplayName = "IndexPath Range Generation Is Correct")]
+ public void GenerateIndexPathRange()
+ {
+ SetupBuilder();
+
+ var result = IndexPathHelpers.GenerateIndexPathRange(0, 0, 5);
+
+ Assert.Equal(5, result.Length);
+
+ Assert.Equal(0, result[0].Section);
+ Assert.Equal(0, (int)result[0].Item);
+
+ Assert.Equal(0, result[4].Section);
+ Assert.Equal(4, (int)result[4].Item);
+ }
+
+ //src/Compatibility/Core/tests/iOS/ObservableItemsSourceTests.cs
+ [Fact(DisplayName = "IndexPath Range Generation For Loops Is Correct")]
+ public void GenerateIndexPathRangeForLoop()
+ {
+ SetupBuilder();
+
+ var result = IndexPathHelpers.GenerateLoopedIndexPathRange(0, 15, 3, 2, 3);
+
+ Assert.Equal(9, result.Length);
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ Assert.Equal(0, result[i].Section);
+ }
+
+ Assert.Equal(2, (int)result[0].Item);
+ Assert.Equal(3, (int)result[1].Item);
+ Assert.Equal(4, (int)result[2].Item);
+
+ Assert.Equal(7, (int)result[3].Item);
+ Assert.Equal(8, (int)result[4].Item);
+ Assert.Equal(9, (int)result[5].Item);
+
+ Assert.Equal(12, (int)result[6].Item);
+ Assert.Equal(13, (int)result[7].Item);
+ Assert.Equal(14, (int)result[8].Item);
+ }
+
+ //src/Compatibility/Core/tests/iOS/ObservableItemsSourceTests.cs
+ [Fact(DisplayName = "IndexPath Validity Check Is Correct")]
+ public void IndexPathValidTest()
+ {
+ var list = new List
+ {
+ "one",
+ "two",
+ "three"
+ };
+
+ var source = new ListSource((IEnumerable