Merge branch 'main' into merge_main_net0
# Conflicts: # src/Essentials/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
|
@ -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 |
Двоичные данные
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShellTabItemsShouldUpdateForDynamicChangesInVisibility.png
Normal file
После Ширина: | Высота: | Размер: 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());
|
||||
|
|