Merge branch 'main' into merge_main_net0

# Conflicts:
#	src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
This commit is contained in:
Shane Neuville 2024-09-03 11:50:22 -05:00
Родитель 6a207d2c05 31ed71f666
Коммит 1274621056
22 изменённых файлов: 397 добавлений и 33 удалений

Просмотреть файл

@ -62,7 +62,7 @@
<MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion>9.0.0-rc.2.24422.4</MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion>
<MicrosoftNETWorkloadEmscriptenPackageVersion>$(MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion)</MicrosoftNETWorkloadEmscriptenPackageVersion>
<!-- wasdk -->
<MicrosoftWindowsAppSDKPackageVersion>1.5.240627000</MicrosoftWindowsAppSDKPackageVersion>
<MicrosoftWindowsAppSDKPackageVersion>1.5.240802000</MicrosoftWindowsAppSDKPackageVersion>
<MicrosoftWindowsSDKBuildToolsPackageVersion>10.0.22621.756</MicrosoftWindowsSDKBuildToolsPackageVersion>
<MicrosoftGraphicsWin2DPackageVersion>1.2.0</MicrosoftGraphicsWin2DPackageVersion>
<!-- Everything else -->

Просмотреть файл

@ -295,7 +295,13 @@ namespace Microsoft.Maui.Controls.Platform.Compatibility
oldPage.PropertyChanged -= OnDisplayedElementPropertyChanged;
if (newPage is not null)
{
newPage.PropertyChanged += OnDisplayedElementPropertyChanged;
if (oldPage is null)
{
_menuSetup = false;
}
}
if (newPage is not null && !_menuSetup)
{

Просмотреть файл

@ -357,6 +357,10 @@ namespace Microsoft.Maui.Controls.Platform.Compatibility
if (shellSection == null || _currentSection == shellSection)
return;
var renderer = RendererForShellContent(shellSection);
if (renderer == null)
return;
if (renderer?.ViewController != SelectedViewController)
SelectedViewController = renderer.ViewController;
CurrentRenderer = renderer;

Просмотреть файл

@ -432,7 +432,7 @@ namespace Microsoft.Maui.Controls.Handlers
if (_mainLevelTabs == null)
return;
var currentItem = VirtualView.CurrentItem.CurrentItem;
var currentItem = VirtualView.CurrentItem?.CurrentItem;
NavigationViewItemViewModel? navigationViewItemViewModel = null;
foreach (var item in _mainLevelTabs)

Просмотреть файл

@ -285,7 +285,10 @@ namespace Microsoft.Maui.Controls
var shellItem = (ShellItem)bindable;
if (newValue == null)
{
shellItem.CurrentItem = null;
return;
}
if (shellItem.Parent is Shell)
{

Просмотреть файл

@ -133,6 +133,74 @@ namespace Microsoft.Maui.Controls.Core.UnitTests.Layouts
Assert.Equal(100, flexFrame.Width);
}
[Fact]
public void FlexLayoutPaddingShouldBeAppliedCorrectly_RowDirection()
{
// Arrange
var padding = 16;
var root = new Grid();
var flexLayout = new FlexLayout { Padding = padding };
var view1 = new TestLabel();
var view2 = new TestLabel();
root.Add(flexLayout);
flexLayout.Add(view1 as IView);
flexLayout.Add(view2 as IView);
// Act
var measure = flexLayout.CrossPlatformMeasure(1000, 1000);
flexLayout.CrossPlatformArrange(new Rect(Point.Zero, measure));
var view1Frame = flexLayout.Children[0].Frame;
var view2Frame = flexLayout.Children[1].Frame;
var leftPadding = view1Frame.X;
var topPadding = view1Frame.Y;
var rightPadding = measure.Width - view2Frame.Right;
var expectedView1Width = measure.Width - (leftPadding + rightPadding + view2.Width);
var expectedView2Width = measure.Width - (leftPadding + rightPadding + view1.Width);
// Assert
Assert.Equal(padding, leftPadding);
Assert.Equal(padding, rightPadding);
Assert.Equal(padding, topPadding);
Assert.Equal(expectedView1Width, view1Frame.Width);
Assert.Equal(expectedView2Width, view2Frame.Width);
}
[Fact]
public void FlexLayoutPaddingShouldBeAppliedCorrectly_ColumnDirection()
{
// Arrange
var padding = 16;
var root = new Grid();
var flexLayout = new FlexLayout { Padding = padding, Direction = FlexDirection.Column };
var view1 = new TestLabel();
var view2 = new TestLabel();
root.Add(flexLayout);
flexLayout.Add(view1 as IView);
flexLayout.Add(view2 as IView);
// Act
var measure = flexLayout.CrossPlatformMeasure(1000, 1000);
flexLayout.CrossPlatformArrange(new Rect(Point.Zero, measure));
var view1Frame = flexLayout.Children[0].Frame;
var view2Frame = flexLayout.Children[1].Frame;
var bottomPadding = measure.Height - view2Frame.Bottom;
var topPadding = view1Frame.Y;
var expectedView1Height = measure.Height - (topPadding + bottomPadding + view2.Height);
var expectedView2Height = measure.Height - (topPadding + bottomPadding + view1.Height);
// Assert
Assert.Equal(padding, bottomPadding);
Assert.Equal(view2.Height, view2Frame.Height);
Assert.Equal(expectedView1Height, view1Frame.Height);
Assert.Equal(expectedView2Height, view2Frame.Height);
}
[Theory]
[InlineData(double.PositiveInfinity, 400, FlexDirection.RowReverse)]
[InlineData(double.PositiveInfinity, 400, FlexDirection.Row)]

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 32 KiB

Просмотреть файл

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue8788"
xmlns:local="clr-namespace:Maui.Controls.Sample.Issues"
Shell.FlyoutBehavior="Locked"
Shell.FlyoutWidth="200">
<MenuItem x:Name="MenuItem" Text="Click to hide Tab1" Clicked="MenuItem_Clicked" AutomationId="FirstButton" />
<TabBar>
<Tab x:Name="Tab1" AutomationId="Tab1" Title="Tab 1">
<ShellContent Title="Tab 1" ContentTemplate="{DataTemplate local:MainTabPage}" Route="MainPage" />
</Tab>
<Tab x:Name="Tab2" AutomationId="Tab2" Title="Tab 2" IsVisible="false">
<ShellContent Title="Tab 2" ContentTemplate="{DataTemplate local:SecondTabPage}" Route="Tab2Page" />
</Tab>
<Tab x:Name="Tab3" Title="Tab 3" AutomationId="Tab3" IsVisible="false">
<ShellContent Title="Tab 3" ContentTemplate="{DataTemplate local:ThirdTabPage}" Route="Tab3Page" />
</Tab>
</TabBar>
</Shell>

Просмотреть файл

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 8788, "Shell Tab is still visible after set Tab.IsVisible to false", PlatformAffected.Android)]
public partial class Issue8788 : Shell
{
public Issue8788()
{
InitializeComponent();
}
private void MenuItem_Clicked(object sender, EventArgs e)
{
#if ANDROID
Tab2.IsVisible = true;
Tab3.IsVisible = true;
Tab1.IsVisible = false;
#else
Tab1.IsVisible = false;
Tab2.IsVisible = true;
Tab3.IsVisible = true;
#endif
}
}
public partial class MainTabPage : ContentPage
{
public MainTabPage()
{
Content = new VerticalStackLayout()
{
new Label(){
Text = "Page Loaded in first Tab",
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
TextDecorations = TextDecorations.Underline,
},
};
}
}
public partial class SecondTabPage : ContentPage
{
public SecondTabPage()
{
Content = new VerticalStackLayout()
{
new Label(){
Text = "Page Loaded in Second Tab",
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
TextDecorations = TextDecorations.Underline,
},
};
}
}
public partial class ThirdTabPage : ContentPage
{
public ThirdTabPage()
{
Content = new VerticalStackLayout()
{
new Label(){
Text = "Page Loaded in Third Tab",
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
TextDecorations = TextDecorations.Underline,
},
};
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
#if !MACCATALYST
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;
namespace Microsoft.Maui.TestCases.Tests.Issues
{
internal class Issue8788 : _IssuesUITest
{
public Issue8788(TestDevice device) : base(device) { }
public override string Issue => "Shell Tab is still visible after set Tab.IsVisible to false";
[Test]
[Category(UITestCategories.Shell)]
public void ShellTabItemsShouldUpdateForDynamicChangesInVisibility()
{
#if WINDOWS
App.TapCoordinates(100, 57);
#else
App.WaitForElement("FirstButton");
App.Tap("FirstButton");
#endif
VerifyScreenshot();
}
}
}
#endif

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 9.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 72 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 25 KiB

После

Ширина:  |  Высота:  |  Размер: 25 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 50 KiB

После

Ширина:  |  Высота:  |  Размер: 48 KiB

Просмотреть файл

@ -15,7 +15,14 @@ namespace Microsoft.Maui.Layouts
public Size ArrangeChildren(Rect bounds)
{
FlexLayout.Layout(bounds.Width, bounds.Height);
var padding = FlexLayout.Padding;
double top = padding.Top + bounds.Top;
double left = padding.Left + bounds.Left;
double availableWidth = bounds.Width - padding.HorizontalThickness;
double availableHeight = bounds.Height - padding.VerticalThickness;
FlexLayout.Layout(availableWidth, availableHeight);
foreach (var child in FlexLayout)
{
@ -25,7 +32,8 @@ namespace Microsoft.Maui.Layouts
|| double.IsNaN(frame.Width)
|| double.IsNaN(frame.Height))
throw new Exception("something is deeply wrong");
frame = frame.Offset(bounds.Left, bounds.Top);
frame = frame.Offset(left, top);
child.Arrange(frame);
}
@ -34,10 +42,15 @@ namespace Microsoft.Maui.Layouts
public Size Measure(double widthConstraint, double heightConstraint)
{
var padding = FlexLayout.Padding;
var availableWidth = widthConstraint - padding.HorizontalThickness;
var availableHeight = heightConstraint - padding.VerticalThickness;
double measuredHeight = 0;
double measuredWidth = 0;
FlexLayout.Layout(widthConstraint, heightConstraint);
FlexLayout.Layout(availableWidth, availableHeight);
foreach (var child in FlexLayout)
{
@ -51,8 +64,11 @@ namespace Microsoft.Maui.Layouts
measuredWidth = Math.Max(measuredWidth, frame.Right);
}
var finalHeight = LayoutManager.ResolveConstraints(heightConstraint, FlexLayout.Height, measuredHeight, FlexLayout.MinimumHeight, FlexLayout.MaximumHeight);
var finalWidth = LayoutManager.ResolveConstraints(widthConstraint, FlexLayout.Width, measuredWidth, FlexLayout.MinimumWidth, FlexLayout.MaximumWidth);
var finalHeight = LayoutManager.ResolveConstraints(heightConstraint, FlexLayout.Height, measuredHeight + padding.VerticalThickness,
FlexLayout.MinimumHeight, FlexLayout.MaximumHeight);
var finalWidth = LayoutManager.ResolveConstraints(widthConstraint, FlexLayout.Width, measuredWidth + padding.HorizontalThickness,
FlexLayout.MinimumWidth, FlexLayout.MaximumWidth);
return new Size(finalWidth, finalHeight);
}

Просмотреть файл

@ -521,14 +521,75 @@ namespace Microsoft.Maui.ApplicationModel
public partial class StorageRead : BasePlatformPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new (string, bool)[] { (Manifest.Permission.ReadExternalStorage, true) };
public override (string androidPermission, bool isRuntime)[] RequiredPermissions
{
get
{
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
return [];
}
return new (string, bool)[] { (Manifest.Permission.ReadExternalStorage, true) };
}
}
public override Task<PermissionStatus> RequestAsync()
{
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
return Task.FromResult(PermissionStatus.Granted);
}
return base.RequestAsync();
}
public override Task<PermissionStatus> CheckStatusAsync()
{
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
return Task.FromResult(PermissionStatus.Granted);
}
return base.CheckStatusAsync();
}
}
public partial class StorageWrite : BasePlatformPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new (string, bool)[] { (Manifest.Permission.WriteExternalStorage, true) };
public override (string androidPermission, bool isRuntime)[] RequiredPermissions
{
get
{
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
return [];
}
return new (string, bool)[] { (Manifest.Permission.WriteExternalStorage, true) };
}
}
public override Task<PermissionStatus> RequestAsync()
{
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
return Task.FromResult(PermissionStatus.Granted);
}
return base.RequestAsync();
}
public override Task<PermissionStatus> CheckStatusAsync()
{
if (OperatingSystem.IsAndroidVersionAtLeast(33))
{
return Task.FromResult(PermissionStatus.Granted);
}
return base.CheckStatusAsync();
}
}
public partial class Vibrate : BasePlatformPermission

