Merge main into net9.0 (#24398)
### Description of Change We all know the drill!
This commit is contained in:
Коммит
ad8275e2bb
|
@ -45,124 +45,6 @@
|
|||
</Attributes>
|
||||
<Docs>
|
||||
<summary>An <see cref="T:Microsoft.Maui.Controls.ItemsView`1" /> that displays a collection of data as a vertical list.</summary>
|
||||
<remarks>
|
||||
<para>
|
||||
<img href="~/xml/Microsoft.Maui.Controls/_images/ListView.TripleScreenShot.png" />
|
||||
</para>
|
||||
<para>The following example shows a basic use:</para>
|
||||
<example>
|
||||
<code lang="csharp lang-csharp"><![CDATA[
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Maui.Controls;
|
||||
|
||||
namespace FormsGallery
|
||||
{
|
||||
class ListViewDemoPage : ContentPage
|
||||
{
|
||||
class Person
|
||||
{
|
||||
public Person(string name, DateTime birthday, Color favoriteColor)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Birthday = birthday;
|
||||
this.FavoriteColor = favoriteColor;
|
||||
}
|
||||
|
||||
public string Name { private set; get; }
|
||||
|
||||
public DateTime Birthday { private set; get; }
|
||||
|
||||
public Color FavoriteColor { private set; get; }
|
||||
};
|
||||
|
||||
public ListViewDemoPage()
|
||||
{
|
||||
Label header = new Label
|
||||
{
|
||||
Text = "ListView",
|
||||
FontSize = Device.GetNamedSize (NamedSize.Large, typeof(Label)),
|
||||
HorizontalOptions = LayoutOptions.Center
|
||||
};
|
||||
|
||||
// Define some data.
|
||||
List<Person> people = new List<Person>
|
||||
{
|
||||
new Person("Abigail", new DateTime(1975, 1, 15), Color.Aqua),
|
||||
new Person("Bob", new DateTime(1976, 2, 20), Color.Black),
|
||||
// ...etc.,...
|
||||
new Person("Yvonne", new DateTime(1987, 1, 10), Color.Purple),
|
||||
new Person("Zachary", new DateTime(1988, 2, 5), Color.Red)
|
||||
};
|
||||
|
||||
// Create the ListView.
|
||||
ListView listView = new ListView
|
||||
{
|
||||
// Source of data items.
|
||||
ItemsSource = people,
|
||||
|
||||
// Define template for displaying each item.
|
||||
// (Argument of DataTemplate constructor is called for
|
||||
// each item; it must return a Cell derivative.)
|
||||
ItemTemplate = new DataTemplate(() =>
|
||||
{
|
||||
// Create views with bindings for displaying each property.
|
||||
Label nameLabel = new Label();
|
||||
nameLabel.SetBinding(Label.TextProperty, "Name");
|
||||
|
||||
Label birthdayLabel = new Label();
|
||||
birthdayLabel.SetBinding(Label.TextProperty,
|
||||
new Binding("Birthday", BindingMode.OneWay,
|
||||
null, null, "Born {0:d}"));
|
||||
|
||||
BoxView boxView = new BoxView();
|
||||
boxView.SetBinding(BoxView.ColorProperty, "FavoriteColor");
|
||||
|
||||
// Return an assembled ViewCell.
|
||||
return new ViewCell
|
||||
{
|
||||
View = new StackLayout
|
||||
{
|
||||
Padding = new Thickness(0, 5),
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
boxView,
|
||||
new StackLayout
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
Spacing = 0,
|
||||
Children =
|
||||
{
|
||||
nameLabel,
|
||||
birthdayLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
// Accomodate iPhone status bar.
|
||||
this.Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 5);
|
||||
|
||||
// Build the page.
|
||||
this.Content = new StackLayout
|
||||
{
|
||||
Children =
|
||||
{
|
||||
header,
|
||||
listView
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
]]></code>
|
||||
</example>
|
||||
</remarks>
|
||||
</Docs>
|
||||
<Members>
|
||||
<Member MemberName=".ctor">
|
||||
|
|
|
@ -330,7 +330,10 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
|
|||
var actuallyRemoved = poppedViewController == null ? true : !await task;
|
||||
_ignorePopCall = false;
|
||||
|
||||
poppedViewController?.Dispose();
|
||||
if (poppedViewController is ParentingViewController pvc)
|
||||
pvc.Disconnect(false);
|
||||
else
|
||||
poppedViewController?.Dispose();
|
||||
|
||||
UpdateToolBarVisible();
|
||||
return actuallyRemoved;
|
||||
|
@ -1144,7 +1147,9 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
|
|||
return;
|
||||
|
||||
if (child is not null)
|
||||
{
|
||||
child.PropertyChanged -= HandleChildPropertyChanged;
|
||||
}
|
||||
|
||||
if (value is not null)
|
||||
{
|
||||
|
@ -1274,6 +1279,57 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
|
|||
}
|
||||
}
|
||||
|
||||
internal void Disconnect(bool dispose)
|
||||
{
|
||||
if (Child is Page child)
|
||||
{
|
||||
child.SendDisappearing();
|
||||
child.PropertyChanged -= HandleChildPropertyChanged;
|
||||
Child = null;
|
||||
}
|
||||
|
||||
if (_tracker is not null)
|
||||
{
|
||||
_tracker.Target = null;
|
||||
_tracker.CollectionChanged -= TrackerOnCollectionChanged;
|
||||
_tracker = null;
|
||||
}
|
||||
|
||||
if (NavigationItem.TitleView is not null)
|
||||
{
|
||||
if (dispose)
|
||||
NavigationItem.TitleView.Dispose();
|
||||
|
||||
NavigationItem.TitleView = null;
|
||||
}
|
||||
|
||||
if (NavigationItem.RightBarButtonItems is not null && dispose)
|
||||
{
|
||||
for (var i = 0; i < NavigationItem.RightBarButtonItems.Length; i++)
|
||||
NavigationItem.RightBarButtonItems[i].Dispose();
|
||||
}
|
||||
|
||||
if (ToolbarItems is not null && dispose)
|
||||
{
|
||||
for (var i = 0; i < ToolbarItems.Length; i++)
|
||||
ToolbarItems[i].Dispose();
|
||||
}
|
||||
|
||||
for (int i = View.Subviews.Length - 1; i >= 0; i--)
|
||||
{
|
||||
View.Subviews[i].RemoveFromSuperview();
|
||||
}
|
||||
|
||||
|
||||
for (int i = ChildViewControllers.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var childViewController = ChildViewControllers[i];
|
||||
childViewController.View.RemoveFromSuperview();
|
||||
childViewController.RemoveFromParentViewController();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
|
@ -1285,34 +1341,7 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
|
|||
|
||||
if (disposing)
|
||||
{
|
||||
if (Child is Page child)
|
||||
{
|
||||
child.SendDisappearing();
|
||||
child.PropertyChanged -= HandleChildPropertyChanged;
|
||||
Child = null;
|
||||
}
|
||||
|
||||
_tracker.Target = null;
|
||||
_tracker.CollectionChanged -= TrackerOnCollectionChanged;
|
||||
_tracker = null;
|
||||
|
||||
if (NavigationItem.TitleView != null)
|
||||
{
|
||||
NavigationItem.TitleView.Dispose();
|
||||
NavigationItem.TitleView = null;
|
||||
}
|
||||
|
||||
if (NavigationItem.RightBarButtonItems != null)
|
||||
{
|
||||
for (var i = 0; i < NavigationItem.RightBarButtonItems.Length; i++)
|
||||
NavigationItem.RightBarButtonItems[i].Dispose();
|
||||
}
|
||||
|
||||
if (ToolbarItems != null)
|
||||
{
|
||||
for (var i = 0; i < ToolbarItems.Length; i++)
|
||||
ToolbarItems[i].Dispose();
|
||||
}
|
||||
Disconnect(true);
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
@ -1973,7 +2002,7 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
|
|||
|
||||
if (_child?.IsConnected() == true)
|
||||
{
|
||||
_child.PlatformView.RemoveFromSuperview();
|
||||
(_child.ContainerView ?? _child.PlatformView).RemoveFromSuperview();
|
||||
_child.DisconnectHandler();
|
||||
_child = null;
|
||||
}
|
||||
|
|
|
@ -208,7 +208,7 @@ namespace Microsoft.Maui.Controls.Handlers.Compatibility
|
|||
public override void SetNeedsLayout()
|
||||
{
|
||||
base.SetNeedsLayout();
|
||||
Superview?.SetNeedsLayout();
|
||||
this.GetSuperViewIfWindowSet()?.SetNeedsLayout();
|
||||
}
|
||||
|
||||
[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
|
||||
|
|
|
@ -402,6 +402,8 @@ namespace Microsoft.Maui.Controls
|
|||
void RemoveFromInnerChildren(Element page)
|
||||
{
|
||||
InternalChildren.Remove(page);
|
||||
|
||||
// TODO For NET9 we should remove this because the DisconnectHandlers will take care of it
|
||||
page.Handler = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -188,7 +188,7 @@ namespace Microsoft.Maui.Controls.Platform
|
|||
{
|
||||
platformButton.ImageEdgeInsets = imageInsets;
|
||||
platformButton.TitleEdgeInsets = titleInsets;
|
||||
platformButton.Superview?.SetNeedsLayout();
|
||||
platformButton.GetSuperViewIfWindowSet()?.SetNeedsLayout();
|
||||
}
|
||||
#pragma warning restore CA1416, CA1422
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Microsoft.Maui.Controls
|
|||
newToolBarItems.AddRange(toolbarItems);
|
||||
|
||||
if (sender is ToolbarItem ti)
|
||||
PlatformView.OnToolbarItemPropertyChanged(e, ti, newToolBarItems, MauiContext!, null, OnToolbarItemPropertyChanged, _currentMenuItems, _currentToolbarItems, UpdateMenuItemIcon);
|
||||
PlatformView.OnToolbarItemPropertyChanged(e, ti, newToolBarItems, MauiContext!, BarTextColor, OnToolbarItemPropertyChanged, _currentMenuItems, _currentToolbarItems, UpdateMenuItemIcon);
|
||||
}
|
||||
|
||||
void UpdateMenuItemIcon(Context context, IMenuItem menuItem, ToolbarItem toolBarItem)
|
||||
|
|
Двоичные данные
src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemFontColorDynamicUpdate.png
Normal file
Двоичные данные
src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarItemFontColorDynamicUpdate.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 27 KiB |
Двоичные данные
src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarTextColorOnInteraction.png
Normal file
Двоичные данные
src/Controls/tests/TestCases.Android.Tests/snapshots/android/ToolbarTextColorOnInteraction.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 25 KiB |
|
@ -0,0 +1,29 @@
|
|||
<?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"
|
||||
xmlns:ns="clr-namespace:Maui.Controls.Sample.Issues"
|
||||
Shell.TitleColor="Red"
|
||||
x:Class="Maui.Controls.Sample.Issues.Issue10660">
|
||||
|
||||
<Shell.ToolbarItems>
|
||||
<ToolbarItem x:Name="StateButton" Text="Close"/>
|
||||
</Shell.ToolbarItems>
|
||||
|
||||
<ShellContent Title="Home page"
|
||||
Route="MainPage">
|
||||
<ShellContent.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ContentPage>
|
||||
<StackLayout>
|
||||
<Button Text="Change Text"
|
||||
AutomationId="ChangeText"
|
||||
Clicked="ChangeTextClicked"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
HorizontalOptions="CenterAndExpand" />
|
||||
</StackLayout>
|
||||
</ContentPage>
|
||||
</DataTemplate>
|
||||
</ShellContent.ContentTemplate>
|
||||
</ShellContent>
|
||||
</Shell>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.Maui.Controls;
|
||||
|
||||
namespace Maui.Controls.Sample.Issues;
|
||||
|
||||
[Issue(IssueTracker.Github, 10660, "Inconsistent toolbar text color on interaction", PlatformAffected.Android)]
|
||||
public partial class Issue10660 : Shell
|
||||
{
|
||||
|
||||
public Issue10660()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ChangeTextClicked(object sender, EventArgs e)
|
||||
{
|
||||
StateButton.Text = StateButton.Text == "Close" ? "Open" : "Close";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?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"
|
||||
xmlns:ns="clr-namespace:Maui.Controls.Sample.Issues"
|
||||
Shell.TitleColor="Red"
|
||||
x:Class="Maui.Controls.Sample.Issues.Issue22937">
|
||||
|
||||
<Shell.ToolbarItems>
|
||||
<ToolbarItem x:Name="EditButton" Text="Edit"/>
|
||||
<ToolbarItem x:Name="SaveButton" Text="Save"/>
|
||||
</Shell.ToolbarItems>
|
||||
|
||||
<ShellContent Title="Home page"
|
||||
Route="MainPage">
|
||||
<ShellContent.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ContentPage>
|
||||
<StackLayout>
|
||||
<Button Text="Change State"
|
||||
AutomationId="ChangeState"
|
||||
Clicked="ChangeStateClicked"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
HorizontalOptions="CenterAndExpand" />
|
||||
</StackLayout>
|
||||
</ContentPage>
|
||||
</DataTemplate>
|
||||
</ShellContent.ContentTemplate>
|
||||
</ShellContent>
|
||||
</Shell>
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.Maui.Controls;
|
||||
|
||||
namespace Maui.Controls.Sample.Issues;
|
||||
|
||||
[Issue(IssueTracker.Github, 22937, "ToolbarItem font color not updating properly after changing the available state at runtime", PlatformAffected.Android)]
|
||||
public partial class Issue22937 : Shell
|
||||
{
|
||||
|
||||
public Issue22937()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ChangeStateClicked(object sender, EventArgs e)
|
||||
{
|
||||
EditButton.IsEnabled = !EditButton.IsEnabled;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#if ANDROID || WINDOWS
|
||||
using NUnit.Framework;
|
||||
using UITest.Appium;
|
||||
using UITest.Core;
|
||||
|
||||
namespace Microsoft.Maui.TestCases.Tests.Issues
|
||||
{
|
||||
public class Issue10660 : _IssuesUITest
|
||||
{
|
||||
public override string Issue => "Inconsistent toolbar text color on interaction";
|
||||
|
||||
public Issue10660(TestDevice device)
|
||||
: base(device)
|
||||
{ }
|
||||
|
||||
[Test]
|
||||
[Category(UITestCategories.ToolbarItem)]
|
||||
public void ToolbarTextColorOnInteraction()
|
||||
{
|
||||
App.WaitForElement("ChangeText");
|
||||
App.Tap("ChangeText");
|
||||
|
||||
VerifyScreenshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,27 @@
|
|||
#if ANDROID || WINDOWS
|
||||
using NUnit.Framework;
|
||||
using UITest.Appium;
|
||||
using UITest.Core;
|
||||
|
||||
namespace Microsoft.Maui.TestCases.Tests.Issues
|
||||
{
|
||||
public class Issue22937 : _IssuesUITest
|
||||
{
|
||||
public override string Issue => "ToolbarItem font color not updating properly after changing the available state at runtime";
|
||||
|
||||
public Issue22937(TestDevice device)
|
||||
: base(device)
|
||||
{ }
|
||||
|
||||
[Test]
|
||||
[Category(UITestCategories.ToolbarItem)]
|
||||
public void ToolbarItemFontColorDynamicUpdate()
|
||||
{
|
||||
App.WaitForElement("ChangeState");
|
||||
App.Tap("ChangeState");
|
||||
|
||||
VerifyScreenshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
Двоичные данные
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemFontColorDynamicUpdate.png
Normal file
Двоичные данные
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarItemFontColorDynamicUpdate.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 8.3 KiB |
Двоичные данные
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarTextColorOnInteraction.png
Normal file
Двоичные данные
src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ToolbarTextColorOnInteraction.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 8.0 KiB |
|
@ -63,7 +63,7 @@ namespace Microsoft.Maui.Handlers
|
|||
if (containerView is WrapperView wrapperView)
|
||||
{
|
||||
wrapperView.RemoveFromSuperview();
|
||||
wrapperView.Dispose();
|
||||
wrapperView.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,14 @@ namespace Microsoft.Maui.Platform
|
|||
{
|
||||
InvalidateConstraintsCache();
|
||||
base.SubviewAdded(uiview);
|
||||
Superview?.SetNeedsLayout();
|
||||
this.GetSuperViewIfWindowSet()?.SetNeedsLayout();
|
||||
}
|
||||
|
||||
public override void WillRemoveSubview(UIView uiview)
|
||||
{
|
||||
InvalidateConstraintsCache();
|
||||
base.WillRemoveSubview(uiview);
|
||||
Superview?.SetNeedsLayout();
|
||||
this.GetSuperViewIfWindowSet()?.SetNeedsLayout();
|
||||
}
|
||||
|
||||
public override UIView? HitTest(CGPoint point, UIEvent? uievent)
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace Microsoft.Maui.Platform
|
|||
{
|
||||
InvalidateConstraintsCache();
|
||||
base.SetNeedsLayout();
|
||||
Superview?.SetNeedsLayout();
|
||||
this.GetSuperViewIfWindowSet()?.SetNeedsLayout();
|
||||
}
|
||||
|
||||
IVisualTreeElement? IVisualTreeElementProvidable.GetElement()
|
||||
|
|
|
@ -266,10 +266,18 @@ namespace Microsoft.Maui.Platform
|
|||
}
|
||||
}
|
||||
|
||||
internal static UIView? GetSuperViewIfWindowSet(this UIView? view)
|
||||
{
|
||||
if (view?.Window is null)
|
||||
return null;
|
||||
|
||||
return view.Superview;
|
||||
}
|
||||
|
||||
public static void InvalidateMeasure(this UIView platformView, IView view)
|
||||
{
|
||||
platformView.SetNeedsLayout();
|
||||
platformView.Superview?.SetNeedsLayout();
|
||||
platformView.GetSuperViewIfWindowSet()?.SetNeedsLayout();
|
||||
}
|
||||
|
||||
public static void UpdateWidth(this UIView platformView, IView view)
|
||||
|
|
|
@ -105,12 +105,19 @@ namespace Microsoft.Maui.Platform
|
|||
SetBorder();
|
||||
}
|
||||
|
||||
internal void Disconnect()
|
||||
{
|
||||
MaskLayer = null;
|
||||
BackgroundMaskLayer = null;
|
||||
ShadowLayer = null;
|
||||
_borderView?.RemoveFromSuperview();
|
||||
}
|
||||
|
||||
|
||||
// TODO obsolete or delete this for NET9
|
||||
public new void Dispose()
|
||||
{
|
||||
DisposeClip();
|
||||
DisposeShadow();
|
||||
DisposeBorder();
|
||||
|
||||
Disconnect();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
|
@ -158,7 +165,7 @@ namespace Microsoft.Maui.Platform
|
|||
{
|
||||
base.SetNeedsLayout();
|
||||
|
||||
Superview?.SetNeedsLayout();
|
||||
this.GetSuperViewIfWindowSet()?.SetNeedsLayout();
|
||||
}
|
||||
|
||||
partial void ClipChanged()
|
||||
|
@ -200,12 +207,6 @@ namespace Microsoft.Maui.Platform
|
|||
backgroundMask.Path = nativePath;
|
||||
}
|
||||
|
||||
void DisposeClip()
|
||||
{
|
||||
MaskLayer = null;
|
||||
BackgroundMaskLayer = null;
|
||||
}
|
||||
|
||||
void SetShadow()
|
||||
{
|
||||
var shadowLayer = ShadowLayer;
|
||||
|
@ -230,11 +231,6 @@ namespace Microsoft.Maui.Platform
|
|||
shadowLayer.SetShadow(Shadow);
|
||||
}
|
||||
|
||||
void DisposeShadow()
|
||||
{
|
||||
ShadowLayer = null;
|
||||
}
|
||||
|
||||
void SetBorder()
|
||||
{
|
||||
if (Border == null)
|
||||
|
@ -251,11 +247,6 @@ namespace Microsoft.Maui.Platform
|
|||
_borderView.UpdateMauiCALayer(Border);
|
||||
}
|
||||
|
||||
void DisposeBorder()
|
||||
{
|
||||
_borderView?.RemoveFromSuperview();
|
||||
}
|
||||
|
||||
CALayer? GetLayer()
|
||||
{
|
||||
var sublayers = Layer?.Sublayers;
|
||||
|
|
|
@ -19,11 +19,9 @@ namespace Microsoft.Maui.Authentication
|
|||
/// <param name="webAuthenticatorOptions">A <see cref="WebAuthenticatorOptions"/> instance containing additional configuration for this authentication call.</param>
|
||||
/// <returns>A <see cref="WebAuthenticatorResult"/> object with the results of this operation.</returns>
|
||||
/// <exception cref="TaskCanceledException">Thrown when the user canceled the authentication flow.</exception>
|
||||
/// <exception cref="HttpRequestException">Windows: Thrown when a HTTP Request error occured.</exception>
|
||||
/// <exception cref="Exception">Windows: Thrown when a unexpected HTTP response was received.</exception>
|
||||
/// <exception cref="PlatformNotSupportedException">Windows: Thrown when called on Windows.</exception>
|
||||
/// <exception cref="FeatureNotSupportedException">iOS/macOS: Thrown when iOS version is less than 13 is used or macOS less than 13.1 is used.</exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// <para>Windows: Thrown when the callback custom URL scheme is not registered in the AppxManifest.xml file.</para>
|
||||
/// <para>Android: Thrown when the no IntentFilter has been created for the callback URL.</para>
|
||||
/// </exception>
|
||||
Task<WebAuthenticatorResult> AuthenticateAsync(WebAuthenticatorOptions webAuthenticatorOptions);
|
||||
|
@ -76,13 +74,19 @@ namespace Microsoft.Maui.Authentication
|
|||
/// <summary>Begin an authentication flow by navigating to the specified url and waiting for a callback/redirect to the callbackUrl scheme.</summary>
|
||||
/// <param name="url"> Url to navigate to, beginning the authentication flow.</param>
|
||||
/// <param name="callbackUrl"> Expected callback url that the navigation flow will eventually redirect to.</param>
|
||||
/// <returns>Returns a result parsed out from the callback url.</returns>
|
||||
/// <returns>Returns a result parsed out from the callback url.</returns>
|
||||
#if !NETSTANDARD
|
||||
[System.Runtime.Versioning.UnsupportedOSPlatform("windows")]
|
||||
#endif
|
||||
public static Task<WebAuthenticatorResult> AuthenticateAsync(Uri url, Uri callbackUrl)
|
||||
=> Current.AuthenticateAsync(url, callbackUrl);
|
||||
|
||||
/// <summary>Begin an authentication flow by navigating to the specified url and waiting for a callback/redirect to the callbackUrl scheme.The start url and callbackUrl are specified in the webAuthenticatorOptions.</summary>
|
||||
/// <param name="webAuthenticatorOptions">Options to configure the authentication request.</param>
|
||||
/// <returns>Returns a result parsed out from the callback url.</returns>
|
||||
#if !NETSTANDARD
|
||||
[System.Runtime.Versioning.UnsupportedOSPlatform("windows")]
|
||||
#endif
|
||||
public static Task<WebAuthenticatorResult> AuthenticateAsync(WebAuthenticatorOptions webAuthenticatorOptions)
|
||||
=> Current.AuthenticateAsync(webAuthenticatorOptions);
|
||||
|
||||
|
|
|
@ -1,64 +1,13 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Microsoft.Maui.ApplicationModel;
|
||||
using Microsoft.Maui.Storage;
|
||||
using Windows.Security.Authentication.Web;
|
||||
|
||||
namespace Microsoft.Maui.Authentication
|
||||
{
|
||||
partial class WebAuthenticatorImplementation : IWebAuthenticator
|
||||
{
|
||||
public async Task<WebAuthenticatorResult> AuthenticateAsync(WebAuthenticatorOptions webAuthenticatorOptions)
|
||||
public Task<WebAuthenticatorResult> AuthenticateAsync(WebAuthenticatorOptions webAuthenticatorOptions)
|
||||
{
|
||||
var url = webAuthenticatorOptions?.Url;
|
||||
var callbackUrl = webAuthenticatorOptions?.CallbackUrl;
|
||||
|
||||
if (!IsUriProtocolDeclared(callbackUrl.Scheme))
|
||||
throw new InvalidOperationException($"You need to declare the windows.protocol usage of the protocol/scheme `{callbackUrl.Scheme}` in your AppxManifest.xml file");
|
||||
|
||||
try
|
||||
{
|
||||
var r = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, url, callbackUrl);
|
||||
|
||||
switch (r.ResponseStatus)
|
||||
{
|
||||
case WebAuthenticationStatus.Success:
|
||||
// For GET requests this is a URI:
|
||||
var resultUri = new Uri(r.ResponseData.ToString());
|
||||
return new WebAuthenticatorResult(resultUri, webAuthenticatorOptions?.ResponseDecoder);
|
||||
case WebAuthenticationStatus.UserCancel:
|
||||
throw new TaskCanceledException();
|
||||
case WebAuthenticationStatus.ErrorHttp:
|
||||
throw new HttpRequestException("Error: " + r.ResponseErrorDetail);
|
||||
default:
|
||||
throw new Exception("Response: " + r.ResponseData.ToString() + "\nStatus: " + r.ResponseStatus);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsUriProtocolDeclared(string scheme)
|
||||
{
|
||||
var docPath = FileSystemUtils.PlatformGetFullAppPackageFilePath(PlatformUtils.AppManifestFilename);
|
||||
var doc = XDocument.Load(docPath, LoadOptions.None);
|
||||
var reader = doc.CreateReader();
|
||||
var namespaceManager = new XmlNamespaceManager(reader.NameTable);
|
||||
namespaceManager.AddNamespace("x", PlatformUtils.AppManifestXmlns);
|
||||
namespaceManager.AddNamespace("uap", "http://schemas.microsoft.com/appx/manifest/uap/windows10");
|
||||
|
||||
// Check if the protocol was declared
|
||||
var decl = doc.Root.XPathSelectElements($"//uap:Extension[@Category='windows.protocol']/uap:Protocol[@Name='{scheme}']", namespaceManager);
|
||||
|
||||
return decl != null && decl.Any();
|
||||
throw new PlatformNotSupportedException("This implementation of WebAuthenticator does not support Windows. See https://github.com/microsoft/WindowsAppSDK/issues/441 for more details.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,21 @@ namespace Microsoft.Maui.Essentials.DeviceTests
|
|||
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
|
||||
public async Task Redirect(string urlBase, string callbackScheme, string accessToken, string refreshToken, int expires)
|
||||
{
|
||||
var r = await WebAuthenticator.AuthenticateAsync(
|
||||
#pragma warning disable CA1416 // Validate platform compatibility: Not supported on Windows
|
||||
var authenticationTask = WebAuthenticator.AuthenticateAsync(
|
||||
new Uri($"{urlBase}?access_token={accessToken}&refresh_token={refreshToken}&expires={expires}"),
|
||||
new Uri($"{callbackScheme}://"));
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
|
||||
#if WINDOWS
|
||||
var exception = await Assert.ThrowsAsync<PlatformNotSupportedException>(async () => await authenticationTask);
|
||||
#else
|
||||
var r = await authenticationTask;
|
||||
Assert.Equal(accessToken, r?.AccessToken);
|
||||
Assert.Equal(refreshToken, r?.RefreshToken);
|
||||
Assert.NotNull(r?.ExpiresIn);
|
||||
Assert.True(r?.ExpiresIn > DateTime.UtcNow);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -42,18 +49,24 @@ namespace Microsoft.Maui.Essentials.DeviceTests
|
|||
public async Task RedirectWithResponseDecoder(string urlBase, string callbackScheme, string accessToken, string refreshToken, int expires)
|
||||
{
|
||||
var responseDecoder = new TestResponseDecoder();
|
||||
var r = await WebAuthenticator.AuthenticateAsync(new WebAuthenticatorOptions
|
||||
#pragma warning disable CA1416 // Validate platform compatibility: Not supported on Windows
|
||||
var authenticationTask = WebAuthenticator.AuthenticateAsync(new WebAuthenticatorOptions
|
||||
{
|
||||
Url = new Uri($"{urlBase}?access_token={accessToken}&refresh_token={refreshToken}&expires={expires}"),
|
||||
CallbackUrl = new Uri($"{callbackScheme}://"),
|
||||
ResponseDecoder = responseDecoder
|
||||
});
|
||||
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
#if WINDOWS
|
||||
var exception = await Assert.ThrowsAsync<PlatformNotSupportedException>(async () => await authenticationTask);
|
||||
#else
|
||||
var r = await authenticationTask;
|
||||
Assert.Equal(accessToken, r?.AccessToken);
|
||||
Assert.Equal(refreshToken, r?.RefreshToken);
|
||||
Assert.NotNull(r?.ExpiresIn);
|
||||
Assert.True(r?.ExpiresIn > DateTime.UtcNow);
|
||||
Assert.Equal(1, responseDecoder.CallCount);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
Загрузка…
Ссылка в новой задаче