diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 834c76d3dbdf..20d35cc675d1 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -21,7 +21,7 @@
]
},
"microsoft.dotnet.xharness.cli": {
- "version": "9.0.0-prerelease.24203.1",
+ "version": "9.0.0-prerelease.24224.1",
"commands": [
"xharness"
]
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 4c5a6b55a182..30539522ddb3 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -45,8 +45,10 @@ body:
description: In what version do you see this issue? Run `dotnet workload list` to find your version.
options:
-
+ - 9.0.0-preview.3.10457
- 9.0.0-preview.2.10293
- 9.0.0-preview.1.9973
+ - 8.0.21 SR4.1
- 8.0.20 SR4
- 8.0.14 SR3.1
- 8.0.10 SR3
@@ -115,8 +117,10 @@ body:
- 8.0.10 SR3
- 8.0.14 SR3.1
- 8.0.20 SR4
+ - 8.0.21 SR4.1
- 9.0.0-preview.1.9973
- 9.0.0-preview.2.10293
+ - 9.0.0-preview.3.10457
validations:
required: true
- type: dropdown
diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml
index 39ed64b00b3b..0fd58cf68d2f 100644
--- a/.github/policies/resourceManagement.yml
+++ b/.github/policies/resourceManagement.yml
@@ -463,13 +463,13 @@ configuration:
then:
- addReply:
reply: >-
- Thanks for the issue report @${issueAuthor}! This issue appears to be a problem with Visual Studio, so we ask that you use the VS feedback tool to report the issue. That way it will get to the routed to the team that owns this experience in VS.
+ Thanks for the issue report @${issueAuthor}! This issue appears to be a problem with Visual Studio (Code), so we ask that you use the VS feedback tool to report the issue. That way it will get to the routed to the team that owns this experience in VS (Code).
- If you encounter a problem with Visual Studio, we want to know about it so that we can diagnose and fix it. By using the Report a Problem tool, you can collect detailed information about the problem, and send it to Microsoft with just a few button clicks.
+ If you encounter a problem with Visual Studio or the .NET MAUI VS Code Extension, we want to know about it so that we can diagnose and fix it. By using the Report a Problem tool, you can collect detailed information about the problem, and send it to Microsoft with just a few button clicks.
- 1. Go to the [Visual Studio for Windows feedback tool](https://docs.microsoft.com/visualstudio/ide/how-to-report-a-problem-with-visual-studio?view=vs-2022) or [Visual Studio for Mac feedback tool](https://learn.microsoft.com/en-us/visualstudio/mac/report-a-problem?view=vsmac-2022) to report the issue
+ 1. Go to the [Visual Studio for Windows feedback tool](https://learn.microsoft.com/visualstudio/ide/how-to-report-a-problem-with-visual-studio) or [.NET MAUI VS Code Extension repository](https://github.com/microsoft/vscode-dotnettools/issues) to report the issue
2. Close this bug, and consider adding a link to the VS Feedback issue so that others can follow its activity there.
description: Ask user to use VS Feedback for VS issues
@@ -581,13 +581,48 @@ configuration:
description: Remove 's/try-latest-version' when new reply from author comes in
- if:
- payloadType: Issues
- - activitySenderHasPermission:
- permission: Write
- isAction:
action: Opened
+ - or:
+ - isActivitySender:
+ user: PureWeen
+ issueAuthor: False
+ - isActivitySender:
+ user: mattleibow
+ issueAuthor: False
+ - isActivitySender:
+ user: rmarinho
+ issueAuthor: False
+ - isActivitySender:
+ user: jsuarezruiz
+ issueAuthor: False
+ - isActivitySender:
+ user: Redth
+ issueAuthor: False
+ - isActivitySender:
+ user: StephaneDelcroix
+ issueAuthor: False
+ - isActivitySender:
+ user: samhouts
+ issueAuthor: False
+ - isActivitySender:
+ user: jamesmontemagno
+ issueAuthor: False
+ - isActivitySender:
+ user: jonathanpeppers
+ issueAuthor: False
+ - isActivitySender:
+ user: rachelkang
+ issueAuthor: False
+ - isActivitySender:
+ user: Eilon
+ issueAuthor: False
+ - isActivitySender:
+ user: jfversluis
+ issueAuthor: False
then:
- addLabel:
label: s/triaged
- description: Add 's/triaged' label to issues opened by the core team, we assume these issues do not need triaging
+ description: Add 's/triaged' label to issues opened by the (core) team, we assume these issues do not need triaging
onFailure:
onSuccess:
diff --git a/.github/workflows/locker.yml b/.github/workflows/locker.yml
index 0e7b77151016..3cf63182334d 100644
--- a/.github/workflows/locker.yml
+++ b/.github/workflows/locker.yml
@@ -20,13 +20,14 @@ permissions:
jobs:
main:
runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'dotnet' }}
steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: "microsoft/vscode-github-triage-actions"
path: ./actions
- ref: cd16cd2aad6ba2da74bb6c6f7293adddd579a90e # locker action commit sha
+ ref: 066bee9cefa6f0b4bf306040ff36fc7d96a6d56d # locker action commit sha
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Locker
diff --git a/.github/workflows/similarIssues.yml b/.github/workflows/similarIssues.yml
new file mode 100644
index 000000000000..ecba7b66952e
--- /dev/null
+++ b/.github/workflows/similarIssues.yml
@@ -0,0 +1,38 @@
+name: GitGudSimilarIssues comments
+
+on:
+ issues:
+ types: [opened]
+ issue_comment:
+ types: [created]
+
+jobs:
+ getSimilarIssues:
+ runs-on: ubuntu-latest
+ if: >-
+ (github.event_name == 'issues' && github.event.action == 'opened') ||
+ (github.event_name == 'issue_comment' && github.event.action == 'created' && startsWith(github.event.comment.body, '/similarissues'))
+ outputs:
+ message: ${{ steps.getBody.outputs.message }}
+ steps:
+ - id: getBody
+ uses: craigloewen-msft/GitGudSimilarIssues@main
+ with:
+ issueTitle: ${{ github.event.issue.title }}
+ issueBody: ${{ github.event.issue.body }}
+ repo: ${{ github.repository }}
+ similaritytolerance: "0.70"
+ add-comment:
+ needs: getSimilarIssues
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ if: needs.getSimilarIssues.outputs.message != ''
+ steps:
+ - name: Add comment
+ run: gh issue comment "$NUMBER" --repo "$REPO" --body "$BODY"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NUMBER: ${{ github.event.issue.number }}
+ REPO: ${{ github.repository }}
+ BODY: ${{ needs.getSimilarIssues.outputs.message }}
diff --git a/Microsoft.Maui-dev.sln b/Microsoft.Maui-dev.sln
index c38c4fe99ab0..636df752bce6 100644
--- a/Microsoft.Maui-dev.sln
+++ b/Microsoft.Maui-dev.sln
@@ -241,9 +241,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITest.Appium", "src\TestUt
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITest.NUnit", "src\TestUtils\src\UITest.NUnit\UITest.NUnit.csproj", "{A307B624-48D4-494E-A70D-5B3CDF6620CF}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controls.SourceGen.UnitTests", "src\Controls\tests\SourceGen.UnitTests\Controls.SourceGen.UnitTests\Controls.SourceGen.UnitTests.csproj", "{06747B55-91DB-47F5-B7A2-56526C28A0D3}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGen.UnitTests", "src\Controls\tests\SourceGen.UnitTests\SourceGen.UnitTests.csproj", "{BC7F7C82-694F-4B97-86FC-273FB3FACA25}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGen.UnitTests", "src\Controls\tests\SourceGen.UnitTests\SourceGen.UnitTests.csproj", "{06747B55-91DB-47F5-B7A2-56526C28A0D3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -618,10 +616,6 @@ Global
{06747B55-91DB-47F5-B7A2-56526C28A0D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06747B55-91DB-47F5-B7A2-56526C28A0D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06747B55-91DB-47F5-B7A2-56526C28A0D3}.Release|Any CPU.Build.0 = Release|Any CPU
- {BC7F7C82-694F-4B97-86FC-273FB3FACA25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BC7F7C82-694F-4B97-86FC-273FB3FACA25}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BC7F7C82-694F-4B97-86FC-273FB3FACA25}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BC7F7C82-694F-4B97-86FC-273FB3FACA25}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -734,7 +728,6 @@ Global
{26379D0E-4D4D-48CA-94B1-A2C1972AB335} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{A307B624-48D4-494E-A70D-5B3CDF6620CF} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{06747B55-91DB-47F5-B7A2-56526C28A0D3} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E}
- {BC7F7C82-694F-4B97-86FC-273FB3FACA25} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50}
diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln
index 3ae8942ce0ec..b0454362fc6e 100644
--- a/Microsoft.Maui.sln
+++ b/Microsoft.Maui.sln
@@ -249,11 +249,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls.Xaml.UnitTests.Int
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls.Sample.UITests", "src\Controls\samples\Controls.Sample.UITests\Controls.Sample.UITests.csproj", "{F39F75DC-671B-4649-8005-1929797B3217}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITest.Core", "src\TestUtils\src\UITest.Core\UITest.Core.csproj", "{352C2381-1DEC-4487-819D-340D1EA98FBE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITest.Core", "src\TestUtils\src\UITest.Core\UITest.Core.csproj", "{352C2381-1DEC-4487-819D-340D1EA98FBE}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITest.Appium", "src\TestUtils\src\UITest.Appium\UITest.Appium.csproj", "{8C8CD467-11F9-4A14-8AF3-047B2CFD19A7}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITest.Appium", "src\TestUtils\src\UITest.Appium\UITest.Appium.csproj", "{8C8CD467-11F9-4A14-8AF3-047B2CFD19A7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITest.NUnit", "src\TestUtils\src\UITest.NUnit\UITest.NUnit.csproj", "{8050448A-E08F-4972-9B47-16042A5DFE82}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITest.NUnit", "src\TestUtils\src\UITest.NUnit\UITest.NUnit.csproj", "{8050448A-E08F-4972-9B47-16042A5DFE82}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlGallery.Android.Appium.UITests", "src\Compatibility\ControlGallery\test\Android.Appium.UITests\ControlGallery.Android.Appium.UITests.csproj", "{F748974F-A8E4-4659-801C-804B739D6326}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlGallery.iOS.Appium.UITests", "src\Compatibility\ControlGallery\test\iOS.Appium.UITests\ControlGallery.iOS.Appium.UITests.csproj", "{5923B35B-EA24-4B86-A384-9DAF9F2AFD56}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlGallery.Shared.Appium.UITests", "src\Compatibility\ControlGallery\test\Shared.Appium.UITests\ControlGallery.Shared.Appium.UITests.csproj", "{07D8D4B5-C89D-4BE3-A14A-17668358587C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -644,6 +650,18 @@ Global
{8050448A-E08F-4972-9B47-16042A5DFE82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8050448A-E08F-4972-9B47-16042A5DFE82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8050448A-E08F-4972-9B47-16042A5DFE82}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F748974F-A8E4-4659-801C-804B739D6326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F748974F-A8E4-4659-801C-804B739D6326}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F748974F-A8E4-4659-801C-804B739D6326}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F748974F-A8E4-4659-801C-804B739D6326}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5923B35B-EA24-4B86-A384-9DAF9F2AFD56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5923B35B-EA24-4B86-A384-9DAF9F2AFD56}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5923B35B-EA24-4B86-A384-9DAF9F2AFD56}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5923B35B-EA24-4B86-A384-9DAF9F2AFD56}.Release|Any CPU.Build.0 = Release|Any CPU
+ {07D8D4B5-C89D-4BE3-A14A-17668358587C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {07D8D4B5-C89D-4BE3-A14A-17668358587C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07D8D4B5-C89D-4BE3-A14A-17668358587C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {07D8D4B5-C89D-4BE3-A14A-17668358587C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -762,6 +780,9 @@ Global
{352C2381-1DEC-4487-819D-340D1EA98FBE} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{8C8CD467-11F9-4A14-8AF3-047B2CFD19A7} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{8050448A-E08F-4972-9B47-16042A5DFE82} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
+ {F748974F-A8E4-4659-801C-804B739D6326} = {DDBA9144-36FC-429E-99E1-2A64825434C1}
+ {5923B35B-EA24-4B86-A384-9DAF9F2AFD56} = {DDBA9144-36FC-429E-99E1-2A64825434C1}
+ {07D8D4B5-C89D-4BE3-A14A-17668358587C} = {DDBA9144-36FC-429E-99E1-2A64825434C1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50}
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 9095a7dc28e6..d9bc8faf9223 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -143,17 +143,17 @@
https://github.com/dotnet/runtime
59edaad404d1b8e47080015ae8d0787f94c970df
-
+
https://github.com/dotnet/xharness
- 28af9496b0e260f7e66ec549b39f1410ee9743d1
+ ec633d9ddbdb86dd3d772989889690821f790484
-
+
https://github.com/dotnet/xharness
- 28af9496b0e260f7e66ec549b39f1410ee9743d1
+ ec633d9ddbdb86dd3d772989889690821f790484
-
+
https://github.com/dotnet/xharness
- 28af9496b0e260f7e66ec549b39f1410ee9743d1
+ ec633d9ddbdb86dd3d772989889690821f790484
diff --git a/eng/Versions.props b/eng/Versions.props
index c5f5d8e89a32..5f0ca83bf418 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -78,9 +78,9 @@
<_HarfBuzzSharpVersion>7.3.0
<_SkiaSharpNativeAssetsVersion>0.0.0-commit.e2c5c86249621857107c779af0f79b4d06613766.655
7.0.114
- 9.0.0-prerelease.24203.1
- 9.0.0-prerelease.24203.1
- 9.0.0-prerelease.24203.1
+ 9.0.0-prerelease.24224.1
+ 9.0.0-prerelease.24224.1
+ 9.0.0-prerelease.24224.1
0.9.2
1.0.0.16
1.3.0
diff --git a/eng/cake/dotnet.cake b/eng/cake/dotnet.cake
index d3c977ee0b07..9e129cf57cb0 100644
--- a/eng/cake/dotnet.cake
+++ b/eng/cake/dotnet.cake
@@ -222,6 +222,7 @@ Task("dotnet-test")
"**/Controls.Core.UnitTests.csproj",
"**/Controls.Core.Design.UnitTests.csproj",
"**/Controls.Xaml.UnitTests.csproj",
+ "**/SourceGen.UnitTests.csproj",
"**/Core.UnitTests.csproj",
"**/Essentials.UnitTests.csproj",
"**/Resizetizer.UnitTests.csproj",
diff --git a/eng/pipelines/common/ui-tests-legacy-steps.yml b/eng/pipelines/common/ui-tests-legacy-steps.yml
new file mode 100644
index 000000000000..45f154de55a4
--- /dev/null
+++ b/eng/pipelines/common/ui-tests-legacy-steps.yml
@@ -0,0 +1,146 @@
+parameters:
+ platform: '' # [ android, ios, windows, catalyst ]
+ path: '' # path to csproj
+ device: '' # the xharness device to use
+ cakeArgs: '' # additional cake args
+ app: '' #path to app to test
+ version: '' #the iOS version'
+ provisionatorChannel: 'latest'
+ agentPoolAccessToken: ''
+ targetSample: "dotnet-legacy-controlgallery"
+ configuration : "Release"
+
+steps:
+ - ${{ if eq(parameters.platform, 'ios')}}:
+ - bash: |
+ chmod +x $(System.DefaultWorkingDirectory)/eng/scripts/clean-bot.sh
+ $(System.DefaultWorkingDirectory)/eng/scripts/clean-bot.sh
+ displayName: 'Clean bot'
+ continueOnError: true
+ timeoutInMinutes: 60
+
+ - template: provision.yml
+ parameters:
+ skipProvisioning: ${{ eq(parameters.platform, 'windows') }}
+ skipAndroidSdks: ${{ ne(parameters.platform, 'android') }}
+ skipXcode: ${{ or(eq(parameters.platform, 'android'), eq(parameters.platform, 'windows')) }}
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+
+ - task: PowerShell@2
+ condition: ne('${{ parameters.platform }}' , 'windows')
+ inputs:
+ targetType: 'inline'
+ script: |
+ defaults write -g NSAutomaticCapitalizationEnabled -bool false
+ defaults write -g NSAutomaticTextCompletionEnabled -bool false
+ defaults write -g NSAutomaticSpellingCorrectionEnabled -bool false
+ displayName: "Modify defaults"
+ continueOnError: true
+
+ # AzDO hosted agents default to 1024x768; set something bigger for Windows UI tests
+ - task: ScreenResolutionUtility@1
+ condition: eq('${{ parameters.platform }}' , 'windows')
+ inputs:
+ displaySettings: 'specific'
+ width: '1920'
+ height: '1080'
+ displayName: "Set screen resolution"
+
+ - task: UseNode@1
+ inputs:
+ version: "20.3.1"
+ displayName: "Install node"
+
+ - bash: |
+ echo "##[group]Running ls -al $(npm root -g)"
+ ls -al $(npm root -g)
+ echo "##[endgroup]"
+
+ echo "##[group]Running ls -al $(npm root -g)/appium"
+ ls -al $(npm root -g)/appium
+ echo "##[endgroup]"
+
+ echo "##[group]Running ls -al $(npm root -g)/.appium-????????"
+ ls -al $(npm root -g)/.appium-????????
+ echo "##[endgroup]"
+
+ echo "##[group]Running ls -al $(npm root -g)/appium-doctor"
+ ls -al $(npm root -g)/appium-doctor
+ echo "##[endgroup]"
+
+ echo "##[group]Running ls -al $(npm root -g)/.appium-doctor-????????"
+ ls -al $(npm root -g)/.appium-doctor-????????
+ echo "##[endgroup]"
+
+ echo "##[group]Running ps aux"
+ ps aux
+ echo "##[endgroup]"
+ displayName: "Debugging output"
+ continueOnError: true
+ condition: startsWith(variables['Agent.Name'], 'XAMBOT')
+
+ # Clean up any leftover cached folders of appium and appium-doctor node modules
+ - bash: |
+ rm -rf $(npm root -g)/.appium-????????
+ rm -rf $(npm root -g)/.appium-doctor-????????
+ displayName: "Delete temp .appium-???????? and .appium-doctor-???????? folders"
+ continueOnError: true
+
+ - pwsh: ./eng/scripts/appium-install.ps1
+ displayName: "Install Appium (Drivers)"
+ continueOnError: false
+ retryCountOnTaskFailure: 1
+
+ - pwsh: ./build.ps1 --target=dotnet --configuration="${{ parameters.configuration }}" --verbosity=diagnostic
+ displayName: 'Install .NET'
+ retryCountOnTaskFailure: 2
+ env:
+ DOTNET_TOKEN: $(dotnetbuilds-internal-container-read-token)
+ PRIVATE_BUILD: $(PrivateBuild)
+
+ - pwsh: echo "##vso[task.prependpath]$(DotNet.Dir)"
+ displayName: 'Add .NET to PATH'
+
+ - pwsh: ./build.ps1 --target=dotnet-buildtasks --configuration="${{ parameters.configuration }}"
+ displayName: 'Build the MSBuild Tasks'
+
+ - pwsh: ./build.ps1 --target=${{ parameters.targetSample }} --configuration="${{ parameters.configuration }}" --${{ parameters.platform }} --verbosity=diagnostic --usenuget=false
+ displayName: 'Build the Legacy ControlGallery'
+
+ - bash: |
+ if [ -f "$HOME/Library/Logs/CoreSimulator/*" ]; then rm -r $HOME/Library/Logs/CoreSimulator/*; fi
+ if [ -f "$HOME/Library/Logs/DiagnosticReports/*" ]; then rm -r $HOME/Library/Logs/DiagnosticReports/*; fi
+ displayName: Delete Old Simulator Logs
+ condition: ${{ eq(parameters.platform, 'ios') }}
+ continueOnError: true
+
+ - pwsh: ./build.ps1 -Script eng/devices/${{ parameters.platform }}.cake --target=uitest --project="${{ parameters.path }}" --appproject="${{ parameters.app }}" --device="${{ parameters.device }}" --apiversion="${{ parameters.version }}" --configuration="${{ parameters.configuration }}" --results="$(TestResultsDirectory)" --binlog="$(LogDirectory)" ${{ parameters.cakeArgs }} --verbosity=diagnostic
+ displayName: $(Agent.JobName)
+ ${{ if ne(parameters.platform, 'android')}}:
+ retryCountOnTaskFailure: 1
+
+ - bash: |
+ suffix=$(date +%Y%m%d%H%M%S)
+ zip -9r "$(LogDirectory)/CoreSimulatorLog_${suffix}.zip" "$HOME/Library/Logs/CoreSimulator/"
+ zip -9r "$(LogDirectory)/DiagnosticReports_${suffix}.zip" "$HOME/Library/Logs/DiagnosticReports/"
+ displayName: Zip Simulator Logs
+ condition: ${{ eq(parameters.platform, 'ios') }}
+ continueOnError: true
+
+ - task: PublishTestResults@2
+ displayName: Publish the $(System.PhaseName) test results
+ condition: always()
+ inputs:
+ testResultsFormat: VSTest
+ testResultsFiles: '$(TestResultsDirectory)/*.trx'
+ testRunTitle: '$(System.PhaseName)'
+ failTaskOnFailedTests: true
+
+ - task: PublishBuildArtifacts@1
+ condition: always()
+ displayName: publish artifacts
+
+ # This must always be placed as the last step in the job
+ - template: agent-rebooter/mac.v1.yml@yaml-templates
+ parameters:
+ AgentPoolAccessToken: ${{ parameters.agentPoolAccessToken }}
diff --git a/eng/pipelines/common/ui-tests.yml b/eng/pipelines/common/ui-tests.yml
index 639c8de45dfc..8865a818f0fa 100644
--- a/eng/pipelines/common/ui-tests.yml
+++ b/eng/pipelines/common/ui-tests.yml
@@ -5,11 +5,14 @@ parameters:
macosPool: { }
androidCompatibilityPool: { }
iosCompatibilityPool: { }
+ androidLegacyPool: { }
+ iosLegacyPool: { }
androidApiLevels: [ 30 ]
iosVersions: [ 'latest' ]
provisionatorChannel: 'latest'
agentPoolAccessToken: ''
runCompatibilityTests: false
+ runLegacyTests: true
projects:
- name: name
desc: Human Description
@@ -22,6 +25,10 @@ parameters:
compatibilityAndroidTestProject: /optional/path/to/android.csproj
compatibilityiOSTestProject: /optional/path/to/ios.csproj
compatibilityiOSApp: /optional/path/to/app.csproj
+ legacyAndroidApp: /optional/path/to/app.csproj
+ legacyAndroidTestProject: /optional/path/to/android.csproj
+ legacyiOSTestProject: /optional/path/to/ios.csproj
+ legacyiOSApp: /optional/path/to/app.csproj
stages:
@@ -170,7 +177,6 @@ stages:
provisionatorChannel: ${{ parameters.provisionatorChannel }}
agentPoolAccessToken: ${{ parameters.agentPoolAccessToken }}
-
- stage: ios_compatibility_ui_tests
displayName: iOS Compatibility UITests
dependsOn: []
@@ -206,3 +212,68 @@ stages:
device: ios-simulator-64_${{ version }}
provisionatorChannel: ${{ parameters.provisionatorChannel }}
agentPoolAccessToken: ${{ parameters.agentPoolAccessToken }}
+ - ${{ if eq(parameters.runLegacyTests, true) }}:
+ - stage: android_legacy_ui_tests
+ displayName: Android Legacy UITests
+ dependsOn: []
+ jobs:
+ - ${{ each project in parameters.projects }}:
+ - ${{ if ne(project.android, '') }}:
+ - ${{ each api in parameters.androidApiLevels }}:
+ - ${{ if not(containsValue(project.androidApiLevelsExclude, api)) }}:
+ - job: android_legacy_ui_tests_${{ project.name }}_${{ api }}
+ timeoutInMinutes: 240
+ workspace:
+ clean: all
+ displayName: ${{ coalesce(project.desc, project.name) }} (API ${{ api }})
+ pool: ${{ parameters.androidLegacyPool }}
+ variables:
+ REQUIRED_XCODE: $(DEVICETESTS_REQUIRED_XCODE)
+ steps:
+ - template: ui-tests-legacy-steps.yml
+ parameters:
+ platform: android
+ version: ${{ api }}
+ path: ${{ project.legacyAndroidTestProject }}
+ app: ${{ project.legacyAndroidApp }}
+ targetSample: "dotnet-legacy-controlgallery-android"
+ ${{ if eq(api, 27) }}:
+ device: android-emulator-32_${{ api }}
+ ${{ if not(eq(api, 27)) }}:
+ device: android-emulator-64_${{ api }}
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ agentPoolAccessToken: ${{ parameters.agentPoolAccessToken }}
+
+ - stage: ios_legacy_ui_tests
+ displayName: iOS Legacy UITests
+ dependsOn: []
+ jobs:
+ - ${{ each project in parameters.projects }}:
+ - ${{ if ne(project.ios, '') }}:
+ - ${{ each version in parameters.iosVersions }}:
+ - ${{ if not(containsValue(project.iosVersionsExclude, version)) }}:
+ - job: ios_legacy_ui_tests_${{ project.name }}_${{ replace(version, '.', '_') }}
+ timeoutInMinutes: 240
+ workspace:
+ clean: all
+ displayName: ${{ coalesce(project.desc, project.name) }} (v${{ version }})
+ pool: ${{ parameters.iosLegacyPool }}
+ variables:
+ REQUIRED_XCODE: $(DEVICETESTS_REQUIRED_XCODE)
+ steps:
+ - template: ui-tests-legacy-steps.yml
+ parameters:
+ platform: ios
+ ${{ if eq(version, 'latest') }}:
+ version: 16.4
+ ${{ if ne(version, 'latest') }}:
+ version: ${{ version }}
+ path: ${{ project.legacyiOSTestProject }}
+ app: ${{ project.legacyiOSApp }}
+ targetSample: "dotnet-legacy-controlgallery-ios"
+ ${{ if eq(version, 'latest') }}:
+ device: ios-simulator-64
+ ${{ if ne(version, 'latest') }}:
+ device: ios-simulator-64_${{ version }}
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ agentPoolAccessToken: ${{ parameters.agentPoolAccessToken }}
\ No newline at end of file
diff --git a/eng/pipelines/ui-tests.yml b/eng/pipelines/ui-tests.yml
index c2269ed23e66..46d4de15c7de 100644
--- a/eng/pipelines/ui-tests.yml
+++ b/eng/pipelines/ui-tests.yml
@@ -109,6 +109,23 @@ parameters:
- macOS.Name -equals Ventura
- macOS.Architecture -equals x64
+ - name: androidLegacyPool
+ type: object
+ default:
+ name: $(androidTestsVmPool)
+ vmImage: $(androidTestsVmImage)
+ demands:
+ - macOS.Name -equals Ventura
+ - macOS.Architecture -equals x64
+
+ - name: iosLegacyPool
+ type: object
+ default:
+ name: $(iosTestsVmPool)
+ vmImage: $(iosTestsVmImage)
+ demands:
+ - macOS.Name -equals Ventura
+ - macOS.Architecture -equals x64
resources:
repositories:
@@ -128,6 +145,8 @@ stages:
macosPool: ${{ parameters.macosPool }}
androidCompatibilityPool: ${{ parameters.androidCompatibilityPool }}
iosCompatibilityPool: ${{ parameters.iosCompatibilityPool }}
+ iosLegacyPool: ${{ parameters.iosLegacyPool }}
+ androidLegacyPool: ${{ parameters.androidLegacyPool }}
agentPoolAccessToken: $(AgentPoolAccessToken)
${{ if or(parameters.BuildEverything, and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['System.TeamProject'], 'devdiv'))) }}:
androidApiLevels: [ 30 ]
@@ -153,4 +172,8 @@ stages:
compatibilityAndroidTestProject: $(System.DefaultWorkingDirectory)/src/Compatibility/ControlGallery/test/Android.UITests/Compatibility.ControlGallery.Android.UITests.csproj
compatibilityiOSApp: $(System.DefaultWorkingDirectory)/src/Compatibility/ControlGallery/src/iOS/Compatibility.ControlGallery.iOS.csproj
compatibilityiOSTestProject: $(System.DefaultWorkingDirectory)/src/Compatibility/ControlGallery/test/iOS.UITests/Compatibility.ControlGallery.iOS.UITests.csproj
+ legacyAndroidApp: $(System.DefaultWorkingDirectory)/src/Compatibility/ControlGallery/src/Android/Compatibility.ControlGallery.Android.csproj
+ legacyAndroidTestProject: $(System.DefaultWorkingDirectory)/src/Compatibility/ControlGallery/test/Android.Appium.UITests/ControlGallery.Android.Appium.UITests.csproj
+ legacyiOSApp: $(System.DefaultWorkingDirectory)/src/Compatibility/ControlGallery/src/iOS/Compatibility.ControlGallery.iOS.csproj
+ legacyiOSTestProject: $(System.DefaultWorkingDirectory)/src/Compatibility/ControlGallery/test/iOS.Appium.UITests/ControlGallery.iOS.Appium.UITests.csproj
diff --git a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs
index 0af5f6fc0198..5a54464ce022 100644
--- a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs
+++ b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs
@@ -177,7 +177,7 @@ private void StartWebViewCoreIfPossible()
}
else
{
- appRootDir = Environment.CurrentDirectory;
+ appRootDir = AppContext.BaseDirectory;
}
var hostPageFullPath = Path.GetFullPath(Path.Combine(appRootDir, HostPage!)); // HostPage is nonnull because RequiredStartupPropertiesSet is checked above
var contentRootDirFullPath = Path.GetDirectoryName(hostPageFullPath)!;
diff --git a/src/BlazorWebView/src/Wpf/BlazorWebView.cs b/src/BlazorWebView/src/Wpf/BlazorWebView.cs
index 98d6a68ca62f..af673607a2de 100644
--- a/src/BlazorWebView/src/Wpf/BlazorWebView.cs
+++ b/src/BlazorWebView/src/Wpf/BlazorWebView.cs
@@ -262,7 +262,7 @@ private void StartWebViewCoreIfPossible()
}
else
{
- appRootDir = Environment.CurrentDirectory;
+ appRootDir = AppContext.BaseDirectory;
}
var hostPageFullPath = Path.GetFullPath(Path.Combine(appRootDir, HostPage));
var contentRootDirFullPath = Path.GetDirectoryName(hostPageFullPath)!;
diff --git a/src/Compatibility/ControlGallery/src/Android/FormsAppCompatActivity.cs b/src/Compatibility/ControlGallery/src/Android/FormsAppCompatActivity.cs
index 2ec5edd2c0f9..0d555a4dbc19 100644
--- a/src/Compatibility/ControlGallery/src/Android/FormsAppCompatActivity.cs
+++ b/src/Compatibility/ControlGallery/src/Android/FormsAppCompatActivity.cs
@@ -4,6 +4,7 @@
using Android.Content;
using Android.Content.PM;
using Android.OS;
+using Android.Runtime;
using Java.Interop;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.ControlGallery;
@@ -28,6 +29,7 @@ namespace Microsoft.Maui.Controls.ControlGallery.Android
DataScheme = "http", DataHost = App.AppName, DataPathPrefix = "/gallery/"
)
]
+ [Register("com.microsoft.mauicompatibilitygallery.MainActivity")]
public partial class Activity1 : MauiAppCompatActivity
{
App App => Microsoft.Maui.Controls.Application.Current as App;
diff --git a/src/Compatibility/ControlGallery/src/Core/GalleryPages/GroupedListContactsGallery.cs b/src/Compatibility/ControlGallery/src/Core/GalleryPages/GroupedListContactsGallery.cs
index f33a3de14eab..0b009adf2da9 100644
--- a/src/Compatibility/ControlGallery/src/Core/GalleryPages/GroupedListContactsGallery.cs
+++ b/src/Compatibility/ControlGallery/src/Core/GalleryPages/GroupedListContactsGallery.cs
@@ -200,25 +200,11 @@ void AddContact(ObservableCollection contactGroups, Contact contact)
InsertBasedOnSort(collection, contact, c => GetSortString(c)[0]);
}
- int IndexOf(IEnumerable elements, T element)
- {
- int i = 0;
- foreach (T e in elements)
- {
- if (Equals(e, element))
- return i;
-
- i++;
- }
-
- return -1;
- }
-
void InsertBasedOnSort(IList items, T item, Func sortBy)
{
List newItems = new List(items);
newItems.Add(item);
- int index = IndexOf(newItems.OrderBy(sortBy), item);
+ int index = newItems.OrderBy(sortBy).IndexOf(item);
items.Insert(index, item);
}
diff --git a/src/Compatibility/ControlGallery/src/Issues.Shared/Issue7534.cs b/src/Compatibility/ControlGallery/src/Issues.Shared/Issue7534.cs
index e4d341f38830..98385bb38792 100644
--- a/src/Compatibility/ControlGallery/src/Issues.Shared/Issue7534.cs
+++ b/src/Compatibility/ControlGallery/src/Issues.Shared/Issue7534.cs
@@ -49,6 +49,7 @@ protected override void Init()
#if UITEST && __ANDROID__
[Test]
+ [Compatibility.UITests.FailsOnMauiAndroid]
public void ExpectingPageNotToBreak()
{
RunningApp.Screenshot("Test passed, label is showing as it should!");
diff --git a/src/Compatibility/ControlGallery/test/Android.Appium.UITests/ControlGallery.Android.Appium.UITests.csproj b/src/Compatibility/ControlGallery/test/Android.Appium.UITests/ControlGallery.Android.Appium.UITests.csproj
new file mode 100644
index 000000000000..5d66c158ad15
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Android.Appium.UITests/ControlGallery.Android.Appium.UITests.csproj
@@ -0,0 +1,43 @@
+
+
+
+ $(_MauiDotNetTfm)
+ enable
+ enable
+ true
+ UITests
+ $(DefineConstants);ANDROID
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Android.Appium.UITests/PlatformSpecificSampleTest.cs b/src/Compatibility/ControlGallery/test/Android.Appium.UITests/PlatformSpecificSampleTest.cs
new file mode 100644
index 000000000000..98435f47f3e8
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Android.Appium.UITests/PlatformSpecificSampleTest.cs
@@ -0,0 +1,16 @@
+using NUnit.Framework;
+
+namespace UITests;
+
+public class PlatformSpecificSampleTest : UITest
+{
+ public PlatformSpecificSampleTest(TestDevice testDevice) : base(testDevice)
+ {
+ }
+
+ [Test]
+ public void SampleTest()
+ {
+ Driver?.GetScreenshot().SaveAsFile($"{nameof(SampleTest)}.png");
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/AppiumServerHelper.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/AppiumServerHelper.cs
new file mode 100644
index 000000000000..6165a3e9a341
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/AppiumServerHelper.cs
@@ -0,0 +1,33 @@
+using OpenQA.Selenium.Appium.Service;
+
+namespace UITests;
+
+public static class AppiumServerHelper
+{
+ static AppiumLocalService? AppiumLocalService;
+
+ public const string DefaultHostAddress = "127.0.0.1";
+ public const int DefaultHostPort = 4723;
+
+ public static void StartAppiumLocalServer(string host = DefaultHostAddress,
+ int port = DefaultHostPort)
+ {
+ if (AppiumLocalService is not null)
+ {
+ return;
+ }
+
+ var builder = new AppiumServiceBuilder()
+ .WithIPAddress(host)
+ .UsingPort(port);
+
+ // Start the server with the builder
+ AppiumLocalService = builder.Build();
+ AppiumLocalService.Start();
+ }
+
+ public static void DisposeAppiumLocalServer()
+ {
+ AppiumLocalService?.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/ControlGallery.Shared.Appium.UITests.csproj b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/ControlGallery.Shared.Appium.UITests.csproj
new file mode 100644
index 000000000000..5c5de9da4a79
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/ControlGallery.Shared.Appium.UITests.csproj
@@ -0,0 +1,36 @@
+
+
+
+ $(_MauiDotNetTfm)
+ False
+ False
+ UITests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(DefineConstants);ANDROID
+
+
+
+ $(DefineConstants);IOS;IOSUITEST
+
+
+
+ $(DefineConstants);WINDOWS;WINTEST
+
+
+
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/GalleryQueries.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/GalleryQueries.cs
new file mode 100644
index 000000000000..889551735add
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/GalleryQueries.cs
@@ -0,0 +1,30 @@
+namespace UITests
+{
+ internal static class GalleryQueries
+ {
+ public const string ActivityIndicatorGallery = "ActivityIndicator Gallery";
+ public const string BoxViewGallery = "BoxView Gallery";
+ public const string ButtonGallery = "Button Gallery";
+ public const string CheckBoxGallery = "CheckBox Gallery";
+ public const string CollectionViewGallery = "CollectionView Gallery";
+ public const string CarouselViewGallery = "CarouselView Gallery";
+ public const string DatePickerGallery = "DatePicker Gallery";
+ public const string EditorGallery = "Editor Gallery";
+ public const string EntryGallery = "Entry Gallery";
+ public const string FrameGallery = "Frame Gallery";
+ public const string ImageGallery = "Image Gallery";
+ public const string ImageButtonGallery = "Image Button Gallery";
+ public const string LabelGallery = "Label Gallery";
+ public const string ListViewGallery = "ListView Gallery";
+ public const string PickerGallery = "Picker Gallery";
+ public const string ProgressBarGallery = "ProgressBar Gallery";
+ public const string RadioButtonGallery = "RadioButton Core Gallery";
+ public const string ScrollViewGallery = "ScrollView Gallery";
+ public const string SearchBarGallery = "SearchBar Gallery";
+ public const string SliderGallery = "Slider Gallery";
+ public const string StepperGallery = "Stepper Gallery";
+ public const string SwitchGallery = "Switch Gallery";
+ public const string TimePickerGallery = "TimePicker Gallery";
+ public const string WebViewGallery = "WebView Gallery";
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/IssuesUITest.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/IssuesUITest.cs
new file mode 100644
index 000000000000..b8931ebf2c4e
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/IssuesUITest.cs
@@ -0,0 +1,63 @@
+using NUnit.Framework;
+using UITest.Appium;
+
+namespace UITests
+{
+ public abstract class IssuesUITest : UITest
+ {
+ public IssuesUITest(TestDevice device) : base(device) { }
+
+ protected override void FixtureSetup()
+ {
+ int retries = 0;
+ while (true)
+ {
+ try
+ {
+ base.FixtureSetup();
+ NavigateToIssue(Issue);
+ break;
+ }
+ catch (Exception e)
+ {
+ TestContext.Error.WriteLine($">>>>> {DateTime.Now} The FixtureSetup threw an exception. Attempt {retries}/{SetupMaxRetries}.{Environment.NewLine}Exception details: {e}");
+ if (retries++ < SetupMaxRetries)
+ {
+ Reset();
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+ }
+
+ protected override void FixtureTeardown()
+ {
+ base.FixtureTeardown();
+ try
+ {
+ this.Back();
+ RunningApp.Tap("GoBackToGalleriesButton");
+ }
+ catch (Exception e)
+ {
+ var name = TestContext.CurrentContext.Test.MethodName ?? TestContext.CurrentContext.Test.Name;
+ TestContext.Error.WriteLine($">>>>> {DateTime.Now} The FixtureTeardown threw an exception during {name}.{Environment.NewLine}Exception details: {e}");
+ }
+ }
+
+ public abstract string Issue { get; }
+
+ private void NavigateToIssue(string issue)
+ {
+ RunningApp.NavigateToIssues();
+
+ RunningApp.EnterText("SearchBarGo", issue);
+
+ RunningApp.WaitForElement("SearchButton");
+ RunningApp.Tap("SearchButton");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestAttributes.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestAttributes.cs
new file mode 100644
index 000000000000..39b219bf0846
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestAttributes.cs
@@ -0,0 +1,71 @@
+namespace UITests
+{
+ public static class Test
+ {
+ public enum Button
+ {
+ Clicked,
+ Command,
+ Text,
+ TextColor,
+ Font,
+ BorderWidth,
+ BorderColor,
+ BorderRadius,
+ Image,
+ Padding,
+ Pressed,
+ LineBreakMode
+ }
+
+ public enum ImageButton
+ {
+ Source,
+ Aspect,
+ IsOpaque,
+ IsLoading,
+ AspectFill,
+ AspectFit,
+ Fill,
+ BorderColor,
+ CornerRadius,
+ BorderWidth,
+ Clicked,
+ Command,
+ Image,
+ Pressed,
+ Padding
+ }
+
+ public enum VisualElement
+ {
+ IsEnabled,
+ Navigation,
+ InputTransparent,
+ Layout,
+ X,
+ Y,
+ AnchorX,
+ AnchorY,
+ TranslationX,
+ TranslationY,
+ Width,
+ Height,
+ Bounds,
+ Rotation,
+ RotationX,
+ RotationY,
+ Scale,
+ IsVisible,
+ Opacity,
+ BackgroundColor,
+ Background,
+ IsFocused,
+ Focus,
+ Unfocus,
+ Focused,
+ Unfocused,
+ Default
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestContextSetupFixture.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestContextSetupFixture.cs
new file mode 100644
index 000000000000..b8631bc9026f
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestContextSetupFixture.cs
@@ -0,0 +1,17 @@
+using UITest.Appium;
+
+// SetupFixture runs once for all tests under the same namespace, if placed outside the namespace it will run once for all tests in the assembly
+namespace UITests
+{
+ public class TestContextSetupFixture : UITestContextSetupFixture
+ {
+ AppiumServerContext? _appiumServerContext;
+
+ public override void Initialize()
+ {
+ _appiumServerContext = new AppiumServerContext();
+ _appiumServerContext.CreateAndStartServer();
+ _serverContext = _appiumServerContext;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestDevice.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestDevice.cs
new file mode 100644
index 000000000000..bb0f09240ac1
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/TestDevice.cs
@@ -0,0 +1,10 @@
+namespace UITests
+{
+ public enum TestDevice
+ {
+ Windows,
+ Android,
+ iOS,
+ Mac
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/Tests/Issues/Issue11853.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/Tests/Issues/Issue11853.cs
new file mode 100644
index 000000000000..1aa0f8291d18
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/Tests/Issues/Issue11853.cs
@@ -0,0 +1,32 @@
+#if IOS
+using NUnit.Framework;
+using UITest.Appium;
+
+namespace UITests
+{
+ public class Issue11853 : IssuesUITest
+ {
+ const string Run = "Run";
+
+ public Issue11853(TestDevice testDevice) : base(testDevice)
+ {
+ }
+
+ public override string Issue => "[Bug][iOS] Concurrent issue leading to crash in SemaphoreSlim.Release in ObservableItemsSource";
+
+ [Test]
+ [Category(UITestCategories.CollectionView)]
+ public void JustWhalingAwayOnTheCollectionViewWithAddsAndClearsShouldNotCrash()
+ {
+ RunningApp.WaitForElement(Run);
+ RunningApp.Tap(Run);
+ Task.Delay(5000).Wait();
+ RunningApp.Tap(Run);
+ Task.Delay(5000).Wait();
+
+ // If we can still find the button, then we didn't crash
+ RunningApp.WaitForElement(Run);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITest.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITest.cs
new file mode 100644
index 000000000000..f129ab406969
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITest.cs
@@ -0,0 +1,169 @@
+using NUnit.Framework;
+using UITest.Core;
+using VisualTestUtils;
+using VisualTestUtils.MagickNet;
+
+namespace UITests
+{
+#if ANDROID
+ [TestFixture(TestDevice.Android)]
+#elif IOSUITEST
+ [TestFixture(TestDevice.iOS)]
+#elif MACUITEST
+ [TestFixture(TestDevice.Mac)]
+#elif WINTEST
+ [TestFixture(TestDevice.Windows)]
+#else
+ [TestFixture(TestDevice.iOS)]
+ [TestFixture(TestDevice.Mac)]
+ [TestFixture(TestDevice.Windows)]
+ [TestFixture(TestDevice.Android)]
+#endif
+ public abstract class UITest : UITestBase
+ {
+ protected const int SetupMaxRetries = 1;
+ readonly VisualRegressionTester _visualRegressionTester;
+ readonly IImageEditorFactory _imageEditorFactory;
+ readonly VisualTestContext _visualTestContext;
+
+ protected UITest(TestDevice testDevice) : base(testDevice)
+ {
+ string? ciArtifactsDirectory = Environment.GetEnvironmentVariable("BUILD_ARTIFACTSTAGINGDIRECTORY");
+ if (ciArtifactsDirectory != null)
+ ciArtifactsDirectory = Path.Combine(ciArtifactsDirectory, "Controls.AppiumTests");
+
+ string assemblyDirectory = Path.GetDirectoryName(System.AppDomain.CurrentDomain.BaseDirectory)!;
+ string projectRootDirectory = Path.GetFullPath(Path.Combine(assemblyDirectory, "..", "..", ".."));
+ _visualRegressionTester = new VisualRegressionTester(testRootDirectory: projectRootDirectory,
+ visualComparer: new MagickNetVisualComparer(),
+ visualDiffGenerator: new MagickNetVisualDiffGenerator(),
+ ciArtifactsDirectory: ciArtifactsDirectory);
+
+ _imageEditorFactory = new MagickNetImageEditorFactory();
+ _visualTestContext = new VisualTestContext();
+ }
+
+ public override IConfig GetTestConfig()
+ {
+ IConfig config = new Config();
+ config.SetProperty("AppId", "com.microsoft.mauicompatibilitygallery");
+
+ switch (_testDevice)
+ {
+ case TestDevice.Android:
+ config.SetProperty("DeviceName", Environment.GetEnvironmentVariable("DEVICE_SKIN") ?? "");
+ config.SetProperty("PlatformVersion", Environment.GetEnvironmentVariable("PLATFORM_VERSION") ?? "");
+ config.SetProperty("Udid", Environment.GetEnvironmentVariable("DEVICE_UDID") ?? "");
+ break;
+ case TestDevice.iOS:
+ config.SetProperty("DeviceName", Environment.GetEnvironmentVariable("DEVICE_NAME") ?? "iPhone X");
+ config.SetProperty("PlatformVersion", Environment.GetEnvironmentVariable("PLATFORM_VERSION") ?? "17.0");
+ config.SetProperty("Udid", Environment.GetEnvironmentVariable("DEVICE_UDID") ?? "");
+ break;
+ }
+
+ return config;
+ }
+
+ public void VerifyScreenshot(string? name = null)
+ {
+ string deviceName = GetTestConfig().GetProperty("DeviceName") ?? string.Empty;
+ // Remove the XHarness suffix if present
+ deviceName = deviceName.Replace(" - created by XHarness", "", StringComparison.Ordinal);
+
+ /*
+ Determine the environmentName, used as the directory name for visual testing snaphots. Here are the rules/conventions:
+ - Names are lower case, no spaces.
+ - By default, the name matches the platform (android, ios, windows, or mac).
+ - Each platform has a default device (or set of devices) - if the snapshot matches the default no suffix is needed (e.g. just ios).
+ - If tests are run on secondary devices that produce different snapshots, the device name is used as suffix (e.g. ios-iphonex).
+ - If tests are run on secondary devices with multiple OS versions that produce different snapshots, both device name and os version are
+ used as a suffix (e.g. ios-iphonex-16_4). We don't have any cases of this today but may eventually. The device name comes first here,
+ before os version, because most visual testing differences come from different sceen size (a device thing), not OS version differences,
+ but both can happen.
+ */
+ string environmentName = string.Empty;
+
+ switch (_testDevice)
+ {
+ case TestDevice.Android:
+ if (deviceName == "Nexus 5X")
+ {
+ environmentName = "android";
+ }
+ else
+ {
+ Assert.Fail($"Android visual tests should be run on an Nexus 5X (API 30) emulator image, but the current device is '{deviceName}'. Follow the steps on the MAUI UI testing wiki.");
+ }
+ break;
+
+ case TestDevice.iOS:
+ if (deviceName == "iPhone Xs (iOS 17.2)")
+ {
+ environmentName = "ios";
+ }
+ else if (deviceName == "iPhone X (iOS 16.4)")
+ {
+ environmentName = "ios-iphonex";
+ }
+ else
+ {
+ Assert.Fail($"iOS visual tests should be run on iPhone Xs (iOS 17.2) or iPhone X (iOS 16.4) simulator images, but the current device is '{deviceName}'. Follow the steps on the MAUI UI testing wiki.");
+ }
+ break;
+
+ case TestDevice.Windows:
+ environmentName = "windows";
+ break;
+
+ case TestDevice.Mac:
+ // For now, ignore visual tests on Mac Catalyst since the Appium screenshot on Mac (unlike Windows)
+ // is of the entire screen, not just the app. Later when xharness relay support is in place to
+ // send a message to the MAUI app to get the screenshot, we can use that to just screenshot
+ // the app.
+ Assert.Ignore("MacCatalyst isn't supported yet for visual tests");
+ break;
+
+ default:
+ throw new NotImplementedException($"Unknown device type {_testDevice}");
+ }
+
+ name ??= TestContext.CurrentContext.Test.MethodName ?? TestContext.CurrentContext.Test.Name;
+
+ byte[] screenshotPngBytes = RunningApp.Screenshot() ?? throw new InvalidOperationException("Failed to get screenshot");
+
+ var actualImage = new ImageSnapshot(screenshotPngBytes, ImageSnapshotFormat.PNG);
+
+ // For Android and iOS, crop off the OS status bar at the top since it's not part of the
+ // app itself and contains the time, which always changes. For WinUI, crop off the title
+ // bar at the top as it varies slightly based on OS theme and is also not part of the app.
+ int cropFromTop = _testDevice switch
+ {
+ TestDevice.Android => 60,
+ TestDevice.iOS => environmentName == "ios-iphonex" ? 90 : 110,
+ TestDevice.Windows => 32,
+ _ => 0,
+ };
+
+ // For Android also crop the 3 button nav from the bottom, since it's not part of the
+ // app itself and the button color can vary (the buttons change clear briefly when tapped)
+ int cropFromBottom = _testDevice switch
+ {
+ TestDevice.Android => 125,
+ _ => 0,
+ };
+
+ if (cropFromTop > 0 || cropFromBottom > 0)
+ {
+ IImageEditor imageEditor = _imageEditorFactory.CreateImageEditor(actualImage);
+ (int width, int height) = imageEditor.GetSize();
+
+ imageEditor.Crop(0, cropFromTop, width, height - cropFromTop - cropFromBottom);
+
+ actualImage = imageEditor.GetUpdatedImage();
+ }
+
+ _visualRegressionTester.VerifyMatchesSnapshot(name!, actualImage, environmentName: environmentName, testContext: _visualTestContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestBase.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestBase.cs
new file mode 100644
index 000000000000..936e5a1f7780
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestBase.cs
@@ -0,0 +1,175 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+using NUnit.Framework.Interfaces;
+using UITest.Core;
+
+namespace UITests
+{
+ public abstract class UITestBase : UITestContextBase
+ {
+ public UITestBase(TestDevice testDevice)
+ : base(testDevice)
+ {
+ }
+
+ [SetUp]
+ public void RecordTestSetup()
+ {
+ var name = TestContext.CurrentContext.Test.MethodName ?? TestContext.CurrentContext.Test.Name;
+ TestContext.Progress.WriteLine($">>>>> {DateTime.Now} {name} Start");
+ }
+
+ [TearDown]
+ public void RecordTestTeardown()
+ {
+ var name = TestContext.CurrentContext.Test.MethodName ?? TestContext.CurrentContext.Test.Name;
+ TestContext.Progress.WriteLine($">>>>> {DateTime.Now} {name} Stop");
+ }
+
+ protected virtual void FixtureSetup()
+ {
+ var name = TestContext.CurrentContext.Test.MethodName ?? TestContext.CurrentContext.Test.Name;
+ TestContext.Progress.WriteLine($">>>>> {DateTime.Now} {nameof(FixtureSetup)} for {name}");
+ }
+
+ protected virtual void FixtureTeardown()
+ {
+ var name = TestContext.CurrentContext.Test.MethodName ?? TestContext.CurrentContext.Test.Name;
+ TestContext.Progress.WriteLine($">>>>> {DateTime.Now} {nameof(FixtureTeardown)} for {name}");
+ }
+
+ [TearDown]
+ public void UITestBaseTearDown()
+ {
+ if (App.AppState == ApplicationState.NotRunning)
+ {
+ SaveDeviceDiagnosticInfo();
+
+ Reset();
+ FixtureSetup();
+
+ // Assert.Fail will immediately exit the test which is desirable as the app is not
+ // running anymore so we can't capture any UI structures or any screenshots
+ Assert.Fail("The app was expected to be running still, investigate as possible crash");
+ }
+
+ var testOutcome = TestContext.CurrentContext.Result.Outcome;
+ if (testOutcome == ResultState.Error ||
+ testOutcome == ResultState.Failure)
+ {
+ SaveDeviceDiagnosticInfo();
+ SaveUIDiagnosticInfo();
+ }
+ }
+
+ [OneTimeSetUp]
+ public void OneTimeSetup()
+ {
+ InitialSetup(UITestContextSetupFixture.ServerContext);
+ try
+ {
+ FixtureSetup();
+ }
+ catch
+ {
+ SaveDeviceDiagnosticInfo();
+ SaveUIDiagnosticInfo();
+ throw;
+ }
+ }
+
+ [OneTimeTearDown]
+ public void OneTimeTearDown()
+ {
+ var outcome = TestContext.CurrentContext.Result.Outcome;
+
+ // We only care about setup failures as regular test failures will already do logging
+ if (outcome.Status == ResultState.SetUpFailure.Status &&
+ outcome.Site == ResultState.SetUpFailure.Site)
+ {
+ SaveDeviceDiagnosticInfo();
+ SaveUIDiagnosticInfo();
+ }
+
+ FixtureTeardown();
+ }
+
+ void SaveDeviceDiagnosticInfo([CallerMemberName] string? note = null)
+ {
+ var types = App.GetLogTypes().ToArray();
+ TestContext.Progress.WriteLine($">>>>> {DateTime.Now} Log types: {string.Join(", ", types)}");
+
+ foreach (var logType in new[] { "logcat" })
+ {
+ if (!types.Contains(logType, StringComparer.InvariantCultureIgnoreCase))
+ continue;
+
+ var logsPath = GetGeneratedFilePath($"AppLogs-{logType}.log", note);
+ if (logsPath is not null)
+ {
+ var entries = App.GetLogEntries(logType);
+ File.WriteAllLines(logsPath, entries);
+
+ AddTestAttachment(logsPath, Path.GetFileName(logsPath));
+ }
+ }
+ }
+
+ void SaveUIDiagnosticInfo([CallerMemberName] string? note = null)
+ {
+ var screenshotPath = GetGeneratedFilePath("ScreenShot.png", note);
+ if (screenshotPath is not null)
+ {
+ _ = RunningApp.Screenshot(screenshotPath);
+
+ AddTestAttachment(screenshotPath, Path.GetFileName(screenshotPath));
+ }
+
+ var pageSourcePath = GetGeneratedFilePath("PageSource.txt", note);
+ if (pageSourcePath is not null)
+ {
+ File.WriteAllText(pageSourcePath, App.ElementTree);
+
+ AddTestAttachment(pageSourcePath, Path.GetFileName(pageSourcePath));
+ }
+ }
+
+ string? GetGeneratedFilePath(string filename, string? note = null)
+ {
+ // App could be null if UITestContext was not able to connect to the test process (e.g. port already in use etc...)
+ if (UITestContext is null)
+ return null;
+
+ if (string.IsNullOrEmpty(note))
+ note = "-";
+ else
+ note = $"-{note}-";
+
+ filename = $"{Path.GetFileNameWithoutExtension(filename)}-{Guid.NewGuid().ToString("N")}{Path.GetExtension(filename)}";
+
+ var logDir =
+ Path.GetDirectoryName(Environment.GetEnvironmentVariable("APPIUM_LOG_FILE") ??
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))!;
+
+ var name =
+ TestContext.CurrentContext.Test.MethodName ??
+ TestContext.CurrentContext.Test.Name;
+
+ return Path.Combine(logDir, $"{name}-{_testDevice}{note}{filename}");
+ }
+
+ void AddTestAttachment(string filePath, string? description = null)
+ {
+ try
+ {
+ TestContext.AddTestAttachment(filePath, description);
+ }
+ catch (FileNotFoundException e) when (e.Message == "Test attachment file path could not be found.")
+ {
+ // Add the file path to better troubleshoot when these errors occur
+ throw new FileNotFoundException($"Test attachment file path could not be found: '{filePath}' {description}", e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestCategories.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestCategories.cs
new file mode 100644
index 000000000000..aa8c41a63837
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestCategories.cs
@@ -0,0 +1,68 @@
+namespace UITests
+{
+ internal static class UITestCategories
+ {
+ public const string ViewBaseTests = "ViewBaseTests";
+ public const string ActionSheet = "ActionSheet";
+ public const string ActivityIndicator = "ActivityIndicator";
+ public const string Animation = "Animation";
+ public const string AutomationId = "AutomationID";
+ public const string BoxView = "BoxView";
+ public const string Button = "Button";
+ public const string CarouselView = "CarouselView";
+ public const string Cells = "Cells";
+ public const string CheckBox = "CheckBox";
+ public const string CollectionView = "CollectionView";
+ public const string ContextActions = "ContextActions";
+ public const string DatePicker = "DatePicker";
+ public const string DragAndDrop = "DragAndDrop";
+ public const string DisplayAlert = "DisplayAlert";
+ public const string Editor = "Editor";
+ public const string Entry = "Entry";
+ public const string Frame = "Frame";
+ public const string Image = "Image";
+ public const string ImageButton = "ImageButton";
+ public const string Label = "Label";
+ public const string Layout = "Layout";
+ public const string ListView = "ListView";
+ public const string UwpIgnore = "UwpIgnore";
+ public const string LifeCycle = "Lifecycle";
+ public const string FlyoutPage = "FlyoutPage";
+ public const string Picker = "Picker";
+ public const string ProgressBar = "ProgressBar";
+ public const string RequiresInternetConnection = "RequiresInternetConnection";
+ public const string RootGallery = "RootGallery";
+ public const string ScrollView = "ScrollView";
+ public const string SearchBar = "SearchBar";
+ public const string Slider = "Slider";
+ public const string Stepper = "Stepper";
+ public const string Switch = "Switch";
+ public const string SwipeView = "SwipeView";
+ public const string TableView = "TableView";
+ public const string TimePicker = "TimePicker";
+ public const string ToolbarItem = "ToolbarItem";
+ public const string WebView = "WebView";
+ public const string Maps = "Maps";
+ public const string InputTransparent = "InputTransparent";
+ public const string IsEnabled = "IsEnabled";
+ public const string Gestures = "Gestures";
+ public const string Navigation = "Navigation";
+ public const string Effects = "Effects";
+ public const string Focus = "Focus";
+ public const string ManualReview = "ManualReview";
+ public const string Performance = "Performance";
+ public const string AppLinks = "AppLinks";
+ public const string Shell = "Shell";
+ public const string TabbedPage = "TabbedPage";
+ public const string CustomHandlers = "CustomHandlers";
+ public const string Page = "Page";
+ public const string RefreshView = "RefreshView";
+ public const string TitleView = "TitleView";
+ public const string DisplayPrompt = "DisplayPrompt";
+ public const string IndicatorView = "IndicatorView";
+ public const string RadioButton = "RadioButton";
+ public const string Shape = "Shape";
+ public const string Accessibility = "Accessibility";
+ public const string Brush = "Brush";
+ }
+}
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestContextBase.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestContextBase.cs
new file mode 100644
index 000000000000..68f0a129e5dd
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestContextBase.cs
@@ -0,0 +1,91 @@
+using OpenQA.Selenium.Appium;
+using UITest.Appium;
+using UITest.Core;
+
+namespace UITests
+{
+ public abstract class UITestContextBase
+ {
+ static IUIClientContext? UiTestContext;
+ IServerContext? _context;
+ protected TestDevice _testDevice;
+
+ public UITestContextBase(TestDevice testDevice)
+ {
+ _testDevice = testDevice;
+ }
+
+ public static IUIClientContext? UITestContext { get { return UiTestContext; } }
+
+ protected AppiumDriver? Driver
+ {
+ get
+ {
+ if (App is AppiumApp app)
+ {
+ return app.Driver;
+ }
+
+ return null;
+ }
+ }
+
+ public TestDevice Device
+ {
+ get
+ {
+ return UITestContext == null
+ ? throw new InvalidOperationException($"Call {nameof(InitialSetup)} before accessing the {nameof(Device)} property.")
+ : UITestContext.Config.GetProperty("TestDevice");
+ }
+ }
+
+ public IApp App
+ {
+ get
+ {
+ return UITestContext == null
+ ? throw new InvalidOperationException($"Call {nameof(InitialSetup)} before accessing the {nameof(App)} property.")
+ : UITestContext.App;
+ }
+ }
+
+ internal IApp RunningApp => App;
+
+ public abstract IConfig GetTestConfig();
+
+ public void InitialSetup(IServerContext context)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ InitialSetup(context, false);
+ }
+
+ public void Reset()
+ {
+ if (_context == null)
+ {
+ throw new InvalidOperationException($"Cannot {nameof(Reset)} if {nameof(InitialSetup)} has not been called.");
+ }
+
+ InitialSetup(_context, true);
+ }
+
+ private void InitialSetup(IServerContext context, bool reset)
+ {
+ var testConfig = GetTestConfig();
+ testConfig.SetProperty("TestDevice", _testDevice);
+
+ // Check to see if we have a context already from a previous test and re-use it as creating the driver is expensive
+ if (reset || UiTestContext == null)
+ {
+ UiTestContext?.Dispose();
+ UiTestContext = context.CreateUIClientContext(testConfig);
+ }
+
+ if (UiTestContext == null)
+ {
+ throw new InvalidOperationException("Failed to get the driver.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestExtensions.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestExtensions.cs
new file mode 100644
index 000000000000..c1b1b1461e67
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestExtensions.cs
@@ -0,0 +1,90 @@
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+using System.Drawing;
+
+namespace UITests
+{
+ public static class UITestExtensions
+ {
+ const string GoToTestButtonId = "GoToTestButton";
+
+ public static void Back(this UITestContextBase testBase)
+ {
+ if (testBase.Device == TestDevice.Android)
+ {
+ var query = testBase.App.Query.ByAccessibilityId("Navigate up").First();
+ query.Click();
+ }
+ else if (testBase.Device == TestDevice.iOS || testBase.Device == TestDevice.Mac)
+ {
+ // Get the first NavigationBar we can find and the first button in it (the back button), index starts at 1
+ var queryBy = testBase.App.Query.ByClass("XCUIElementTypeNavigationBar").First().ByClass("XCUIElementTypeButton").First();
+ queryBy.Click();
+ }
+ else
+ {
+ testBase.RunningApp.FindElement("NavigationViewBackButton").Click();
+ }
+ }
+
+ public static void NavigateToGallery(this IApp app, string page)
+ {
+ app.WaitForElement(GoToTestButtonId, "Timed out waiting for Go To Test button to appear", TimeSpan.FromMinutes(2));
+ NavigateTo(app, page);
+ }
+
+ public static void NavigateTo(this IApp app, string text)
+ {
+ app.WaitForElement("SearchBar");
+ app.ClearText("SearchBar");
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ app.EnterText("SearchBar", text);
+ }
+ app.Tap(GoToTestButtonId);
+
+ app.WaitForNoElement(GoToTestButtonId, "Timed out waiting for Go To Test button to disappear", TimeSpan.FromMinutes(1));
+ }
+
+ public static void NavigateToIssues(this IApp app)
+ {
+ app.WaitForElement(GoToTestButtonId, "Timed out waiting for Go To Test button to appear", TimeSpan.FromMinutes(2));
+
+ app.WaitForElement("SearchBar");
+ app.ClearText("SearchBar");
+
+ app.Tap(GoToTestButtonId);
+ app.WaitForElement("TestCasesIssueList");
+ }
+
+ public static void IgnoreIfPlatforms(this UITestBase? test, IEnumerable devices, string? message = null)
+ {
+ foreach (var device in devices)
+ {
+ test?.IgnoreIfPlatform(device, message);
+ }
+ }
+
+ public static void IgnoreIfPlatform(this UITestBase? test, TestDevice device, string? message = null)
+ {
+ if (test != null && test.Device == device)
+ {
+ if (string.IsNullOrEmpty(message))
+ Assert.Ignore();
+ else
+ Assert.Ignore(message);
+ }
+ }
+
+ public static int CenterX(this Rectangle rect)
+ {
+ return rect.X + rect.Width / 2;
+ }
+
+ public static int CenterY(this Rectangle rect)
+ {
+ return rect.Y + rect.Height / 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestIgnoreAttributes.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestIgnoreAttributes.cs
new file mode 100644
index 000000000000..4113ca8aaa5e
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/UITestIgnoreAttributes.cs
@@ -0,0 +1,112 @@
+using NUnit.Framework;
+
+namespace UITests
+{
+ public class IgnoredDuringMoveToAppium : IgnoreAttribute
+ {
+ public IgnoredDuringMoveToAppium() : base(nameof(IgnoredDuringMoveToAppium))
+ {
+ }
+ public IgnoredDuringMoveToAppium(string reason) : base(reason)
+ {
+ }
+ }
+
+ public class FailsOnAllPlatforms : IgnoreAttribute
+ {
+ public FailsOnAllPlatforms() : base(nameof(FailsOnAndroid))
+ {
+ }
+ public FailsOnAllPlatforms(string reason) : base(reason)
+ {
+ }
+ }
+
+#if ANDROID
+ public class FailsOnAndroid : IgnoreAttribute
+ {
+ public FailsOnAndroid() : base(nameof(FailsOnAndroid))
+ {
+ }
+ public FailsOnAndroid(string reason) : base(reason)
+ {
+ }
+ }
+#else
+ public class FailsOnAndroid : CategoryAttribute
+ {
+ public FailsOnAndroid() : base(nameof(FailsOnAndroid))
+ {
+ }
+ public FailsOnAndroid(string name) : base(name)
+ {
+ }
+ }
+#endif
+
+#if IOS
+ public class FailsOnIOS : IgnoreAttribute
+ {
+ public FailsOnIOS() : base(nameof(FailsOnIOS))
+ {
+ }
+ public FailsOnIOS(string reason) : base(reason)
+ {
+ }
+ }
+#else
+ public class FailsOnIOS : CategoryAttribute
+ {
+ public FailsOnIOS() : base(nameof(FailsOnIOS))
+ {
+ }
+ public FailsOnIOS(string name) : base(name)
+ {
+ }
+ }
+#endif
+
+#if MACCATALYST
+ public class FailsOnMac : IgnoreAttribute
+ {
+ public FailsOnMac() : base(nameof(FailsOnMac))
+ {
+ }
+ public FailsOnMac(string reason) : base(reason)
+ {
+ }
+ }
+#else
+ public class FailsOnMac : CategoryAttribute
+ {
+ public FailsOnMac() : base(nameof(FailsOnMac))
+ {
+ }
+ public FailsOnMac(string name) : base(name)
+ {
+ }
+ }
+#endif
+
+#if WINDOWS
+ public class FailsOnWindows : IgnoreAttribute
+ {
+ public FailsOnWindows() : base(nameof(FailsOnWindows))
+ {
+ }
+ public FailsOnWindows(string reason) : base(reason)
+ {
+ }
+ }
+#else
+ public class FailsOnWindows : CategoryAttribute
+ {
+ public FailsOnWindows() : base(nameof(FailsOnWindows))
+ {
+ }
+ public FailsOnWindows(string name) : base(name)
+ {
+ }
+ }
+#endif
+}
diff --git a/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/VisualTestContext.cs b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/VisualTestContext.cs
new file mode 100644
index 000000000000..38598c27c489
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/Shared.Appium.UITests/VisualTestContext.cs
@@ -0,0 +1,11 @@
+using NUnit.Framework;
+using VisualTestUtils;
+
+namespace UITests
+{
+ public class VisualTestContext : ITestContext
+ {
+ public void AddTestAttachment(string filePath, string? description = null) =>
+ TestContext.AddTestAttachment(filePath, description);
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/iOS.Appium.UITests/ControlGallery.iOS.Appium.UITests.csproj b/src/Compatibility/ControlGallery/test/iOS.Appium.UITests/ControlGallery.iOS.Appium.UITests.csproj
new file mode 100644
index 000000000000..4558c59d5333
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/iOS.Appium.UITests/ControlGallery.iOS.Appium.UITests.csproj
@@ -0,0 +1,39 @@
+
+
+
+ $(_MauiDotNetTfm)
+ enable
+ enable
+ true
+ UITests
+ $(DefineConstants);IOS;IOSUITEST
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Compatibility/ControlGallery/test/iOS.Appium.UITests/PlatformSpecificSampleTest.cs b/src/Compatibility/ControlGallery/test/iOS.Appium.UITests/PlatformSpecificSampleTest.cs
new file mode 100644
index 000000000000..98435f47f3e8
--- /dev/null
+++ b/src/Compatibility/ControlGallery/test/iOS.Appium.UITests/PlatformSpecificSampleTest.cs
@@ -0,0 +1,16 @@
+using NUnit.Framework;
+
+namespace UITests;
+
+public class PlatformSpecificSampleTest : UITest
+{
+ public PlatformSpecificSampleTest(TestDevice testDevice) : base(testDevice)
+ {
+ }
+
+ [Test]
+ public void SampleTest()
+ {
+ Driver?.GetScreenshot().SaveAsFile($"{nameof(SampleTest)}.png");
+ }
+}
\ No newline at end of file
diff --git a/src/Compatibility/Core/src/Android/CollectionView/ObservableItemsSource.cs b/src/Compatibility/Core/src/Android/CollectionView/ObservableItemsSource.cs
index eb68661752ff..9ca19ac3e8cb 100644
--- a/src/Compatibility/Core/src/Android/CollectionView/ObservableItemsSource.cs
+++ b/src/Compatibility/Core/src/Android/CollectionView/ObservableItemsSource.cs
@@ -137,7 +137,7 @@ void Move(NotifyCollectionChangedEventArgs args)
void Add(NotifyCollectionChangedEventArgs args)
{
- var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+ var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : _itemsSource.IndexOf(args.NewItems[0]);
startIndex = AdjustPositionForHeader(startIndex);
var count = args.NewItems.Count;
@@ -178,7 +178,7 @@ void Remove(NotifyCollectionChangedEventArgs args)
void Replace(NotifyCollectionChangedEventArgs args)
{
- var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+ var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : _itemsSource.IndexOf(args.NewItems[0]);
startIndex = AdjustPositionForHeader(startIndex);
var newCount = args.NewItems.Count;
@@ -229,21 +229,5 @@ internal object ElementAt(int index)
return -1;
}
-
- internal int IndexOf(object item)
- {
- if (_itemsSource is IList list)
- return list.IndexOf(item);
-
- int count = 0;
- foreach (var i in _itemsSource)
- {
- if (i == item)
- return count;
- count++;
- }
-
- return -1;
- }
}
}
diff --git a/src/Compatibility/Core/src/iOS/CollectionView/ObservableItemsSource.cs b/src/Compatibility/Core/src/iOS/CollectionView/ObservableItemsSource.cs
index f67f1d3ca9c1..c18e0e5e44a8 100644
--- a/src/Compatibility/Core/src/iOS/CollectionView/ObservableItemsSource.cs
+++ b/src/Compatibility/Core/src/iOS/CollectionView/ObservableItemsSource.cs
@@ -154,7 +154,7 @@ void Add(NotifyCollectionChangedEventArgs args)
{
var count = args.NewItems.Count;
Count += count;
- var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+ var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : _itemsSource.IndexOf(args.NewItems[0]);
// Queue up the updates to the UICollectionView
Update(() => CollectionView.InsertItems(CreateIndexesFrom(startIndex, count)), args);
@@ -185,7 +185,7 @@ void Replace(NotifyCollectionChangedEventArgs args)
if (newCount == args.OldItems.Count)
{
- var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+ var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : _itemsSource.IndexOf(args.NewItems[0]);
// We are replacing one set of items with a set of equal size; we can do a simple item range update
@@ -245,22 +245,6 @@ internal object ElementAt(int index)
return -1;
}
- internal int IndexOf(object item)
- {
- if (_itemsSource is IList list)
- return list.IndexOf(item);
-
- int count = 0;
- foreach (var i in _itemsSource)
- {
- if (i == item)
- return count;
- count++;
- }
-
- return -1;
- }
-
void Update(Action update, NotifyCollectionChangedEventArgs args)
{
if (CollectionView.Hidden)
diff --git a/src/Controls/samples/Controls.Sample.UITests/CoreViews/CorePageView.cs b/src/Controls/samples/Controls.Sample.UITests/CoreViews/CorePageView.cs
index cac261a064e4..ff54e348b2fe 100644
--- a/src/Controls/samples/Controls.Sample.UITests/CoreViews/CorePageView.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/CoreViews/CorePageView.cs
@@ -64,11 +64,11 @@ public override string ToString()
new GalleryPageFactory(() => new ImageButtonCoreGalleryPage(), "Image Button Gallery"),
new GalleryPageFactory(() => new ImageCoreGalleryPage(), "Image Gallery"),
new GalleryPageFactory(() => new KeyboardScrollingGridGallery(), "Keyboard Scrolling Gallery - Grid with Star Row"),
- new GalleryPageFactory(() => new KeyboardScrollingNonScrollingPageLargeTitlesGallery(), "Keyboard Scrolling Gallery - NonScrolling Page / Large Titles"),
- new GalleryPageFactory(() => new KeyboardScrollingNonScrollingPageSmallTitlesGallery(), "Keyboard Scrolling Gallery - NonScrolling Page / Small Titles"),
+ new GalleryPageFactory(() => new KeyboardScrollingNonScrollingPageLargeTitlesGallery(), "Keyboard Scrolling Gallery - NonScrolling Page / Large Titles"),
+ new GalleryPageFactory(() => new KeyboardScrollingNonScrollingPageSmallTitlesGallery(), "Keyboard Scrolling Gallery - NonScrolling Page / Small Titles"),
new GalleryPageFactory(() => new KeyboardScrollingScrollingPageLargeTitlesGallery(), "Keyboard Scrolling Gallery - Scrolling Page / Large Titles"),
new GalleryPageFactory(() => new KeyboardScrollingScrollingPageSmallTitlesGallery(), "Keyboard Scrolling Gallery - Scrolling Page / Small Titles"),
- new GalleryPageFactory(() => new LabelCoreGalleryPage(), "Label Gallery"),
+ new GalleryPageFactory(() => new LabelCoreGalleryPage(), "Label Gallery"),
new GalleryPageFactory(() => new ListViewCoreGalleryPage(), "ListView Gallery"),
new GalleryPageFactory(() => new PickerCoreGalleryPage(), "Picker Gallery"),
new GalleryPageFactory(() => new ProgressBarCoreGalleryPage(), "Progress Bar Gallery"),
@@ -152,19 +152,34 @@ async Task PushPage(Page contentPage)
}
readonly Dictionary _titleToPage;
- public async Task PushPage(string pageTitle)
+ public Task NavigateToGalleryPage(string pageTitle)
{
if (_titleToPage.TryGetValue(pageTitle.ToLowerInvariant(), out GalleryPageFactory pageFactory))
{
var page = pageFactory.Realize();
+ this.Window.Page = page;
+ return Task.FromResult(true);
+ }
+
+ return Task.FromResult(false);
+ }
- await PushPage(page);
+ public async Task NavigateToTest(string pageTitle)
+ {
+ var testCaseScreen = new TestCases.TestCaseScreen();
+ if (testCaseScreen.TryToNavigateTo(pageTitle))
+ {
+ return true;
+ }
+ else if (await NavigateToGalleryPage(pageTitle))
+ {
return true;
}
return false;
}
+
public void FilterPages(string filter)
{
ItemsSource = string.IsNullOrWhiteSpace(filter)
diff --git a/src/Controls/samples/Controls.Sample.UITests/CoreViews/CoreRootPage.cs b/src/Controls/samples/Controls.Sample.UITests/CoreViews/CoreRootPage.cs
index a2d15d58edc3..4cc600e2319d 100644
--- a/src/Controls/samples/Controls.Sample.UITests/CoreViews/CoreRootPage.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/CoreViews/CoreRootPage.cs
@@ -30,7 +30,7 @@ public CoreRootPage(Microsoft.Maui.Controls.Page rootPage)
{
if (!string.IsNullOrEmpty(searchBar.Text))
{
- await corePageView.PushPage(searchBar.Text);
+ await corePageView.NavigateToTest(searchBar.Text);
}
else
{
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/HideSoftInputOnTappedPage.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/HideSoftInputOnTappedPage.cs
index f6d5d9dceb4d..06a29080b924 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/HideSoftInputOnTappedPage.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/HideSoftInputOnTappedPage.cs
@@ -6,15 +6,19 @@ namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.None, 12003, "Hide Soft Input On Tapped Page",
PlatformAffected.iOS | PlatformAffected.Android)]
- public class HideSoftInputOnTappedPage : TestContentPage
+ public class HideSoftInputOnTappedPage : NavigationPage
{
- public HideSoftInputOnTappedPage()
+ public HideSoftInputOnTappedPage() : base(new StartingPage())
{
+
}
- protected override void Init()
+ public class StartingPage : TestContentPage
{
- Content = new VerticalStackLayout()
+
+ protected override void Init()
+ {
+ Content = new VerticalStackLayout()
{
new Button()
{
@@ -36,48 +40,48 @@ protected override void Init()
}
};
- (Content as VerticalStackLayout).Spacing = 6;
- }
+ (Content as VerticalStackLayout).Spacing = 6;
+ }
- public class TestPage : ContentPage
- {
- Label _isKeyboardOpen = new Label();
- public TestPage(bool hideSoftInputOnTapped)
+ public class TestPage : ContentPage
{
- this.HideSoftInputOnTapped = hideSoftInputOnTapped;
- Title = "Hide Soft Input On Tapped Page";
- _isKeyboardOpen.Text = "Tap Page and Keyboard Should Close";
- _isKeyboardOpen.AutomationId = "EmptySpace";
+ Label _isKeyboardOpen = new Label();
+ public TestPage(bool hideSoftInputOnTapped)
+ {
+ this.HideSoftInputOnTapped = hideSoftInputOnTapped;
+ Title = "Hide Soft Input On Tapped Page";
+ _isKeyboardOpen.Text = "Tap Page and Keyboard Should Close";
+ _isKeyboardOpen.AutomationId = "EmptySpace";
- Entry entry = new Entry() { AutomationId = "Entry" };
+ Entry entry = new Entry() { AutomationId = "Entry" };
- var checkbox = new CheckBox();
- checkbox.BindingContext = this;
- checkbox.SetBinding(
- CheckBox.IsCheckedProperty,
- nameof(HideSoftInputOnTapped));
+ var checkbox = new CheckBox();
+ checkbox.BindingContext = this;
+ checkbox.SetBinding(
+ CheckBox.IsCheckedProperty,
+ nameof(HideSoftInputOnTapped));
- checkbox.AutomationId = "ToggleHideSoftInputOnTapped";
+ checkbox.AutomationId = "ToggleHideSoftInputOnTapped";
- Entry dontHideKeyboardWhenTappingPage = new Entry()
- {
- Placeholder = "When this entry is focused tapping the page won't close the keyboard",
- AutomationId = "DontHideKeyboardWhenTappingPage"
- };
+ Entry dontHideKeyboardWhenTappingPage = new Entry()
+ {
+ Placeholder = "When this entry is focused tapping the page won't close the keyboard",
+ AutomationId = "DontHideKeyboardWhenTappingPage"
+ };
- dontHideKeyboardWhenTappingPage
- .Focused += (_, _) => checkbox.IsChecked = false;
+ dontHideKeyboardWhenTappingPage
+ .Focused += (_, _) => checkbox.IsChecked = false;
- Entry hideKeyboardWhenTappingPage = new Entry()
- {
- Placeholder = "When this entry is focused tapping the page will close the keyboard",
- AutomationId = "HideKeyboardWhenTappingPage"
- };
+ Entry hideKeyboardWhenTappingPage = new Entry()
+ {
+ Placeholder = "When this entry is focused tapping the page will close the keyboard",
+ AutomationId = "HideKeyboardWhenTappingPage"
+ };
- hideKeyboardWhenTappingPage
- .Focused += (_, _) => checkbox.IsChecked = true;
+ hideKeyboardWhenTappingPage
+ .Focused += (_, _) => checkbox.IsChecked = true;
- Content = new VerticalStackLayout()
+ Content = new VerticalStackLayout()
{
new HorizontalStackLayout()
{
@@ -90,6 +94,7 @@ public TestPage(bool hideSoftInputOnTapped)
dontHideKeyboardWhenTappingPage,
hideKeyboardWhenTappingPage
};
+ }
}
}
}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue11501.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue11501.cs
index ae68e4fe475e..d5154fc75091 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue11501.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue11501.cs
@@ -8,49 +8,57 @@
namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 11501, "Making Fragment Changes While App is Backgrounded Fails", PlatformAffected.Android)]
- public class Issue11501 : TestContentPage
+
+ public class Issue11501 : NavigationPage
{
- Func _currentTest;
- Page _mainPage;
- List _modalStack;
- Window _window;
- public Issue11501()
+ public Issue11501() : base(new TestPage())
{
- Loaded += OnLoaded;
}
- private void OnLoaded(object sender, EventArgs e)
- {
- _window = Window;
- _mainPage = Application.Current.MainPage;
- _modalStack = Navigation.ModalStack.ToList();
- }
- private async void OnWindowActivated(object sender, EventArgs e)
+ public class TestPage : TestContentPage
{
- DisconnectFromWindow();
- if (_currentTest is not null)
+ Func _currentTest;
+ Page _mainPage;
+ List _modalStack;
+ Window _window;
+ public TestPage()
{
- await Task.Yield();
- await _currentTest();
- _currentTest = null;
+ Loaded += OnLoaded;
}
- }
- void ConnectToWindow()
- {
- _window.Stopped -= OnWindowActivated;
- _window.Stopped += OnWindowActivated;
- }
+ private void OnLoaded(object sender, EventArgs e)
+ {
+ _window = Window;
+ _mainPage = Application.Current.MainPage;
+ _modalStack = Navigation.ModalStack.ToList();
+ }
- void DisconnectFromWindow()
- {
- _window.Stopped -= OnWindowActivated;
- }
+ private async void OnWindowActivated(object sender, EventArgs e)
+ {
+ DisconnectFromWindow();
+ if (_currentTest is not null)
+ {
+ await Task.Yield();
+ await _currentTest();
+ _currentTest = null;
+ }
+ }
- protected override void Init()
- {
- Content = new VerticalStackLayout()
+ void ConnectToWindow()
+ {
+ _window.Stopped -= OnWindowActivated;
+ _window.Stopped += OnWindowActivated;
+ }
+
+ void DisconnectFromWindow()
+ {
+ _window.Stopped -= OnWindowActivated;
+ }
+
+ protected override void Init()
+ {
+ Content = new VerticalStackLayout()
{
new Button()
{
@@ -151,14 +159,14 @@ protected override void Init()
})
},
};
- }
+ }
- ContentPage CreateDestinationPage()
- {
- return new ContentPage()
+ ContentPage CreateDestinationPage()
{
- Title = "Test",
- Content = new VerticalStackLayout()
+ return new ContentPage()
+ {
+ Title = "Test",
+ Content = new VerticalStackLayout()
{
new Button()
{
@@ -177,7 +185,8 @@ ContentPage CreateDestinationPage()
})
}
}
- };
+ };
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue14829.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue14829.cs
index f8c2bf458aa4..cfedb9c0728a 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue14829.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue14829.cs
@@ -1,4 +1,5 @@
-using Microsoft.Maui.Controls;
+using System;
+using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace Maui.Controls.Sample.Issues
@@ -9,12 +10,11 @@ public class Issue14829 : TestContentPage
protected override void Init()
{
var navPage = new NavigationPage(new MainPage());
- NavigatedTo += Issue14829_NavigatedTo;
+ Loaded += OnLoaded;
- async void Issue14829_NavigatedTo(object sender, NavigatedToEventArgs e)
+ async void OnLoaded(object sender, EventArgs e)
{
- NavigatedTo -= Issue14829_NavigatedTo;
-
+ Loaded -= OnLoaded;
await Navigation.PushModalAsync(navPage);
}
}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue16499.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue16499.cs
index c886c671ff00..d73801e944b9 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue16499.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue16499.cs
@@ -3,32 +3,39 @@
namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 16499, "Crash when using NavigationPage.TitleView and Restarting App", PlatformAffected.Android)]
- public class Issue16499 : TestContentPage
+ public class Issue16499 : NavigationPage
{
- protected override void Init()
+ public Issue16499() : base(new MainPage())
{
- var contentPage = new ContentPage();
- var navPage = new NavigationPage(contentPage);
+ }
- NavigationPage.SetTitleView(contentPage, new Label());
+ public class MainPage : ContentPage
+ {
+ public MainPage() : base()
+ {
+ var contentPage = new ContentPage();
+ var navPage = new NavigationPage(contentPage);
- NavigatedTo += Issue16499_NavigatedTo;
+ NavigationPage.SetTitleView(contentPage, new Label());
- async void Issue16499_NavigatedTo(object sender, NavigatedToEventArgs e)
- {
- NavigatedTo -= Issue16499_NavigatedTo;
- await Navigation.PushModalAsync(navPage);
- await Navigation.PopModalAsync();
- await Navigation.PushModalAsync(navPage);
- await Navigation.PopModalAsync();
- this.Content = new VerticalStackLayout()
+ NavigatedTo += Issue16499_NavigatedTo;
+
+ async void Issue16499_NavigatedTo(object sender, NavigatedToEventArgs e)
{
- new Label()
+ NavigatedTo -= Issue16499_NavigatedTo;
+ await Navigation.PushModalAsync(navPage);
+ await Navigation.PopModalAsync();
+ await Navigation.PushModalAsync(navPage);
+ await Navigation.PopModalAsync();
+ Content = new VerticalStackLayout()
{
- Text = "If the app didn't crash this test was a success",
- AutomationId = "SuccessLabel"
- }
- };
+ new Label()
+ {
+ Text = "If the app didn't crash this test was a success",
+ AutomationId = "SuccessLabel"
+ }
+ };
+ }
}
}
}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml
index 818a40baa44a..8fe7409889c1 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml
@@ -4,28 +4,29 @@
AutomationId="NavBarTranslucentPage"
x:Class="Maui.Controls.Sample.Issues.Issue17022"
xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml.cs
index f78d041d0787..3f238ee3427c 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17022.xaml.cs
@@ -10,6 +10,14 @@ namespace Maui.Controls.Sample.Issues;
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 17022, "UINavigationBar is Translucent", PlatformAffected.iOS)]
+public class Issue17022NavPage : NavigationPage
+{
+ public Issue17022NavPage() : base(new Issue17022())
+ {
+
+ }
+}
+
public partial class Issue17022 : ContentPage
{
public Issue17022()
@@ -97,7 +105,7 @@ async void NewFlyoutPagePressed(System.Object sender, System.EventArgs e)
var detail = new NavigationPage(CreateMainPage(true, _notTopOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.BarBackgroundColor = Colors.Transparent;
@@ -111,7 +119,7 @@ async void NewFlyoutPageTransparentPressed(System.Object sender, System.EventArg
var detail = new NavigationPage(CreateMainPage(true, _notTopOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.BarBackgroundColor = Colors.Transparent;
@@ -123,7 +131,7 @@ async void NewFlyoutPageTranslucentPressed(System.Object sender, System.EventArg
var detail = new NavigationPage(CreateMainPage(true, _notTopOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.On().EnableTranslucentNavigationBar();
@@ -135,7 +143,7 @@ async void NewFlyoutPageTransparentTranslucentPressed(System.Object sender, Syst
var detail = new NavigationPage(CreateMainPage(true, _notTopOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.BarBackgroundColor = Colors.Transparent;
@@ -148,7 +156,7 @@ async void NewFlyoutPageGridPressed(System.Object sender, System.EventArgs e)
var detail = new NavigationPage(CreateMainPage(false, _notTopOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
await Navigation.PushModalAsync(flyoutPage);
@@ -159,7 +167,7 @@ async void NewFlyoutPageGridTransparentPressed(System.Object sender, System.Even
var detail = new NavigationPage(CreateMainPage(false, _notTopOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.BarBackgroundColor = Colors.Transparent;
@@ -171,7 +179,7 @@ async void NewFlyoutPageGridTranslucentPressed(System.Object sender, System.Even
var detail = new NavigationPage(CreateMainPage(false, _notTopOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.On().EnableTranslucentNavigationBar();
@@ -182,7 +190,7 @@ async void NewFlyoutPageGridTransparentTranslucentPressed(System.Object sender,
var detail = new NavigationPage(CreateMainPage(false, _topOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.BarBackgroundColor = Colors.Transparent;
@@ -190,7 +198,7 @@ async void NewFlyoutPageGridTransparentTranslucentPressed(System.Object sender,
await Navigation.PushModalAsync(flyoutPage);
}
- async void SemiTransparentNavigationPageBackgroundColorPressed(System.Object sender, System.EventArgs e)
+ async void SemiTransparentNavigationPageBackgroundColorPressed(System.Object sender, System.EventArgs e)
{
var mainPage = CreateMainPage(false, _topOfScreenText);
var navPage = new NavigationPage(mainPage)
@@ -217,7 +225,7 @@ async void SemiTransparentFlyoutPageBackgroundColorPressed(System.Object sender,
var detail = new NavigationPage(CreateMainPage(false, _topOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.BarBackgroundColor = Color.FromRgba(100, 100, 100, 50);
@@ -230,7 +238,7 @@ async void SemiTransparentFlyoutPageBrushPressed(System.Object sender, System.Ev
var detail = new NavigationPage(CreateMainPage(false, _topOfScreenText));
var flyoutPage = new FlyoutPage()
{
- Flyout = new ContentPage(){Title = "FlyoutPage"},
+ Flyout = new ContentPage() { Title = "FlyoutPage" },
Detail = detail
};
detail.BarBackground = Color.FromRgba(100, 100, 100, 50);
@@ -238,10 +246,11 @@ async void SemiTransparentFlyoutPageBrushPressed(System.Object sender, System.Ev
await Navigation.PushModalAsync(flyoutPage);
}
- ContentPage CreateMainPage (bool useSafeArea, string expectedText)
+ ContentPage CreateMainPage(bool useSafeArea, string expectedText)
{
- var mainPage = new ContentPage(){
- AutomationId="PopupMainPage"
+ var mainPage = new ContentPage()
+ {
+ AutomationId = "PopupMainPage"
};
var grid = new Grid
{
@@ -253,13 +262,13 @@ ContentPage CreateMainPage (bool useSafeArea, string expectedText)
},
};
- var button = new Button { Text = "Pop Page", AutomationId="PopPageButton" };
+ var button = new Button { Text = "Pop Page", AutomationId = "PopPageButton" };
button.Clicked += PopModalButtonClicked;
- grid.Add (new BoxView { BackgroundColor = Colors.Green, AutomationId="TopBoxView" }, 0, 0 );
- grid.Add (new Label { TextColor = Colors.Black, Margin= new Microsoft.Maui.Thickness(0,60,0,0), HorizontalTextAlignment = Microsoft.Maui.TextAlignment.Center, Text="Can you see me?" }, 0, 0 );
- grid.Add (new Label { Text = expectedText }, 0, 1 );
- grid.Add (button, 0, 2 );
+ grid.Add(new BoxView { BackgroundColor = Colors.Green, AutomationId = "TopBoxView" }, 0, 0);
+ grid.Add(new Label { TextColor = Colors.Black, Margin = new Microsoft.Maui.Thickness(0, 60, 0, 0), HorizontalTextAlignment = Microsoft.Maui.TextAlignment.Center, Text = "Can you see me?" }, 0, 0);
+ grid.Add(new Label { Text = expectedText }, 0, 1);
+ grid.Add(button, 0, 2);
grid.IgnoreSafeArea = true;
mainPage.Content = grid;
@@ -267,7 +276,7 @@ ContentPage CreateMainPage (bool useSafeArea, string expectedText)
return mainPage;
}
- async void PopModalButtonClicked (System.Object sender, System.EventArgs e)
+ async void PopModalButtonClicked(System.Object sender, System.EventArgs e)
{
await Navigation.PopModalAsync();
}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17347.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17347.cs
index 3f40323e06f1..a83880e5c904 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17347.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue17347.cs
@@ -4,32 +4,40 @@
namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 17347, "Setting a new TitleView on an already created page crashes iOS", PlatformAffected.iOS)]
- public class Issue17347 : TestContentPage
+ public class Issue17347 : NavigationPage
{
- protected override void Init()
+ public Issue17347() : base(new MainPage())
{
- var navPage = new NavigationPage(new MainPage());
- var label = new Label() { Text = "NavigatedTo Has Not Fired" };
- NavigatedTo += Issue16499_NavigatedTo;
- Content = new VerticalStackLayout()
- {
- label
- };
+ }
- async void Issue16499_NavigatedTo(object sender, NavigatedToEventArgs e)
+ public class MainPage : ContentPage
+ {
+ public MainPage()
{
- label.Text = "Navigated to Has Fired";
- NavigatedTo -= Issue16499_NavigatedTo;
+ var navPage = new NavigationPage(new TestPage());
+ var label = new Label() { Text = "NavigatedTo Has Not Fired" };
+ Content = new VerticalStackLayout()
+ {
+ label
+ };
+
+ NavigatedTo += Issue16499_NavigatedTo;
+
+ async void Issue16499_NavigatedTo(object sender, NavigatedToEventArgs e)
+ {
+ label.Text = "Navigated to Has Fired";
+ NavigatedTo -= Issue16499_NavigatedTo;
- await Navigation.PushModalAsync(navPage);
- await navPage.Navigation.PushAsync(new MainPage());
- await navPage.Navigation.PushAsync(new MainPage());
- await navPage.Navigation.PopAsync();
- await navPage.Navigation.PopAsync();
+ await Navigation.PushModalAsync(navPage);
+ await navPage.Navigation.PushAsync(new TestPage());
+ await navPage.Navigation.PushAsync(new TestPage());
+ await navPage.Navigation.PopAsync();
+ await navPage.Navigation.PopAsync();
+ }
}
}
- public partial class MainPage : ContentPage
+ public partial class TestPage : ContentPage
{
Label TopView;
static int i = 0;
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18242_2.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18242_2.xaml
new file mode 100644
index 000000000000..ef1de055d80c
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18242_2.xaml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18242_2.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18242_2.xaml.cs
new file mode 100644
index 000000000000..b388cfa276e9
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18242_2.xaml.cs
@@ -0,0 +1,38 @@
+using System;
+using Microsoft.Maui;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.ManualTest, "18242_2", "Button ImageSource not Scaling as expected - manual test", PlatformAffected.iOS)]
+ public partial class Issue18242_2 : ContentPage
+ {
+ readonly Random _random;
+
+ public Issue18242_2()
+ {
+ InitializeComponent();
+
+ _random = new Random();
+ }
+
+ void OnSliderPaddingValueChanged(object sender, ValueChangedEventArgs e)
+ {
+ TopButton.Padding =
+ BottonButton.Padding =
+ LeftButton.Padding =
+ RightButton.Padding = new Thickness(e.NewValue);
+ }
+
+ void OnUpdateSizeButtonClicked(object sender, EventArgs e)
+ {
+ TopButton.HeightRequest = TopButton.WidthRequest =
+ BottonButton.HeightRequest = BottonButton.WidthRequest =
+ LeftButton.HeightRequest = LeftButton.WidthRequest =
+ RightButton.HeightRequest = RightButton.WidthRequest =
+ _random.Next(200, 300);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml
index c0abc2708d57..bd547284c50a 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml
@@ -2,34 +2,33 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml.cs
index 54cc6e80c155..0d213b17c21e 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue18740.xaml.cs
@@ -4,11 +4,18 @@
namespace Maui.Controls.Sample.Issues
{
- [XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 18740, "Virtual keyboard appears with focus on Entry", PlatformAffected.Android)]
+ public class Issue18740NavPage : NavigationPage
+ {
+ public Issue18740NavPage() : base(new Issue18740())
+ {
+
+ }
+ }
+
public partial class Issue18740 : ContentPage
{
- public Issue18740()
+ public Issue18740() : base()
{
InitializeComponent();
}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue19786.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue19786.xaml
new file mode 100644
index 000000000000..195ed2f36e8d
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue19786.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue19786.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue19786.xaml.cs
new file mode 100644
index 000000000000..a6afed72ebc3
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue19786.xaml.cs
@@ -0,0 +1,61 @@
+using System.Collections.ObjectModel;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.Github, 19786, "[Android] Crash removing item from CarouselView", PlatformAffected.All)]
+ public partial class Issue19786 : ContentPage
+ {
+ public Command AddItemCommand { get; set; }
+ public Command RemoveItemCommand { get; set; }
+ public Command GoToNextItemCommand { get; set; }
+
+ private int _position;
+ public int Position
+ {
+ get => _position;
+ set
+ {
+ _position = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private ObservableCollection _items = new();
+ public ObservableCollection Items
+ {
+ get => _items;
+ set
+ {
+ _items = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public Issue19786()
+ {
+ InitializeComponent();
+
+ AddItemCommand = new Command(() =>
+ {
+ Items.Add(Items.Count.ToString());
+ });
+
+ RemoveItemCommand = new Command(() =>
+ {
+ if (Items.Count > 0)
+ Items.RemoveAt(Items.Count - 1);
+ });
+
+ GoToNextItemCommand = new Command(() =>
+ {
+ if (Position < Items.Count - 1)
+ Position++;
+ });
+
+ BindingContext = this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20199.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20199.xaml
new file mode 100644
index 000000000000..aac6bcca0c2a
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20199.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20199.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20199.xaml.cs
new file mode 100644
index 000000000000..260cd69e0ec6
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue20199.xaml.cs
@@ -0,0 +1,29 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.Github, 20199, "[iOS] Page titles do not appear until navigating when pushing a modal page at startup", PlatformAffected.iOS)]
+ public partial class Issue20199 : Shell
+ {
+ public Issue20199()
+ {
+ InitializeComponent();
+ }
+
+ protected override async void OnAppearing()
+ {
+ base.OnAppearing();
+
+ var closeModalPageButton = new Button() { Text = "Hide", AutomationId = "button" };
+ closeModalPageButton.Clicked += (s, e) => Navigation.PopAsync();
+
+ var modalPage = new ContentPage() { Content = closeModalPageButton };
+
+ await Navigation.PushModalAsync(modalPage);
+ }
+ }
+
+ public class Issue20199Page : ContentPage { }
+}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630.xaml
new file mode 100644
index 000000000000..72e84609c70d
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630.xaml.cs
new file mode 100644
index 000000000000..b27ef44a8bff
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630.xaml.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues;
+
+[XamlCompilation(XamlCompilationOptions.Compile)]
+[Issue(IssueTracker.Github, 21630, "Entries in NavBar don't trigger keyboard scroll", PlatformAffected.iOS)]
+public partial class Issue21630 : ContentPage
+{
+ Page _page;
+ List _modalStack;
+
+ public Issue21630()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private void OnLoaded(object sender, EventArgs e)
+ {
+ _page = Application.Current.MainPage;
+ _modalStack = Navigation.ModalStack.ToList();
+ }
+
+ void SwapMainPageNav (object sender, EventArgs e)
+ {
+ Application.Current.MainPage = new NavigationPage(new Issue21630_navPage(_page, _modalStack));
+ }
+
+ void SwapMainPageShell (object sender, EventArgs e)
+ {
+ Application.Current.MainPage = new Issue21630_shellPage(_page, _modalStack);
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_navPage.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_navPage.xaml
new file mode 100644
index 000000000000..d43d940470d4
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_navPage.xaml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+ asdf
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_navPage.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_navPage.xaml.cs
new file mode 100644
index 000000000000..5b6302e7824f
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_navPage.xaml.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+
+namespace Maui.Controls.Sample.Issues;
+
+public partial class Issue21630_navPage : ContentPage
+{
+ Page _page;
+ List _modalStack;
+
+ public Issue21630_navPage()
+ {
+ InitializeComponent();
+ var bc = (ValueTuple>)Shell.Current.BindingContext;
+ _page = bc.Item1;
+ _modalStack = bc.Item2;
+ }
+
+ public Issue21630_navPage(Page page, List modalStack)
+ {
+ InitializeComponent();
+ _page = page;
+ _modalStack = modalStack;
+ }
+
+ void FocusNavBarEntryNav (object sender, EventArgs e)
+ {
+ NavBarEntryNav.Focus();
+ }
+
+ void FocusNavBarEntryShell (object sender, EventArgs e)
+ {
+ NavBarEntryShell.Focus();
+ }
+
+ async void RestoreMainPage (object sender, EventArgs e)
+ {
+ Application.Current.MainPage = _page;
+ await Task.Yield();
+
+ foreach(var page in _modalStack)
+ {
+ await _page.Navigation.PushModalAsync(page);
+ }
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_shellPage.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_shellPage.xaml
new file mode 100644
index 000000000000..24b88105c362
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_shellPage.xaml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_shellPage.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_shellPage.xaml.cs
new file mode 100644
index 000000000000..6fc33ae43263
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21630_shellPage.xaml.cs
@@ -0,0 +1,14 @@
+using Microsoft.Maui.Controls;
+using System.Collections.Generic;
+
+namespace Maui.Controls.Sample.Issues
+{
+ public partial class Issue21630_shellPage : Shell
+ {
+ public Issue21630_shellPage(Page page, List modalStack)
+ {
+ InitializeComponent();
+ BindingContext = (page, modalStack);
+ }
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml
new file mode 100644
index 000000000000..c35fc16d51e3
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml.cs
new file mode 100644
index 000000000000..b371cb4437ba
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21631.xaml.cs
@@ -0,0 +1,15 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.Github, 21631, "Injecting base tag in Webview2 works", PlatformAffected.UWP)]
+ public partial class Issue21631 : ContentPage
+ {
+ public Issue21631()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21726.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21726.xaml
new file mode 100644
index 000000000000..9fe10a491ca7
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21726.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21726.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21726.xaml.cs
new file mode 100644
index 000000000000..e58a84fd5f1c
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21726.xaml.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Linq;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+using System.Threading.Tasks;
+
+namespace Maui.Controls.Sample.Issues;
+
+[XamlCompilation(XamlCompilationOptions.Compile)]
+[Issue(IssueTracker.Github, 21726, "Modal with a bottom sheet should not crash iOS Keyboard Scroll", PlatformAffected.iOS)]
+public partial class Issue21726 : ContentPage
+{
+ public Issue21726()
+ {
+ InitializeComponent();
+ }
+
+ void AddVC (object sender, EventArgs e)
+ {
+#if IOS
+ var window = UIKit.UIApplication.SharedApplication
+ .ConnectedScenes
+ .OfType()
+ .SelectMany(s => s.Windows)
+ .FirstOrDefault(w => w.IsKeyWindow);
+
+ var rootVC = window?.RootViewController;
+ while (rootVC?.PresentedViewController != null)
+ {
+ rootVC = rootVC.PresentedViewController;
+ }
+
+ if (rootVC is not null) {
+ var testVC = new TestViewController();
+
+ var testNC = new UIKit.UINavigationController(testVC)
+ {
+ ModalPresentationStyle = UIKit.UIModalPresentationStyle.FullScreen
+ };
+
+ rootVC.PresentViewController(testNC, true, null);
+ }
+#endif
+ }
+
+#if IOS
+ public class TestViewController: UIKit.UIViewController {
+
+ UIKit.UITextField TextField1;
+ UIKit.UIButton Button1;
+
+ public override void ViewDidLoad() {
+ base.ViewDidLoad();
+
+ View.BackgroundColor = UIKit.UIColor.White;
+
+ TextField1 = new UIKit.UITextField(new CoreGraphics.CGRect(20, 120, 200, 20))
+ {
+ Placeholder = "TextField1",
+ BorderStyle = UIKit.UITextBorderStyle.RoundedRect,
+ AccessibilityIdentifier = "TextField1"
+ };
+
+ Button1 = new UIKit.UIButton(new CoreGraphics.CGRect(20, 320, 200, 40));
+ Button1.SetTitle("Dismiss", UIKit.UIControlState.Normal);
+ Button1.BackgroundColor = UIKit.UIColor.SystemBlue;
+ Button1.AccessibilityIdentifier = "Button1";
+ Button1.TouchUpInside += (sender, e) => {
+ DismissViewController(true, null);
+ };
+
+ View.AddSubview(TextField1);
+ View.AddSubview(Button1);
+ }
+ }
+#endif
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846.xaml
new file mode 100644
index 000000000000..97b83ee4f6fa
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846.xaml.cs
new file mode 100644
index 000000000000..7e3cc3e505d7
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846.xaml.cs
@@ -0,0 +1,22 @@
+using System;
+using Microsoft.Maui;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.Github, 21846, "Fix crash closing Popup with WebView", PlatformAffected.iOS)]
+ public partial class Issue21846 : ContentPage
+ {
+ public Issue21846()
+ {
+ InitializeComponent();
+ }
+
+ async void OnButtonClicked(object sender, System.EventArgs e)
+ {
+ await Navigation.PushModalAsync(new Issue21846Modal());
+ }
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846Modal.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846Modal.xaml
new file mode 100644
index 000000000000..7c6a0d4993bb
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846Modal.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846Modal.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846Modal.xaml.cs
new file mode 100644
index 000000000000..617d30c9037a
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21846Modal.xaml.cs
@@ -0,0 +1,34 @@
+using System;
+using Microsoft.Maui;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class Issue21846Modal : ContentPage
+ {
+ public Issue21846Modal()
+ {
+ InitializeComponent();
+
+ BindingContext = this;
+ }
+
+ async void OnButtonClicked(object sender, System.EventArgs e)
+ {
+ if (TestWebView is IElement visualElement)
+ {
+ if (visualElement.Handler != null)
+ {
+ if (visualElement.Handler is IDisposable disposableHandler)
+ disposableHandler.Dispose();
+
+ visualElement.Handler.DisconnectHandler();
+ }
+ }
+
+ await Navigation.PopModalAsync();
+ }
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue5555.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue5555.cs
index 113238799009..dc1556f3df98 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue5555.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue5555.cs
@@ -6,58 +6,64 @@
namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.None, 5555, "Memory leak when SwitchCell or EntryCell", PlatformAffected.iOS)]
- public class Issue5555 : TestContentPage
+ public class Issue5555 : NavigationPage
{
- public static Label DestructorCount = new Label() { Text = "0" };
- protected override void Init()
+ public Issue5555() : base(new TestPage())
{
- var instructions = new Label
- {
- FontSize = 16,
- Text = "Click 'Push page' twice"
- };
+ }
- var result = new Label
+ public class TestPage : TestContentPage
+ {
+ public static Label DestructorCount = new Label() { Text = "0" };
+ protected override void Init()
{
- Text = "Success",
- AutomationId = "SuccessLabel",
- IsVisible = false
- };
+ var instructions = new Label
+ {
+ FontSize = 16,
+ Text = "Click 'Push page' twice"
+ };
- var list = new List();
+ var result = new Label
+ {
+ Text = "Success",
+ AutomationId = "SuccessLabel",
+ IsVisible = false
+ };
- var checkButton = new Button
- {
- Text = "Check Result",
- AutomationId = "CheckResult",
- IsVisible = false,
- Command = new Command(async () =>
+ var list = new List();
+
+ var checkButton = new Button
{
- if (list.Count < 2)
+ Text = "Check Result",
+ AutomationId = "CheckResult",
+ IsVisible = false,
+ Command = new Command(async () =>
{
- instructions.Text = "Click 'Push page' again";
- return;
- }
+ if (list.Count < 2)
+ {
+ instructions.Text = "Click 'Push page' again";
+ return;
+ }
- try
- {
- await GarbageCollectionHelper.WaitForGC(2500, list.ToArray());
- result.Text = "Success";
- result.IsVisible = true;
- instructions.Text = "";
- }
- catch (Exception)
- {
- instructions.Text = "Failed";
- result.IsVisible = false;
- return;
- }
- })
- };
+ try
+ {
+ await GarbageCollectionHelper.WaitForGC(2500, list.ToArray());
+ result.Text = "Success";
+ result.IsVisible = true;
+ instructions.Text = "";
+ }
+ catch (Exception)
+ {
+ instructions.Text = "Failed";
+ result.IsVisible = false;
+ return;
+ }
+ })
+ };
- Content = new StackLayout
- {
- Children = {
+ Content = new StackLayout
+ {
+ Children = {
DestructorCount,
instructions,
result,
@@ -88,16 +94,16 @@ protected override void Init()
},
checkButton
}
- };
- }
+ };
+ }
- class LeakPage : ContentPage
- {
- public LeakPage()
+ class LeakPage : ContentPage
{
- Content = new StackLayout
+ public LeakPage()
{
- Children = {
+ Content = new StackLayout
+ {
+ Children = {
new Entry { Text = "LeakPage" },
new TableView
{
@@ -111,12 +117,13 @@ public LeakPage()
}
}
}
- };
- }
+ };
+ }
- ~LeakPage()
- {
- System.Diagnostics.Debug.WriteLine("LeakPage Finalized");
+ ~LeakPage()
+ {
+ System.Diagnostics.Debug.WriteLine("LeakPage Finalized");
+ }
}
}
}
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16094.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16094.xaml
index 36d9a63a3a40..eb716a7225f0 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16094.xaml
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16094.xaml
@@ -2,7 +2,7 @@
-
+
+
-
-
-
+
+
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16321.xaml.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16321.xaml.cs
index dca39801fe85..ea75962617fe 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16321.xaml.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issues16321.xaml.cs
@@ -14,9 +14,17 @@ namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 16321, "Alerts Open on top of current presented view", PlatformAffected.All)]
+ public class Issue16321NavPage : NavigationPage
+ {
+ public Issue16321NavPage() : base(new Issue16321())
+ {
+
+ }
+ }
+
public partial class Issue16321 : ContentPage
{
- public Issue16321()
+ public Issue16321() : base()
{
InitializeComponent();
}
@@ -67,10 +75,10 @@ async void OpenAlertWithNewUIWindow(System.Object sender, System.EventArgs e)
this.RemoveLogicalChild(page);
}
#else
- async void OpenAlertWithNewUIWindow(System.Object sender, System.EventArgs e)
- {
- await this.DisplayAlert("hello", "message", "Cancel");
- }
+ async void OpenAlertWithNewUIWindow(System.Object sender, System.EventArgs e)
+ {
+ await this.DisplayAlert("hello", "message", "Cancel");
+ }
#endif
}
-}
+}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/ScrollViewIsEnabled.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/ScrollViewIsEnabled.cs
index 600481cb1131..4256187fd02c 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/ScrollViewIsEnabled.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/ScrollViewIsEnabled.cs
@@ -8,80 +8,87 @@ namespace Maui.Controls.Sample.Issues
// ScrollViewInitiallyNotEnabled (src\Compatibility\ControlGallery\src\Issues.Shared\ScrollViewIsEnabled.cs)
// ScrollViewInitiallyNotEnabledThenEnabled (src\Compatibility\ControlGallery\src\Issues.Shared\ScrollViewIsEnabled.cs)
[Issue(IssueTracker.None, 0112358, "ScrollView set to disabled will still allow scrolling", PlatformAffected.All)]
- public class ScrollViewIsEnabled : ContentPage
+ public class ScrollViewIsEnabled : NavigationPage
{
- const string InitiallyEnabled = "Initially Enabled";
- const string InitiallyNotEnabled = "Initially Not Enabled";
- const string ToggleButton = "ToggleButton";
- const string ScrollView = "TheScrollView";
- const string FirstItem = "FirstItem";
- const string Success = "Success";
-
- public ScrollViewIsEnabled()
+ public ScrollViewIsEnabled() : base(new TestPage())
{
- var initiallyEnabled = new Button { AutomationId = "InitiallyEnabled", Text = InitiallyEnabled };
- initiallyEnabled.Clicked += (sender, args) => { Navigation.PushAsync(ScrollViewTestPage(true)); };
+ }
- var initiallyNotEnabled = new Button { AutomationId= "InitiallyNotEnabled", Text = InitiallyNotEnabled };
- initiallyNotEnabled.Clicked += (sender, args) => { Navigation.PushAsync(ScrollViewTestPage(false)); };
+ public class TestPage : ContentPage
+ {
+ const string InitiallyEnabled = "Initially Enabled";
+ const string InitiallyNotEnabled = "Initially Not Enabled";
+ const string ToggleButton = "ToggleButton";
+ const string ScrollView = "TheScrollView";
+ const string FirstItem = "FirstItem";
+ const string Success = "Success";
+
+ public TestPage()
+ {
+ var initiallyEnabled = new Button { AutomationId = "InitiallyEnabled", Text = InitiallyEnabled };
+ initiallyEnabled.Clicked += (sender, args) => { Navigation.PushAsync(ScrollViewTestPage(true)); };
- var layout = new StackLayout { Children = { initiallyNotEnabled, initiallyEnabled } };
+ var initiallyNotEnabled = new Button { AutomationId = "InitiallyNotEnabled", Text = InitiallyNotEnabled };
+ initiallyNotEnabled.Clicked += (sender, args) => { Navigation.PushAsync(ScrollViewTestPage(false)); };
- Content = layout;
- }
+ var layout = new StackLayout { Children = { initiallyNotEnabled, initiallyEnabled } };
- static ContentPage ScrollViewTestPage(bool initiallyEnabled)
- {
- var scrollViewContents = new StackLayout();
- scrollViewContents.Children.Add(new Label { AutomationId = FirstItem, Text = FirstItem });
+ Content = layout;
+ }
- for (int n = 0; n < 100; n++)
+ static ContentPage ScrollViewTestPage(bool initiallyEnabled)
{
- scrollViewContents.Children.Add(new Label { AutomationId = $"Item{n + 1}", Text = $"Item{n + 1}" });
- }
+ var scrollViewContents = new StackLayout();
+ scrollViewContents.Children.Add(new Label { AutomationId = FirstItem, Text = FirstItem });
- var sv = new ScrollView { Content = scrollViewContents, IsEnabled = initiallyEnabled, AutomationId = ScrollView };
- var layout = new Grid { Margin = new Thickness(5, 40, 5, 0) };
+ for (int n = 0; n < 100; n++)
+ {
+ scrollViewContents.Children.Add(new Label { AutomationId = $"Item{n + 1}", Text = $"Item{n + 1}" });
+ }
- layout.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
- layout.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
- layout.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
- layout.RowDefinitions.Add(new RowDefinition());
+ var sv = new ScrollView { Content = scrollViewContents, IsEnabled = initiallyEnabled, AutomationId = ScrollView };
+ var layout = new Grid { Margin = new Thickness(5, 40, 5, 0) };
- var toggleButton = new Button { Text = $"Toggle IsEnabled (currently {sv.IsEnabled})", AutomationId = ToggleButton };
+ layout.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
+ layout.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
+ layout.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
+ layout.RowDefinitions.Add(new RowDefinition());
- toggleButton.Clicked += (sender, args) =>
- {
- sv.IsEnabled = !sv.IsEnabled;
- toggleButton.Text = $"Toggle IsEnabled (currently {sv.IsEnabled})";
- };
+ var toggleButton = new Button { Text = $"Toggle IsEnabled (currently {sv.IsEnabled})", AutomationId = ToggleButton };
- var instructions = new Label
- {
- Text = @"Attempt to scroll the ScrollView below.
+ toggleButton.Clicked += (sender, args) =>
+ {
+ sv.IsEnabled = !sv.IsEnabled;
+ toggleButton.Text = $"Toggle IsEnabled (currently {sv.IsEnabled})";
+ };
+
+ var instructions = new Label
+ {
+ Text = @"Attempt to scroll the ScrollView below.
If 'IsEnabled' is false and the ScrollView scrolls, this test has failed.
If 'IsEnabled' is true and the ScrollView does not scroll, this test has failed.
Use the toggle button to check both values of 'IsEnabled'."
- };
+ };
- var success = new Label
- {
- AutomationId = Success
- };
+ var success = new Label
+ {
+ AutomationId = Success
+ };
- layout.Children.Add(instructions);
- layout.Children.Add(toggleButton);
- layout.Children.Add(success);
- layout.Children.Add(sv);
+ layout.Children.Add(instructions);
+ layout.Children.Add(toggleButton);
+ layout.Children.Add(success);
+ layout.Children.Add(sv);
- Grid.SetRow(instructions, 0);
- Grid.SetRow(toggleButton, 1);
- Grid.SetRow(success, 2);
- Grid.SetRow(sv, 3);
+ Grid.SetRow(instructions, 0);
+ Grid.SetRow(toggleButton, 1);
+ Grid.SetRow(success, 2);
+ Grid.SetRow(sv, 3);
- sv.Scrolled += (sender, args) => success.Text = Success;
+ sv.Scrolled += (sender, args) => success.Text = Success;
- return new ContentPage { Content = layout };
+ return new ContentPage { Content = layout };
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/XFIssue2681.cs b/src/Controls/samples/Controls.Sample.UITests/Issues/XFIssue2681.cs
index f0976d6d2e37..db781eec1417 100644
--- a/src/Controls/samples/Controls.Sample.UITests/Issues/XFIssue2681.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/Issues/XFIssue2681.cs
@@ -9,54 +9,61 @@ namespace Maui.Controls.Sample.Issues;
[Issue(IssueTracker.None, 2681, "[UWP] Label inside Listview gets stuck inside infinite loop",
PlatformAffected.UWP)]
-public class XFIssue2681 : TestContentPage
+
+public class XFIssue2681 : NavigationPage
{
- const string NavigateToPage = "Click Me.";
- protected override void Init()
+ public XFIssue2681() : base(new TestPage())
{
- Content = new Button()
- {
- Text = NavigateToPage,
- AutomationId = "ClickMe",
- Command = new Command(async () => await this.Navigation.PushAsync(new FreezeMe()))
- };
}
- public partial class FreezeMe : ContentPage
+ public class TestPage : TestContentPage
{
- public List Items { get; set; }
-
- public FreezeMe()
+ const string NavigateToPage = "Click Me.";
+ protected override void Init()
{
- this.BindingContext = this;
- var lv = new ListView()
+ Content = new Button()
{
- Margin = new Thickness(20, 5, 5, 5)
+ Text = NavigateToPage,
+ AutomationId = "ClickMe",
+ Command = new Command(async () => await this.Navigation.PushAsync(new FreezeMe()))
};
+ }
+
+ public partial class FreezeMe : ContentPage
+ {
+ public List Items { get; set; }
- lv.ItemTemplate = new DataTemplate(() =>
+ public FreezeMe()
{
- var label = new Label() { Text = "sassifrass" };
- label.SetBinding(Label.TextProperty, ".");
- label.SetBinding(Label.AutomationIdProperty, ".");
- return new ViewCell() { View = label };
- });
+ this.BindingContext = this;
+ var lv = new ListView()
+ {
+ Margin = new Thickness(20, 5, 5, 5)
+ };
- lv.SetBinding(ListView.ItemsSourceProperty, "Items");
+ lv.ItemTemplate = new DataTemplate(() =>
+ {
+ var label = new Label() { Text = "sassifrass" };
+ label.SetBinding(Label.TextProperty, ".");
+ label.SetBinding(Label.AutomationIdProperty, ".");
+ return new ViewCell() { View = label };
+ });
- this.Content = new ScrollView()
- {
- Content = new StackLayout()
+ lv.SetBinding(ListView.ItemsSourceProperty, "Items");
+
+ this.Content = new ScrollView()
{
- Children =
+ Content = new StackLayout()
+ {
+ Children =
{
new Label(){ Text = "If page is not frozen this test has passed" },
new StackLayout()
{
Orientation = StackOrientation.Horizontal,
- Children =
+ Children =
{
- lv
+ lv
}
},
new Button(){
@@ -68,14 +75,15 @@ public FreezeMe()
})
}
}
- }
- };
+ }
+ };
- this.Appearing += (s, e) =>
- {
- this.Items = new List { 1, 2, 3 };
- this.OnPropertyChanged("Items");
- };
+ this.Appearing += (s, e) =>
+ {
+ this.Items = new List { 1, 2, 3 };
+ this.OnPropertyChanged("Items");
+ };
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.UITests/TestCases.cs b/src/Controls/samples/Controls.Sample.UITests/TestCases.cs
index a070a2c41d22..30e43adfeb0d 100644
--- a/src/Controls/samples/Controls.Sample.UITests/TestCases.cs
+++ b/src/Controls/samples/Controls.Sample.UITests/TestCases.cs
@@ -39,7 +39,6 @@ Action ActivatePageAndNavigate(IssueAttribute issueAttribute, Type type)
return async () =>
{
var page = ActivatePage(type);
- TrackOnInsights(page);
await Navigation.PushAsync(page);
};
}
@@ -49,38 +48,16 @@ Action ActivatePageAndNavigate(IssueAttribute issueAttribute, Type type)
return async () =>
{
var page = ActivatePage(type);
- TrackOnInsights(page);
await Navigation.PushModalAsync(page);
};
}
- if (issueAttribute.NavigationBehavior == NavigationBehavior.Default)
- {
- return async () =>
- {
- var page = ActivatePage(type);
- TrackOnInsights(page);
- if (page is ContentPage /*|| page is CarouselPage*/)
- {
- await Navigation.PushAsync(page);
- }
- else if (page is Shell)
- {
- Application.Current.MainPage = page;
- }
- else
- {
- await Navigation.PushModalAsync(page);
- }
- };
- }
-
- if (issueAttribute.NavigationBehavior == NavigationBehavior.SetApplicationRoot)
+ if (issueAttribute.NavigationBehavior == NavigationBehavior.SetApplicationRoot ||
+ issueAttribute.NavigationBehavior == NavigationBehavior.Default)
{
return () =>
{
var page = ActivatePage(type);
- TrackOnInsights(page);
Application.Current.MainPage = page;
};
}
@@ -88,11 +65,6 @@ Action ActivatePageAndNavigate(IssueAttribute issueAttribute, Type type)
return navigationAction;
}
- static void TrackOnInsights(Page page)
- {
-
- }
-
Page ActivatePage(Type type)
{
var page = Activator.CreateInstance(type) as Page;
@@ -182,7 +154,6 @@ public TestCaseScreen()
}).ToList();
VerifyNoDuplicates();
-
FilterIssues();
}
@@ -212,6 +183,8 @@ public void FilterTracker(IssueTracker tracker)
public bool TryToNavigateTo(string name)
{
var issue = _issues.SingleOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
+ issue = _issues.SingleOrDefault(x => string.Equals(x.Description, name, StringComparison.OrdinalIgnoreCase));
+
if (issue == null)
return false;
@@ -219,12 +192,11 @@ public bool TryToNavigateTo(string name)
return true;
}
- public async void FilterIssues(string filter = null)
+ public void FilterIssues(string filter = null)
{
filter = filter?.Trim();
_filter = filter;
- await Task.Delay(10);
if (_filter != filter)
{
return;
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml
index ae76e6e58ba3..0d7e2af9c3b8 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml
@@ -1,14 +1,14 @@
-
+
-
-
-
+ Title="Hide Home Indicator">
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml.cs
index e8714bcf249f..12a0730475d8 100644
--- a/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/PlatformSpecifics/iOS/iOSHideHomeIndicatorPage.xaml.cs
@@ -12,9 +12,255 @@ public iOSHideHomeIndicatorPage()
InitializeComponent();
}
- void OnButtonClicked(object sender, EventArgs e)
+ void NavigationPage_Clicked(object sender, EventArgs e)
{
- On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden());
+ Navigation.PushAsync(new iOSHideHomeIndicatorNavigationPageDemo());
+ }
+
+ void TabbedPage_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new iOSHideHomeIndicatorPageDemo());
+ }
+
+ void FlyoutPage_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new iOSHideHomeIndicatorFlyoutPageDemo());
+ }
+
+ void Shell_Clicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new iOSHideHomeIndicatorShellDemo());
+ }
+ }
+
+ public class iOSHideHomeIndicatorNavigationPageDemo : ContentPage
+ {
+ public iOSHideHomeIndicatorNavigationPageDemo()
+ {
+ Content = new StackLayout()
+ {
+ Margin = new Microsoft.Maui.Thickness(20),
+ Children = {
+ new Button()
+ {
+ Text = "Toggle Home Indicator",
+ Command = new Command(() => On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()))
+ },
+ new Button()
+ {
+ Text = "Navigate back",
+ Command = new Command(() => Navigation.PopAsync())
+ }
+ }
+ };
+ }
+ }
+
+ public class iOSHideHomeIndicatorFlyoutPageDemo : Microsoft.Maui.Controls.FlyoutPage
+ {
+ public iOSHideHomeIndicatorFlyoutPageDemo()
+ {
+ Flyout = new ContentPage()
+ {
+ Title = "Detail",
+ Content = new StackLayout()
+ {
+ Children = {
+ new Button()
+ {
+ Text = "Open page 1",
+ Command = new Command(() => Detail = OpenPage("Page 1"))
+ },
+ new Button()
+ {
+ Text = "Open page 2",
+ Command = new Command(() => Detail = OpenPage("Page 2"))
+ }
+ }
+ }
+ };
+
+ Detail = OpenPage("Page 1");
+ }
+
+ public ContentPage OpenPage(string title)
+ {
+ return new ContentPage()
+ {
+ Title = title,
+ Content = new StackLayout()
+ {
+ Margin = new Microsoft.Maui.Thickness(20),
+ Children = {
+ new Button
+ {
+ Text = "Toggle Home Indicator",
+ Command = new Command(() => On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()))
+ },
+ new Button()
+ {
+ Text = "Click to open the flyout",
+ Command = new Command(() => IsPresented = true)
+ },
+ new Button
+ {
+ VerticalOptions = LayoutOptions.End,
+ Text = "Back",
+ Command = new Command(() => Navigation.PopAsync())
+ }
+ }
+ }
+ };
+ }
+ }
+
+ public class iOSHideHomeIndicatorPageDemo : Microsoft.Maui.Controls.TabbedPage
+ {
+ public iOSHideHomeIndicatorPageDemo()
+ {
+
+ Children.Add
+ (
+ new ContentPage()
+ {
+ Title = "Tab 1",
+ Content = new StackLayout()
+ {
+ Margin = new Microsoft.Maui.Thickness(20),
+ Children =
+ {
+ new Button()
+ {
+ Text = "Toggle Home Indicator",
+ Command = new Command(() => On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()))
+ },
+ new Button()
+ {
+ Text = "Navigate back",
+ Command = new Command(() => Navigation.PopAsync())
+ }
+ }
+ }
+ }
+ );
+
+ Children.Add
+ (
+ new ContentPage()
+ {
+ Title = "Tab 2",
+ Content = new StackLayout()
+ {
+ Margin = new Microsoft.Maui.Thickness(20),
+ Children =
+ {
+ new Button()
+ {
+ Text = "Toggle Home Indicator",
+ Command = new Command(() => On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()))
+ },
+ new Button()
+ {
+ Text = "Navigate back",
+ Command = new Command(() => Navigation.PopAsync())
+ }
+ }
+ }
+ }
+ );
+
+ }
+ }
+
+ public class iOSHideHomeIndicatorShellDemo : Shell
+ {
+ public iOSHideHomeIndicatorShellDemo()
+ {
+ TabBar tabBar = new TabBar();
+
+ var toggleHomeIndicatorButton1 = new Button()
+ {
+ Text = "Toggle Home Indicator",
+ Command = new Command(() => On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()))
+ };
+
+ var contentPage1 = new ContentPage
+ {
+ Title = "Shell Tab 1",
+ Content = new StackLayout()
+ {
+ Margin = new Microsoft.Maui.Thickness(20),
+ Children =
+ {
+ toggleHomeIndicatorButton1,
+ new Button()
+ {
+ Text = "Navigate back",
+ Command = new Command(() => Navigation.PopAsync())
+ }
+ }
+ }
+ };
+
+ toggleHomeIndicatorButton1.Command = new Command(() => contentPage1.On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()));
+
+ tabBar.Items.Add
+ (
+ new Tab
+ {
+ Title = "Shell Tab 1",
+ Items = {
+ new ShellContent
+ {
+ Content = contentPage1,
+ Title = "Shell Tab 1",
+ }
+ }
+ }
+ );
+
+ var toggleHomeIndicatorButton2 = new Button()
+ {
+ Text = "Toggle Home Indicator",
+ Command = new Command(() => On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()))
+ };
+
+ var contentPage2 = new ContentPage
+ {
+ Title = "Shell Tab 2",
+ Content = new StackLayout()
+ {
+ Margin = new Microsoft.Maui.Thickness(20),
+ Children =
+ {
+ toggleHomeIndicatorButton2,
+ new Button()
+ {
+ Text = "Navigate back",
+ Command = new Command(() => Navigation.PopAsync())
+ }
+ }
+ }
+ };
+
+ toggleHomeIndicatorButton2.Command = new Command(() => contentPage2.On().SetPrefersHomeIndicatorAutoHidden(!On().PrefersHomeIndicatorAutoHidden()));
+
+ tabBar.Items.Add
+ (
+ new Tab
+ {
+ Title = "Shell Tab 2",
+ Items = {
+ new ShellContent
+ {
+ Content = contentPage2,
+ Title = "Shell Tab 2",
+ }
+ }
+ }
+ );
+
+ Items.Add(tabBar);
}
}
}
diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
index 093a05b4c98c..15f021d26bda 100644
--- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
+++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
@@ -382,14 +382,26 @@ static IEnumerable CompileBindingPath(ElementNode node, ILContext c
INode dataTypeNode = null;
IElementNode n = node;
+
+ // Special handling for BindingContext={Binding ...}
+ // The order of checks is:
+ // - x:DataType on the binding itself
+ // - SKIP looking for x:DataType on the parent
+ // - continue looking for x:DataType on the parent's parent...
+ IElementNode skipNode = null;
+ if (IsBindingContextBinding(node))
+ {
+ skipNode = GetParent(node);
+ }
+
while (n != null)
{
- if (n.Properties.TryGetValue(XmlName.xDataType, out dataTypeNode))
+ if (n != skipNode && n.Properties.TryGetValue(XmlName.xDataType, out dataTypeNode))
+ {
break;
- if (n.Parent is ListNode listNode)
- n = listNode.Parent as IElementNode;
- else
- n = n.Parent as IElementNode;
+ }
+
+ n = GetParent(n);
}
if (dataTypeNode is null)
@@ -475,6 +487,25 @@ static IEnumerable CompileBindingPath(ElementNode node, ILContext c
yield return Create(Ldnull);
yield return Create(Newobj, module.ImportReference(ctorinforef));
yield return Create(Callvirt, module.ImportPropertySetterReference(context.Cache, bindingExtensionType, propertyName: "TypedBinding"));
+
+ static IElementNode GetParent(IElementNode node)
+ {
+ return node switch
+ {
+ { Parent: ListNode { Parent: IElementNode parentNode } } => parentNode,
+ { Parent: IElementNode parentNode } => parentNode,
+ _ => null,
+ };
+ }
+
+ static bool IsBindingContextBinding(ElementNode node)
+ {
+ // looking for BindingContext="{Binding ...}"
+ return GetParent(node) is IElementNode parentNode
+ && ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out var propertyName)
+ && propertyName.NamespaceURI == ""
+ && propertyName.LocalName == nameof(BindableObject.BindingContext);
+ }
}
static IList<(PropertyDefinition property, TypeReference propDeclTypeRef, string indexArg)> ParsePath(ILContext context, string path, TypeReference tSourceRef, IXmlLineInfo lineInfo, ModuleDefinition module)
diff --git a/src/Controls/src/Build.Tasks/XamlCTask.cs b/src/Controls/src/Build.Tasks/XamlCTask.cs
index d2001ebd7306..4b975c1ff012 100644
--- a/src/Controls/src/Build.Tasks/XamlCTask.cs
+++ b/src/Controls/src/Build.Tasks/XamlCTask.cs
@@ -364,8 +364,8 @@ bool TryCoreCompile(MethodDefinition initComp, ILRootNode rootnode, string xamlF
rootnode.Accept(new CreateObjectVisitor(visitorContext), null);
rootnode.Accept(new SetNamescopesAndRegisterNamesVisitor(visitorContext), null);
rootnode.Accept(new SetFieldVisitor(visitorContext), null);
- rootnode.Accept(new SetResourcesVisitor(visitorContext), null);
rootnode.Accept(new SimplifyTypeExtensionVisitor(), null);
+ rootnode.Accept(new SetResourcesVisitor(visitorContext), null);
rootnode.Accept(new SetPropertiesVisitor(visitorContext, true), null);
il.Emit(Ret);
diff --git a/src/Controls/src/Core/Application/Application.cs b/src/Controls/src/Core/Application/Application.cs
index b8a4e260dc89..404ec6f21398 100644
--- a/src/Controls/src/Core/Application/Application.cs
+++ b/src/Controls/src/Core/Application/Application.cs
@@ -108,8 +108,7 @@ public Page? MainPage
if (value is not null)
{
- OnParentResourcesChanged(this.GetMergedResources());
- OnParentResourcesChanged([new KeyValuePair(AppThemeBinding.AppThemeResource, _lastAppTheme)]);
+ value.OnResourcesChanged(this.GetMergedResources());
((IElementDefinition)this).AddResourcesChangedListener(value.OnParentResourcesChanged);
}
diff --git a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
index c35ce5927093..ee420a4eac33 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
@@ -198,6 +198,7 @@ bool shouldReceive(UIGestureRecognizer g, UITouch t)
UpdatePanGesture();
UpdateApplyShadow(((FlyoutPage)Element).OnThisPlatform().GetApplyShadow());
+ UpdatePageSpecifics();
}
public override void ViewWillTransitionToSize(CoreGraphics.CGSize toSize, IUIViewControllerTransitionCoordinator coordinator)
@@ -364,6 +365,9 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
UpdateBackground();
else if (e.PropertyName == PlatformConfiguration.iOSSpecific.FlyoutPage.ApplyShadowProperty.PropertyName)
UpdateApplyShadow(((FlyoutPage)Element).OnThisPlatform().GetApplyShadow());
+ else if (e.PropertyName == PlatformConfiguration.iOSSpecific.Page.PrefersHomeIndicatorAutoHiddenProperty.PropertyName ||
+ e.PropertyName == PlatformConfiguration.iOSSpecific.Page.PrefersStatusBarHiddenProperty.PropertyName)
+ UpdatePageSpecifics();
}
void LayoutChildren(bool animated)
@@ -551,6 +555,7 @@ void UpdateFlyoutPageContainers()
detailRenderer.ViewController.View.Superview.BackgroundColor = Microsoft.Maui.Graphics.Colors.Black.ToPlatform();
ToggleAccessibilityElementsHidden();
+ UpdatePageSpecifics();
}
void UpdateLeftBarButton()
@@ -573,6 +578,12 @@ void UpdateApplyShadow(bool value)
_applyShadow = value;
}
+ void UpdatePageSpecifics()
+ {
+ ChildViewControllerForHomeIndicatorAutoHidden.SetNeedsUpdateOfHomeIndicatorAutoHidden();
+ ChildViewControllerForStatusBarHidden().SetNeedsStatusBarAppearanceUpdate();
+ }
+
public override UIViewController ChildViewControllerForStatusBarHidden()
{
if (((FlyoutPage)Element).Detail?.Handler is IPlatformViewHandler nvh)
diff --git a/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs
index 42d7dcd3df74..697a41d62b45 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs
@@ -802,14 +802,16 @@ public UnevenListViewDataSource(ListViewDataSource source) : base(source)
nfloat GetEstimatedRowHeight(UITableView table)
{
- if (List.RowHeight != -1)
+ if (!_list.TryGetTarget(out var list))
+ return 0;
+ if (list.RowHeight != -1)
{
// Not even sure we need this case; A list with HasUnevenRows and a RowHeight doesn't make a ton of sense
// Anyway, no need for an estimate, because the heights we'll use are known
return 0;
}
- var templatedItems = TemplatedItemsView.TemplatedItems;
+ var templatedItems = ((ITemplatedItemsView)list).TemplatedItems;
if (templatedItems.Count == 0)
{
@@ -818,7 +820,7 @@ nfloat GetEstimatedRowHeight(UITableView table)
}
// We're going to base our estimate off of the first cell
- var isGroupingEnabled = List.IsGroupingEnabled;
+ var isGroupingEnabled = list.IsGroupingEnabled;
if (isGroupingEnabled)
templatedItems = templatedItems.GetGroup(0);
@@ -861,9 +863,11 @@ protected override void UpdateEstimatedRowHeight(UITableView tableView)
internal Cell GetPrototypicalCell(NSIndexPath indexPath)
{
- var itemTypeOrDataTemplate = default(object);
+ if (!_list.TryGetTarget(out var list))
+ return null;
- var cachingStrategy = List.CachingStrategy;
+ var itemTypeOrDataTemplate = default(object);
+ var cachingStrategy = list.CachingStrategy;
if (cachingStrategy == ListViewCachingStrategy.RecycleElement)
itemTypeOrDataTemplate = GetDataTemplateForPath(indexPath);
@@ -896,7 +900,7 @@ public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexP
var cell = GetPrototypicalCell(indexPath);
- if (List.RowHeight == -1 && cell.Height == -1 && cell is ViewCell)
+ if (_list.TryGetTarget(out var list) && list.RowHeight == -1 && cell.Height == -1 && cell is ViewCell)
return UITableView.AutomaticDimension;
var renderHeight = cell.RenderHeight;
@@ -969,10 +973,9 @@ internal class ListViewDataSource : UITableViewSource
static int s_dataTemplateIncrementer = 2; // lets start at not 0 because
readonly nfloat _defaultSectionHeight;
Dictionary _templateToId = new Dictionary();
- UITableView _uiTableView;
- FormsUITableViewController _uiTableViewController;
- protected ListView List;
- protected ITemplatedItemsView TemplatedItemsView => List;
+ readonly WeakReference _uiTableView;
+ readonly WeakReference _uiTableViewController;
+ protected readonly WeakReference _list;
bool _isDragging;
bool _setupSelection;
bool _selectionFromNative;
@@ -985,7 +988,7 @@ internal class ListViewDataSource : UITableViewSource
public ListViewDataSource(ListViewDataSource source)
{
_uiTableViewController = source._uiTableViewController;
- List = source.List;
+ _list = source._list;
_uiTableView = source._uiTableView;
_defaultSectionHeight = source._defaultSectionHeight;
_selectionFromNative = source._selectionFromNative;
@@ -995,11 +998,11 @@ public ListViewDataSource(ListViewDataSource source)
public ListViewDataSource(ListView list, FormsUITableViewController uiTableViewController)
{
- _uiTableViewController = uiTableViewController;
- _uiTableView = uiTableViewController.TableView;
+ _uiTableViewController = new(uiTableViewController);
+ _uiTableView = new(uiTableViewController.TableView);
_defaultSectionHeight = DefaultRowHeight;
- List = list;
- List.ItemSelected += OnItemSelected;
+ _list = new(list);
+ list.ItemSelected += OnItemSelected;
UpdateShortNameListener();
Counts = new Dictionary();
@@ -1022,7 +1025,10 @@ internal virtual void InvalidatingPrototypicalCellCache()
public override void DraggingEnded(UIScrollView scrollView, bool willDecelerate)
{
_isDragging = false;
- _uiTableViewController.UpdateShowHideRefresh(false);
+ if (_uiTableViewController.TryGetTarget(out var uiTableViewController))
+ {
+ uiTableViewController.UpdateShowHideRefresh(false);
+ }
}
public override void DraggingStarted(UIScrollView scrollView)
@@ -1050,7 +1056,9 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index
Performance.Start(out string reference);
- var cachingStrategy = List.CachingStrategy;
+ if (!_list.TryGetTarget(out var list))
+ return null;
+ var cachingStrategy = list.CachingStrategy;
if (cachingStrategy == ListViewCachingStrategy.RetainElement)
{
cell = GetCellForPath(indexPath);
@@ -1068,7 +1076,7 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index
}
else
{
- var templatedList = TemplatedItemsView.TemplatedItems.GetGroup(indexPath.Section);
+ var templatedList = list.TemplatedItems.GetGroup(indexPath.Section);
cell = (Cell)((INativeElementView)platformCell).Element;
cell.SendDisappearing();
@@ -1082,9 +1090,9 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index
SetupSelection(platformCell, tableView);
- if (List.IsSet(Specifics.SeparatorStyleProperty))
+ if (list.IsSet(Specifics.SeparatorStyleProperty))
{
- if (List.OnThisPlatform().GetSeparatorStyle() == SeparatorStyle.FullWidth)
+ if (list.OnThisPlatform().GetSeparatorStyle() == SeparatorStyle.FullWidth)
{
platformCell.SeparatorInset = UIEdgeInsets.Zero;
platformCell.LayoutMargins = UIEdgeInsets.Zero;
@@ -1100,9 +1108,11 @@ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath index
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
- if (List.IsGroupingEnabled)
+ if (!_list.TryGetTarget(out var list))
+ return 0;
+ if (list.IsGroupingEnabled)
{
- var cell = TemplatedItemsView.TemplatedItems[(int)section];
+ var cell = list.TemplatedItems[(int)section];
nfloat height = (float)cell.RenderHeight;
if (height == -1)
height = _defaultSectionHeight;
@@ -1115,10 +1125,12 @@ public override nfloat GetHeightForHeader(UITableView tableView, nint section)
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
- if (!List.IsGroupingEnabled)
+ if (!_list.TryGetTarget(out var list))
+ return null;
+ if (!list.IsGroupingEnabled)
return null;
- var cell = TemplatedItemsView.TemplatedItems[(int)section];
+ var cell = list.TemplatedItems[(int)section];
if (cell.HasContextActions)
throw new NotSupportedException("Header cells do not support context actions");
@@ -1138,7 +1150,7 @@ public override UIView GetViewForHeader(UITableView tableView, nint section)
public override void HeaderViewDisplayingEnded(UITableView tableView, UIView headerView, nint section)
{
- if (!List.IsGroupingEnabled)
+ if (!_list.TryGetTarget(out var list) || !list.IsGroupingEnabled)
return;
if (headerView is HeaderWrapperView wrapper)
@@ -1150,8 +1162,8 @@ public override void HeaderViewDisplayingEnded(UITableView tableView, UIView hea
public override nint NumberOfSections(UITableView tableView)
{
- if (List.IsGroupingEnabled)
- return TemplatedItemsView.TemplatedItems.Count;
+ if (_list.TryGetTarget(out var list) && list.IsGroupingEnabled)
+ return list.TemplatedItems.Count;
return 1;
}
@@ -1164,19 +1176,22 @@ public void OnItemSelected(object sender, SelectedItemChangedEventArgs eventArg)
return;
}
- if (List == null)
+ if (!_list.TryGetTarget(out var list))
return;
- var location = TemplatedItemsView.TemplatedItems.GetGroupAndIndexOfItem(eventArg.SelectedItem);
+ if (!_uiTableView.TryGetTarget(out var uiTableView))
+ return;
+
+ var location = list.TemplatedItems.GetGroupAndIndexOfItem(eventArg.SelectedItem);
if (location.Item1 == -1 || location.Item2 == -1)
{
- var selectedIndexPath = _uiTableView.IndexPathForSelectedRow;
+ var selectedIndexPath = uiTableView.IndexPathForSelectedRow;
var animate = true;
if (selectedIndexPath != null)
{
- var cell = _uiTableView.CellAt(selectedIndexPath) as ContextActionsCell;
+ var cell = uiTableView.CellAt(selectedIndexPath) as ContextActionsCell;
if (cell != null)
{
cell.PrepareForDeselect();
@@ -1186,11 +1201,11 @@ public void OnItemSelected(object sender, SelectedItemChangedEventArgs eventArg)
}
if (selectedIndexPath != null)
- _uiTableView.DeselectRow(selectedIndexPath, animate);
+ uiTableView.DeselectRow(selectedIndexPath, animate);
return;
}
- _uiTableView.SelectRow(NSIndexPath.FromRowSection(location.Item2, location.Item1), true, UITableViewScrollPosition.Middle);
+ uiTableView.SelectRow(NSIndexPath.FromRowSection(location.Item2, location.Item1), true, UITableViewScrollPosition.Middle);
}
public override void RowDeselected(UITableView tableView, NSIndexPath indexPath)
@@ -1208,20 +1223,22 @@ public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
if (cell == null)
return;
+ if (!_list.TryGetTarget(out var list))
+ return;
Cell formsCell = null;
- if ((List.CachingStrategy & ListViewCachingStrategy.RecycleElement) != 0)
+ if ((list.CachingStrategy & ListViewCachingStrategy.RecycleElement) != 0)
formsCell = (Cell)((INativeElementView)cell).Element;
SetCellBackgroundColor(cell, UIColor.Clear);
- if (List.SelectionMode == ListViewSelectionMode.None)
+ if (list.SelectionMode == ListViewSelectionMode.None)
tableView.DeselectRow(indexPath, false);
_selectionFromNative = true;
tableView.EndEditing(true);
- List.NotifyRowTapped(indexPath.Section, indexPath.Row, formsCell);
+ list.NotifyRowTapped(indexPath.Section, indexPath.Row, formsCell);
}
public override nint RowsInSection(UITableView tableview, nint section)
@@ -1240,8 +1257,11 @@ public override nint RowsInSection(UITableView tableview, nint section)
return countOverride;
}
- var templatedItems = TemplatedItemsView.TemplatedItems;
- if (List.IsGroupingEnabled)
+ if (!_list.TryGetTarget(out var list))
+ return 0;
+
+ var templatedItems = list.TemplatedItems;
+ if (list.IsGroupingEnabled)
{
var group = (IList)((IList)templatedItems)[(int)section];
return group.Count;
@@ -1252,8 +1272,12 @@ public override nint RowsInSection(UITableView tableview, nint section)
public override void Scrolled(UIScrollView scrollView)
{
+ if (!_uiTableViewController.TryGetTarget(out var uiTableViewController))
+ return;
+
var args = new ScrolledEventArgs(scrollView.ContentOffset.X, scrollView.ContentOffset.Y);
- List?.SendScrolled(args);
+ if (!_list.TryGetTarget(out var list))
+ list.SendScrolled(args);
if (_isDragging && scrollView.ContentOffset.Y < 0)
{
@@ -1263,19 +1287,21 @@ public override void Scrolled(UIScrollView scrollView)
// will promptly kill, and the app will close)
// So we temporarily flip _isDragging to false in order to prevent the loop.
_isDragging = false;
- _uiTableViewController.UpdateShowHideRefresh(true);
+ uiTableViewController.UpdateShowHideRefresh(true);
_isDragging = true;
}
- if (_isDragging && scrollView.ContentOffset.Y < -10f && _uiTableViewController._usingLargeTitles && DeviceDisplay.MainDisplayInfo.Orientation.IsPortrait())
+ if (_isDragging && scrollView.ContentOffset.Y < -10f && uiTableViewController._usingLargeTitles && DeviceDisplay.MainDisplayInfo.Orientation.IsPortrait())
{
- _uiTableViewController.ForceRefreshing();
+ uiTableViewController.ForceRefreshing();
}
}
public override string[] SectionIndexTitles(UITableView tableView)
{
- var templatedItems = TemplatedItemsView.TemplatedItems;
+ if (!_list.TryGetTarget(out var list))
+ return null;
+ var templatedItems = list.TemplatedItems;
if (templatedItems.ShortNames == null)
return null;
@@ -1292,10 +1318,15 @@ public void UpdateGrouping()
{
UpdateShortNameListener();
- if (List.OnThisPlatform().RowAnimationsEnabled())
- _uiTableView.ReloadData();
+ if (!_uiTableView.TryGetTarget(out var uiTableView))
+ return;
+ if (!_list.TryGetTarget(out var list))
+ return;
+
+ if (list.OnThisPlatform().RowAnimationsEnabled())
+ uiTableView.ReloadData();
else
- PerformWithoutAnimation(() => { _uiTableView.ReloadData(); });
+ PerformWithoutAnimation(uiTableView.ReloadData);
}
public void DetermineEstimatedRowHeight()
@@ -1303,15 +1334,20 @@ public void DetermineEstimatedRowHeight()
if (_estimatedRowHeight)
return;
- UpdateEstimatedRowHeight(_uiTableView);
+ if (!_uiTableView.TryGetTarget(out var uiTableView))
+ return;
+
+ UpdateEstimatedRowHeight(uiTableView);
_estimatedRowHeight = true;
}
protected bool IsValidIndexPath(NSIndexPath indexPath)
{
- var templatedItems = TemplatedItemsView.TemplatedItems;
- if (List.IsGroupingEnabled)
+ if (!_list.TryGetTarget(out var list))
+ return false;
+ var templatedItems = ((ITemplatedItemsView)list).TemplatedItems;
+ if (list.IsGroupingEnabled)
{
var section = indexPath.Section;
if (section < 0 || section >= templatedItems.Count)
@@ -1325,8 +1361,10 @@ protected bool IsValidIndexPath(NSIndexPath indexPath)
protected ITemplatedItemsList GetTemplatedItemsListForPath(NSIndexPath indexPath)
{
- var templatedItems = TemplatedItemsView.TemplatedItems;
- if (List.IsGroupingEnabled)
+ if (!_list.TryGetTarget(out var list))
+ return null;
+ var templatedItems = ((ITemplatedItemsView)list).TemplatedItems;
+ if (list.IsGroupingEnabled)
templatedItems = (ITemplatedItemsList| )((IList)templatedItems)[indexPath.Section];
return templatedItems;
@@ -1355,10 +1393,14 @@ protected Cell GetCellForPath(NSIndexPath indexPath)
void OnShortNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
- if (List.OnThisPlatform().RowAnimationsEnabled())
- _uiTableView.ReloadSectionIndexTitles();
+ if (!_uiTableView.TryGetTarget(out var uiTableView))
+ return;
+ if (!_list.TryGetTarget(out var list))
+ return;
+ if (list.OnThisPlatform().RowAnimationsEnabled())
+ uiTableView.ReloadSectionIndexTitles();
else
- PerformWithoutAnimation(() => { _uiTableView.ReloadSectionIndexTitles(); });
+ PerformWithoutAnimation(uiTableView.ReloadSectionIndexTitles);
}
static void SetCellBackgroundColor(UITableViewCell cell, UIColor color)
@@ -1371,7 +1413,9 @@ static void SetCellBackgroundColor(UITableViewCell cell, UIColor color)
int TemplateIdForPath(NSIndexPath indexPath)
{
- var itemTemplate = List.ItemTemplate;
+ if (!_list.TryGetTarget(out var list))
+ return 0;
+ var itemTemplate = list.ItemTemplate;
var selector = itemTemplate as DataTemplateSelector;
if (selector == null)
return DefaultItemTemplateId;
@@ -1379,7 +1423,7 @@ int TemplateIdForPath(NSIndexPath indexPath)
var templatedList = GetTemplatedItemsListForPath(indexPath);
var item = templatedList.ListProxy[indexPath.Row];
- itemTemplate = selector.SelectTemplate(item, List);
+ itemTemplate = selector.SelectTemplate(item, list);
int key;
if (!_templateToId.TryGetValue(itemTemplate, out key))
{
@@ -1392,12 +1436,16 @@ int TemplateIdForPath(NSIndexPath indexPath)
void UpdateShortNameListener()
{
- WatchShortNameCollection(List.IsGroupingEnabled);
+ if (!_list.TryGetTarget(out var list))
+ return;
+ WatchShortNameCollection(list.IsGroupingEnabled);
}
void WatchShortNameCollection(bool watch)
{
- var templatedList = TemplatedItemsView.TemplatedItems;
+ if (!_list.TryGetTarget(out var list))
+ return;
+ var templatedList = list.TemplatedItems;
if (templatedList.ShortNames == null)
{
@@ -1424,7 +1472,9 @@ protected virtual void UpdateEstimatedRowHeight(UITableView tableView)
tableView.EstimatedRowHeight = DefaultRowHeight;
// if even rows OR uneven rows but user specified a row height anyway...
- if (!List.HasUnevenRows || List.RowHeight != -1)
+ if (!_list.TryGetTarget(out var list))
+ return;
+ if (!list.HasUnevenRows || list.RowHeight != -1)
tableView.EstimatedRowHeight = 0;
}
@@ -1435,16 +1485,13 @@ protected override void Dispose(bool disposing)
if (disposing)
{
- if (List != null)
+ if (!_list.TryGetTarget(out var list))
{
- List.ItemSelected -= OnItemSelected;
+ list.ItemSelected -= OnItemSelected;
WatchShortNameCollection(false);
- List = null;
}
_templateToId = null;
- _uiTableView = null;
- _uiTableViewController = null;
}
_disposed = true;
@@ -1502,7 +1549,7 @@ public override void LayoutSubviews()
internal sealed class FormsUITableViewController : UITableViewController
{
- ListView _list;
+ readonly WeakReference _list;
UIRefreshControl _refresh;
bool _refreshAdded;
@@ -1522,7 +1569,7 @@ public FormsUITableViewController(ListView element, bool usingLargeTitles)
_refresh = new FormsRefreshControl(_usingLargeTitles);
_refresh.ValueChanged += OnRefreshingChanged;
- _list = element;
+ _list = new(element);
}
public void UpdateIsRefreshing(bool refreshing)
@@ -1566,7 +1613,7 @@ public void UpdateIsRefreshing(bool refreshing)
UpdateContentOffset(-1);
_isRefreshing = false;
- if (!_list.IsPullToRefreshEnabled)
+ if (_list.TryGetTarget(out var list) && !list.IsPullToRefreshEnabled)
RemoveRefresh();
}
}
@@ -1613,31 +1660,37 @@ public void UpdatePullToRefreshEnabled(bool pullToRefreshEnabled)
//hack: Form some reason UIKit isn't allowing to scroll negative values with largetitles
public void ForceRefreshing()
{
- if (!_list.IsPullToRefreshEnabled)
+ if (!_list.TryGetTarget(out var list))
+ return;
+ if (!list.IsPullToRefreshEnabled)
return;
if (!_refresh.Refreshing && !_isRefreshing)
{
_isRefreshing = true;
UpdateContentOffset(TableView.ContentOffset.Y - _refresh.Frame.Height, StartRefreshing);
- _list.SendRefreshing();
+ list.SendRefreshing();
}
}
public void UpdateShowHideRefresh(bool shouldHide)
{
- if (_list.IsPullToRefreshEnabled)
+ if (!_list.TryGetTarget(out var list))
+ return;
+ if (list.IsPullToRefreshEnabled)
return;
if (shouldHide)
RemoveRefresh();
else
- UpdateIsRefreshing(_list.IsRefreshing);
+ UpdateIsRefreshing(list.IsRefreshing);
}
public override void ViewWillAppear(bool animated)
{
(TableView?.Source as ListViewRenderer.ListViewDataSource)?.Cleanup();
- if (!_list.IsRefreshing || !_refresh.Refreshing)
+ if (!_list.TryGetTarget(out var list))
+ return;
+ if (!list.IsRefreshing || !_refresh.Refreshing)
return;
// Restart the refreshing to get the animation to trigger
@@ -1670,8 +1723,6 @@ protected override void Dispose(bool disposing)
_refresh.Dispose();
_refresh = null;
}
-
- _list = null;
}
_disposed = true;
@@ -1689,8 +1740,8 @@ void CheckContentSize()
void OnRefreshingChanged(object sender, EventArgs eventArgs)
{
- if (_refresh.Refreshing)
- _list.SendRefreshing();
+ if (_refresh.Refreshing && _list.TryGetTarget(out var list))
+ list.SendRefreshing();
_isRefreshing = _refresh.Refreshing;
}
diff --git a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
index cd75ac067e30..84ba561a93f8 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
@@ -499,6 +499,14 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
UpdateHideNavigationBarSeparator();
}
+ else if (e.PropertyName == PrefersHomeIndicatorAutoHiddenProperty.PropertyName)
+ {
+ UpdateHomeIndicatorAutoHidden();
+ }
+ else if (e.PropertyName == PrefersStatusBarHiddenProperty.PropertyName)
+ {
+ UpdateStatusBarHidden();
+ }
}
void ValidateNavbarExists(Page newCurrentPage)
@@ -509,6 +517,22 @@ void ValidateNavbarExists(Page newCurrentPage)
View.InvalidateMeasure(Element);
}
+ void UpdateHomeIndicatorAutoHidden()
+ {
+ if (Element == null)
+ return;
+
+ SetNeedsUpdateOfHomeIndicatorAutoHidden();
+ }
+
+ void UpdateStatusBarHidden()
+ {
+ if (Element == null)
+ return;
+
+ SetNeedsStatusBarAppearanceUpdate();
+ }
+
void UpdateHideNavigationBarSeparator()
{
bool shouldHide = NavPage.OnThisPlatform().HideNavigationBarSeparator();
@@ -1483,6 +1507,7 @@ void UpdatePrefersStatusBarHidden()
{
View.SetNeedsLayout();
ParentViewController?.View.SetNeedsLayout();
+ SetNeedsStatusBarAppearanceUpdate();
}
void TrackerOnCollectionChanged(object sender, EventArgs eventArgs)
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutRenderer.cs
index b51dfd090692..2bf288734638 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellFlyoutRenderer.cs
@@ -44,6 +44,12 @@ void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance)
UIViewController IShellFlyoutRenderer.ViewController => this;
+ public override bool PrefersHomeIndicatorAutoHidden => Detail.PrefersHomeIndicatorAutoHidden;
+
+ public override bool PrefersStatusBarHidden() => Detail.PrefersStatusBarHidden();
+
+ public override UIStatusBarAnimation PreferredStatusBarUpdateAnimation => Detail.PreferredStatusBarUpdateAnimation;
+
void IShellFlyoutRenderer.AttachFlyout(IShellContext context, UIViewController content)
{
Context = context;
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellRenderer.cs
index dd5b0e3fe987..d66a7abb8002 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellRenderer.cs
@@ -5,6 +5,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.Platform.Compatibility;
+using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Graphics;
using UIKit;
@@ -21,6 +22,29 @@ public ShellRenderer()
}
+ public override bool PrefersHomeIndicatorAutoHidden
+ => Shell?.CurrentPage?.OnThisPlatform()?.PrefersHomeIndicatorAutoHidden() ?? base.PrefersHomeIndicatorAutoHidden;
+
+
+ public override bool PrefersStatusBarHidden()
+ => Shell?.CurrentPage?.OnThisPlatform()?.PrefersStatusBarHidden() == StatusBarHiddenMode.True;
+
+ public override UIKit.UIStatusBarAnimation PreferredStatusBarUpdateAnimation
+ {
+ get
+ {
+ var mode = Shell?.CurrentPage?.OnThisPlatform()?.PreferredStatusBarUpdateAnimation();
+ return mode switch
+ {
+ PlatformConfiguration.iOSSpecific.UIStatusBarAnimation.None => UIKit.UIStatusBarAnimation.None,
+ PlatformConfiguration.iOSSpecific.UIStatusBarAnimation.Fade => UIKit.UIStatusBarAnimation.Fade,
+ PlatformConfiguration.iOSSpecific.UIStatusBarAnimation.Slide => UIKit.UIStatusBarAnimation.Slide,
+ _ => base.PreferredStatusBarUpdateAnimation,
+ };
+ }
+ }
+
+
#region IShellContext
bool IShellContext.AllowFlyoutGesture
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 2e486f9642fb..9321069b3b89 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellSectionRenderer.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using System.Windows.Input;
using Foundation;
+using Microsoft.Maui.Controls.Handlers.Compatibility;
using Microsoft.Maui.Controls.Internals;
using ObjCRuntime;
using UIKit;
@@ -221,6 +222,15 @@ public override void ViewDidLoad()
}
+ public override void ViewDidAppear(bool animated)
+ {
+ base.ViewDidAppear(animated);
+ if (_context is ShellRenderer shellRenderer)
+ {
+ shellRenderer.ViewController.SetNeedsUpdateOfHomeIndicatorAutoHidden();
+ shellRenderer.ViewController.SetNeedsStatusBarAppearanceUpdate();
+ }
+ }
void IDisconnectable.Disconnect()
{
diff --git a/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs
index 52eda660fd55..0bb751d5a9be 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs
@@ -88,6 +88,7 @@ public void SetElement(VisualElement element)
UpdateBarTextColor();
UpdateSelectedTabColors();
UpdateBarTranslucent();
+ UpdatePageSpecifics();
}
public UIViewController ViewController
@@ -209,6 +210,8 @@ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
if (controller == null)
return;
+ SetNeedsUpdateOfHomeIndicatorAutoHidden();
+ SetNeedsStatusBarAppearanceUpdate();
SelectedViewController = controller;
}
else if (e.PropertyName == TabbedPage.BarBackgroundColorProperty.PropertyName)
@@ -223,8 +226,8 @@ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
UpdateCurrentPagePreferredStatusBarUpdateAnimation();
else if (e.PropertyName == TabbedPage.SelectedTabColorProperty.PropertyName || e.PropertyName == TabbedPage.UnselectedTabColorProperty.PropertyName)
UpdateSelectedTabColors();
- else if (e.PropertyName == PrefersHomeIndicatorAutoHiddenProperty.PropertyName)
- UpdatePrefersHomeIndicatorAutoHiddenOnPages();
+ else if (e.PropertyName == PrefersHomeIndicatorAutoHiddenProperty.PropertyName || e.PropertyName == PrefersStatusBarHiddenProperty.PropertyName)
+ UpdatePageSpecifics();
else if (e.PropertyName == TabbedPageConfiguration.TranslucencyModeProperty.PropertyName)
UpdateBarTranslucent();
@@ -265,13 +268,10 @@ public override UIViewController ChildViewControllerForHomeIndicatorAutoHidden
}
}
- void UpdatePrefersHomeIndicatorAutoHiddenOnPages()
+ void UpdatePageSpecifics()
{
- bool isHomeIndicatorHidden = Tabbed.OnThisPlatform().PrefersHomeIndicatorAutoHidden();
- for (var i = 0; i < ViewControllers.Length; i++)
- {
- Tabbed.GetPageByIndex(i).OnThisPlatform().SetPrefersHomeIndicatorAutoHidden(isHomeIndicatorHidden);
- }
+ ChildViewControllerForHomeIndicatorAutoHidden.SetNeedsUpdateOfHomeIndicatorAutoHidden();
+ ChildViewControllerForStatusBarHidden().SetNeedsStatusBarAppearanceUpdate();
}
void Reset()
diff --git a/src/Controls/src/Core/ContentPage/ContentPage.Mapper.cs b/src/Controls/src/Core/ContentPage/ContentPage.Mapper.cs
index 9b5de3ad62a0..c3d425d12c4d 100644
--- a/src/Controls/src/Core/ContentPage/ContentPage.Mapper.cs
+++ b/src/Controls/src/Core/ContentPage/ContentPage.Mapper.cs
@@ -12,8 +12,24 @@ public partial class ContentPage
internal new static void RemapForControls()
{
PageHandler.Mapper.ReplaceMapping(nameof(ContentPage.HideSoftInputOnTapped), MapHideSoftInputOnTapped);
+#if IOS
+ PageHandler.Mapper.ReplaceMapping(PlatformConfiguration.iOSSpecific.Page.PrefersHomeIndicatorAutoHiddenProperty.PropertyName, MapPrefersHomeIndicatorAutoHidden);
+ PageHandler.Mapper.ReplaceMapping(PlatformConfiguration.iOSSpecific.Page.PrefersStatusBarHiddenProperty.PropertyName, MapPrefersStatusBarHidden);
+#endif
}
+#if IOS
+ static void MapPrefersHomeIndicatorAutoHidden(IPageHandler handler, ContentPage page)
+ {
+ handler?.UpdateValue(nameof(IiOSPageSpecifics.IsHomeIndicatorAutoHidden));
+ }
+
+ static void MapPrefersStatusBarHidden(IPageHandler handler, ContentPage page)
+ {
+ handler?.UpdateValue(nameof(IiOSPageSpecifics.PrefersStatusBarHiddenMode));
+ }
+#endif
+
static void MapHideSoftInputOnTapped(IPageHandler handler, ContentPage page)
{
page.UpdateHideSoftInputOnTapped();
diff --git a/src/Controls/src/Core/Element/Element.cs b/src/Controls/src/Core/Element/Element.cs
index ca9be0ca1e16..8d18b3e2458c 100644
--- a/src/Controls/src/Core/Element/Element.cs
+++ b/src/Controls/src/Core/Element/Element.cs
@@ -319,7 +319,7 @@ internal Element ParentOverride
///
void IElementDefinition.AddResourcesChangedListener(Action | | | | | |