Просмотреть файл

@ -56,7 +56,11 @@ static Microsoft.Maui.Devices.Sensors.Geolocation.StopListeningForeground() -> v
*REMOVED*Microsoft.Maui.Media.IMediaPicker.CaptureVideoAsync(Microsoft.Maui.Media.MediaPickerOptions? options = null) -> System.Threading.Tasks.Task<Microsoft.Maui.Storage.FileResult!>!
*REMOVED*Microsoft.Maui.Media.IMediaPicker.PickPhotoAsync(Microsoft.Maui.Media.MediaPickerOptions? options = null) -> System.Threading.Tasks.Task<Microsoft.Maui.Storage.FileResult!>!
*REMOVED*Microsoft.Maui.Media.IMediaPicker.PickVideoAsync(Microsoft.Maui.Media.MediaPickerOptions? options = null) -> System.Threading.Tasks.Task<Microsoft.Maui.Storage.FileResult!>!
~override Microsoft.Maui.ApplicationModel.Permissions.StorageRead.CheckStatusAsync() -> System.Threading.Tasks.Task<Microsoft.Maui.ApplicationModel.PermissionStatus>
~override Microsoft.Maui.ApplicationModel.Permissions.StorageRead.RequestAsync() -> System.Threading.Tasks.Task<Microsoft.Maui.ApplicationModel.PermissionStatus>
~override Microsoft.Maui.ApplicationModel.Permissions.StorageWrite.CheckStatusAsync() -> System.Threading.Tasks.Task<Microsoft.Maui.ApplicationModel.PermissionStatus>
~override Microsoft.Maui.ApplicationModel.Permissions.StorageWrite.RequestAsync() -> System.Threading.Tasks.Task<Microsoft.Maui.ApplicationModel.PermissionStatus>
static Microsoft.Maui.Storage.Preferences.Get(string! key, System.DateTimeOffset defaultValue, string? sharedName) -> System.DateTimeOffset
static Microsoft.Maui.Storage.Preferences.Get(string! key, System.DateTimeOffset defaultValue) -> System.DateTimeOffset
static Microsoft.Maui.Storage.Preferences.Set(string! key, System.DateTimeOffset value, string? sharedName) -> void
static Microsoft.Maui.Storage.Preferences.Set(string! key, System.DateTimeOffset value) -> void
static Microsoft.Maui.Storage.Preferences.Set(string! key, System.DateTimeOffset value) -> void

Просмотреть файл

@ -3,6 +3,7 @@ using Android.App;
using Android.Content;
using AndroidX.Security.Crypto;
using Java.Security;
using Javax.Crypto;
using Xamarin.Google.Crypto.Tink.Shaded.Protobuf;
namespace Microsoft.Maui.Storage
@ -80,31 +81,62 @@ namespace Microsoft.Maui.Storage
editor?.Clear()?.Apply();
}
static void DeleteSharedPreferences()
{
// Open an editor to the preferences we can clear, using the alias for storing encrypted values
var editPreferences = Application.Context.GetSharedPreferences(Alias, FileCreationMode.Private).Edit();
// Commit is synchronous here so we can be sure it's done before trying to create the encrypted preferences again
editPreferences?.Clear()?.Commit();
}
ISharedPreferences GetEncryptedSharedPreferences()
{
try
{
var context = Application.Context;
var prefsMainKey = new MasterKey.Builder(context, Alias)
.SetKeyScheme(MasterKey.KeyScheme.Aes256Gcm)
.Build();
return EncryptedSharedPreferences.Create(
context,
Alias,
prefsMainKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.Aes256Siv,
EncryptedSharedPreferences.PrefValueEncryptionScheme.Aes256Gcm);
return CreateEncryptedSharedPreferences();
}
catch (InvalidProtocolBufferException)
catch (System.Exception ex)
when (ex is InvalidProtocolBufferException or Android.Security.KeyStoreException or KeyStoreException or BadPaddingException)
{
// TODO: Use Logger here?
System.Diagnostics.Debug.WriteLine(
"Unable get encrypted shared preferences, which is likely due to an app uninstall. Removing all keys and returning null.");
PlatformRemoveAll();
// If we encounter any of these exceptions, it's likely due to a corrupt key or bad migration between devices
// There isn't much to do at this point except try to delete the shared preferences so we can recreate them
try
{
System.Diagnostics.Debug.WriteLine(
"Unable get encrypted shared preferences, which is likely due to corrupt encryption key or bad app cache backup/restore. Removing all keys and returning null.");
System.Diagnostics.Debug.WriteLine(ex);
// Delete the shared preferences
DeleteSharedPreferences();
// Try to return a new instance now that we've deleted the old
return CreateEncryptedSharedPreferences();
}
catch (System.Exception ex2)
{
// If we still can't create things, we'll have to give up and return null
// TODO: Use Logger here?
System.Diagnostics.Debug.WriteLine("Still unable to create encrypted shared preferences after attempting to deleting them. Returning null.");
System.Diagnostics.Debug.WriteLine(ex2);
}
return null;
}
}
ISharedPreferences CreateEncryptedSharedPreferences()
{
var context = Application.Context;
var prefsMainKey = new MasterKey.Builder(context, Alias)
.SetKeyScheme(MasterKey.KeyScheme.Aes256Gcm)
.Build();
return EncryptedSharedPreferences.Create(
context,
Alias,
prefsMainKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.Aes256Siv,
EncryptedSharedPreferences.PrefValueEncryptionScheme.Aes256Gcm);
}
}
}

Просмотреть файл

@ -1,6 +1,10 @@
using System;
using System.Threading.Tasks;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Dispatching;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.Maui.Essentials.DeviceTests
{
@ -74,5 +78,30 @@ namespace Microsoft.Maui.Essentials.DeviceTests
await Assert.ThrowsAsync<PermissionException>(async () => await Permissions.RequestAsync<Permissions.LocationWhenInUse>());
});
}
[Fact
#if !__ANDROID__
(Skip = "Test only applies to Android")
#endif
]
public async Task StorageAndroid13AlwaysGranted()
{
if (DeviceInfo.Platform == DevicePlatform.Android && OperatingSystem.IsAndroidVersionAtLeast(33))
{
var status = await Permissions.CheckStatusAsync<Permissions.StorageRead>();
Assert.Equal(PermissionStatus.Granted, status);
status = await Permissions.CheckStatusAsync<Permissions.StorageWrite>();
Assert.Equal(PermissionStatus.Granted, status);
}
else // Android < API 33, we didn't request these, so status denied
{
var status = await Permissions.CheckStatusAsync<Permissions.StorageRead>();
Assert.Equal(PermissionStatus.Denied, status);
status = await Permissions.CheckStatusAsync<Permissions.StorageWrite>();
Assert.Equal(PermissionStatus.Denied, status);
}
}
}
}

Просмотреть файл

@ -106,7 +106,7 @@ namespace Microsoft.Maui.Graphics
public void SubtractFromClip(float x, float y, float width, float height);
/// <summary>
/// Clips an object so that only the area outside of a <see cref="PathF"/> object will be visible.
/// Clips an object so that only the area that's within the region of a <see cref="PathF"/> object will be visible.
/// </summary>
/// <param name="path">The path used to clip the object</param>
/// <param name="windingMode">Fill algorithm used for the path. Default is <see cref="WindingMode.NonZero"/>.</param>

Просмотреть файл

@ -55,7 +55,7 @@
<!-- additional controls -->
<StackLayout Orientation="Horizontal" Grid.Row="3" HorizontalOptions="Center" Spacing="5">
<Button Text="Run Filtered" Command="{Binding RunFilteredTestsCommand}" />
<Button Text="{Binding RunFilteredText}" Command="{Binding RunFilteredTestsCommand}" />
<Button Text="Run All" Command="{Binding RunAllTestsCommand}" />
</StackLayout>

Просмотреть файл

@ -44,7 +44,7 @@ namespace Microsoft.Maui.TestUtils.DeviceTests.Runners.VisualRunner
RunInfo = runInfo;
RunAllTestsCommand = new Command(RunAllTestsExecute, () => !_isBusy);
RunFilteredTestsCommand = new Command(RunFilteredTestsExecute, () => !_isBusy);
RunFilteredTestsCommand = new Command(RunFilteredTestsExecute, () => !_isBusy && (_filteredTests != null && _filteredTests.Any()));
NavigateToResultCommand = new Command<TestCaseViewModel?>(NavigateToResultExecute, tc => !_isBusy);
DisplayName = Path.GetFileNameWithoutExtension(runInfo.AssemblyFileName);
@ -170,6 +170,8 @@ namespace Microsoft.Maui.TestUtils.DeviceTests.Runners.VisualRunner
set => Set(ref _skipped, value);
}
public string RunFilteredText => (_filteredTests == null || !_filteredTests.Any()) ? $"Run Filtered" : $"Run {_filteredTests.Count()} Filtered";
void FilterAfterDelay()
{
_filterCancellationTokenSource?.Cancel();
@ -179,7 +181,12 @@ namespace Microsoft.Maui.TestUtils.DeviceTests.Runners.VisualRunner
Task.Delay(500, token)
.ContinueWith(
x => { _filteredTests.FilterArgument = new FilterArgs(SearchQuery, ResultFilter); },
x =>
{
_filteredTests.FilterArgument = new FilterArgs(SearchQuery, ResultFilter);
RunFilteredTestsCommand.ChangeCanExecute();
RaisePropertyChanged(nameof(RunFilteredText));
},
token,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());