Merge branch 'main' into feature/sl/remove-popup-constraint-from-popup-service

This commit is contained in:
Shaun Lawrence 2024-09-11 22:09:52 +01:00 коммит произвёл GitHub
Родитель 880bd322fe 713b4f03e3
Коммит b701700b71
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
20 изменённых файлов: 437 добавлений и 52 удалений

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

@ -117,17 +117,16 @@ public partial class AppShell : Shell
CreateViewModelMapping<AvatarViewShadowsPage, AvatarViewShadowsViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<AvatarViewShapesPage, AvatarViewShapesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<AvatarViewSizesPage, AvatarViewSizesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<BasicMapsPage, BasicMapsViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<CameraViewPage, CameraViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<CustomSizeAndPositionPopupPage, CustomSizeAndPositionPopupViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<DrawingViewPage, DrawingViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<ExpanderPage, ExpanderViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<BasicMapsPage, BasicMapsViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MapsPinsPage, MapsPinsViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<LazyViewPage, LazyViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MapsPinsPage, MapsPinsViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MediaElementPage, MediaElementViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<CustomSizeAndPositionPopupPage, CustomSizeAndPositionPopupViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MediaElementCarouselViewPage, MediaElementCarouselViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MediaElementCollectionViewPage, MediaElementCollectionViewViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<MultiplePopupPage, MultiplePopupViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<PopupAnchorPage, PopupAnchorViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<PopupLayoutAlignmentPage, PopupLayoutAlignmentViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
@ -174,7 +173,7 @@ public partial class AppShell : Shell
return uri.Uri.OriginalString[..^1];
}
static string GetPageRoute(Type galleryPageType, Type contentPageType) => $"/{galleryPageType.Name}/{contentPageType.Name}";
static string GetPageRoute(Type galleryPageType, Type contentPageType) => $"//{galleryPageType.Name}/{contentPageType.Name}";
static KeyValuePair<Type, (Type GalleryPageType, Type ContentPageType)> CreateViewModelMapping<TPage, TViewModel, TGalleryPage, TGalleryViewModel>() where TPage : BasePage<TViewModel>
where TViewModel : BaseViewModel

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

@ -229,17 +229,16 @@ public static class MauiProgram
services.AddTransientWithShellRoute<NavigationBarPage, NavigationBarAndroidViewModel>();
// Add Views Pages + ViewModels
services.AddTransientWithShellRoute<BasicMapsPage, BasicMapsViewModel>();
services.AddTransientWithShellRoute<CameraViewPage, CameraViewViewModel>();
services.AddTransientWithShellRoute<CustomSizeAndPositionPopupPage, CustomSizeAndPositionPopupViewModel>();
services.AddTransientWithShellRoute<DrawingViewPage, DrawingViewViewModel>();
services.AddTransientWithShellRoute<ExpanderPage, ExpanderViewModel>();
services.AddTransientWithShellRoute<BasicMapsPage, BasicMapsViewModel>();
services.AddTransientWithShellRoute<MapsPinsPage, MapsPinsViewModel>();
services.AddTransientWithShellRoute<LazyViewPage, LazyViewViewModel>();
services.AddTransientWithShellRoute<MapsPinsPage, MapsPinsViewModel>();
services.AddTransientWithShellRoute<MediaElementPage, MediaElementViewModel>();
services.AddTransientWithShellRoute<CustomSizeAndPositionPopupPage, CustomSizeAndPositionPopupViewModel>();
services.AddTransientWithShellRoute<MediaElementCarouselViewPage, MediaElementCarouselViewViewModel>();
services.AddTransientWithShellRoute<MediaElementCollectionViewPage, MediaElementCollectionViewViewModel>();
services.AddTransientWithShellRoute<MultiplePopupPage, MultiplePopupViewModel>();
services.AddTransientWithShellRoute<PopupAnchorPage, PopupAnchorViewModel>();
services.AddTransientWithShellRoute<PopupLayoutAlignmentPage, PopupLayoutAlignmentViewModel>();

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

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
x:TypeArguments="viewModels:MediaElementCarouselViewViewModel"
x:DataType="viewModels:MediaElementCarouselViewViewModel"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.MediaElementCarouselViewPage"
Padding="0, 20, 0, 0"
Title="MediaElement in CarouselView">
<VerticalStackLayout Spacing="12">
<Label HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HorizontalOptions="Center"
VerticalOptions="Center"
Text="This page demonstrates that the MediaElement can be used inside of a DataTemplate"
Margin="12,0,12,0"/>
<CarouselView HeightRequest="275" PeekAreaInsets="52" ItemsSource="{Binding ItemSource}">
<CarouselView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
ItemSpacing="24"
SnapPointsAlignment="Center"
SnapPointsType="MandatorySingle" />
</CarouselView.ItemsLayout>
<CarouselView.ItemTemplate>
<DataTemplate x:DataType="viewModels:MediaElementDataSource">
<Border
x:Name="CarouselViewBorder"
BackgroundColor="Black"
Padding="5">
<Border.StrokeShape>
<RoundRectangle CornerRadius="4" />
</Border.StrokeShape>
<VerticalStackLayout Spacing="6" HeightRequest="250">
<toolkit:MediaElement
HeightRequest="200"
x:Name="MediaElement"
ShouldAutoPlay="True"
ShouldShowPlaybackControls="True"
Source="{Binding Source, Mode=OneTime}" />
<Label TextColor="White"
HorizontalOptions="Center"
VerticalOptions="Center"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FontAttributes="Bold"
FontSize="18"
Text="{Binding Name, Mode=OneTime}"/>
</VerticalStackLayout>
</Border>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<Label Text="Swipe Left or Right to see next video"
HorizontalOptions="Center"
VerticalOptions="Center"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FontAttributes="Italic"/>
</VerticalStackLayout>
</pages:BasePage>

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

@ -0,0 +1,11 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
namespace CommunityToolkit.Maui.Sample.Pages.Views;
public partial class MediaElementCarouselViewPage : BasePage<MediaElementCarouselViewViewModel>
{
public MediaElementCarouselViewPage(MediaElementCarouselViewViewModel viewModel) : base(viewModel)
{
InitializeComponent();
}
}

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

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
x:TypeArguments="viewModels:MediaElementCollectionViewViewModel"
x:DataType="viewModels:MediaElementCollectionViewViewModel"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.MediaElementCollectionViewPage"
Padding="0, 20, 0, 0"
Title="MediaElement in CollectionView">
<ScrollView>
<VerticalStackLayout Spacing="12">
<Label HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HorizontalOptions="Center"
VerticalOptions="Center"
Text="This page demonstrates that the MediaElement can be used inside of a DataTemplate"
Margin="12,0,12,0"/>
<CollectionView HeightRequest="850" ItemsSource="{Binding ItemSource}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="1"
SnapPointsAlignment="Center"
SnapPointsType="MandatorySingle" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="viewModels:MediaElementDataSource">
<Border
x:Name="CollectionViewBorder"
BackgroundColor="Black"
Padding="5">
<Border.StrokeShape>
<RoundRectangle CornerRadius="4" />
</Border.StrokeShape>
<VerticalStackLayout Spacing="6" HeightRequest="250">
<toolkit:MediaElement
HeightRequest="200"
x:Name="MediaElement"
ShouldAutoPlay="True"
ShouldShowPlaybackControls="True"
Source="{Binding Source, Mode=OneTime}" />
<Label TextColor="White"
HorizontalOptions="Center"
VerticalOptions="Center"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FontAttributes="Bold"
FontSize="18"
Text="{Binding Name, Mode=OneTime}"/>
</VerticalStackLayout>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ScrollView>
</pages:BasePage>

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

@ -0,0 +1,11 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
namespace CommunityToolkit.Maui.Sample.Pages.Views;
public partial class MediaElementCollectionViewPage : BasePage<MediaElementCollectionViewViewModel>
{
public MediaElementCollectionViewPage(MediaElementCollectionViewViewModel viewModel) : base(viewModel)
{
InitializeComponent();
}
}

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

@ -1,5 +1,4 @@
using System.ComponentModel;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Core.Primitives;
using CommunityToolkit.Maui.Sample.ViewModels.Views;
using CommunityToolkit.Maui.Views;
@ -27,7 +26,7 @@ public partial class MediaElementPage : BasePage<MediaElementViewModel>
void MediaElement_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == MediaElement.DurationProperty.PropertyName)
if (e.PropertyName == CommunityToolkit.Maui.Views.MediaElement.DurationProperty.PropertyName)
{
logger.LogInformation("Duration: {newDuration}", MediaElement.Duration);
PositionSlider.Maximum = MediaElement.Duration.TotalSeconds;
@ -238,7 +237,7 @@ public partial class MediaElementPage : BasePage<MediaElementViewModel>
void DisplayPopup(object sender, EventArgs e)
{
MediaElement.Pause();
MediaElement popupMediaElement = new MediaElement
var popupMediaElement = new Maui.Views.MediaElement
{
Source = MediaSource.FromResource("AppleVideo.mp4"),
HeightRequest = 600,
@ -250,12 +249,12 @@ public partial class MediaElementPage : BasePage<MediaElementViewModel>
{
VerticalOptions = LayoutAlignment.Center,
HorizontalOptions = LayoutAlignment.Center,
};
popup.Content = new StackLayout
{
Children =
Content = new StackLayout
{
popupMediaElement,
Children =
{
popupMediaElement,
}
}
};

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

@ -0,0 +1,14 @@
using System.Collections.ObjectModel;
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public partial class MediaElementCarouselViewViewModel : BaseViewModel
{
public ObservableCollection<MediaElementDataSource> ItemSource { get; } =
[
new(new Uri("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"), "Buck Bunny", 720, 1280),
new(new Uri("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"), "Elephants Dream", 720, 1280),
new(new Uri("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"), "Sintel", 546, 1280)
];
}
public record MediaElementDataSource(Uri Source, string Name, int VideoHeight, int VideoWidth);

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

@ -0,0 +1,12 @@
using System.Collections.ObjectModel;
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
public partial class MediaElementCollectionViewViewModel : BaseViewModel
{
public ObservableCollection<MediaElementDataSource> ItemSource { get; } =
[
new(new Uri("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"), "Buck Bunny", 720, 1280),
new(new Uri("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"), "Elephants Dream", 720, 1280),
new(new Uri("https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"), "Sintel", 546, 1280)
];
}

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

@ -21,9 +21,11 @@ public sealed class ViewsGalleryViewModel() : BaseGalleryViewModel(
SectionModel.Create<DrawingViewViewModel>("DrawingView", Colors.Red, "DrawingView provides a canvas for users to \"paint\" on the screen. The drawing can also be captured and displayed as an Image."),
SectionModel.Create<ExpanderViewModel>("Expander Page", Colors.Red, "Expander allows collapse and expand content."),
SectionModel.Create<BasicMapsViewModel>("Windows Maps Basic Page", Colors.Red, "A page demonstrating a basic example of .NET MAUI Maps for Windows."),
SectionModel.Create<MapsPinsViewModel>("Windows Maps Pins Page", Colors.Red, "A page demonstrating .NET MAUI Maps for Windows with Pins."),
SectionModel.Create<LazyViewViewModel>("LazyView", Colors.Red, "LazyView is a view that allows you to load its children in a delayed manner."),
SectionModel.Create<MapsPinsViewModel>("Windows Maps Pins Page", Colors.Red, "A page demonstrating .NET MAUI Maps for Windows with Pins."),
SectionModel.Create<MediaElementViewModel>("MediaElement", Colors.Red, "MediaElement is a view for playing video and audio"),
SectionModel.Create<MediaElementCarouselViewViewModel>("MediaElement in CarouselView", Colors.Red, "MediaElement can be used inside a DataTemplate"),
SectionModel.Create<MediaElementCollectionViewViewModel>("MediaElement in CollectionView", Colors.Red, "MediaElement can be used inside a DataTemplate"),
SectionModel.Create<MultiplePopupViewModel>("Multiple Popups Page", Colors.Red, "A page demonstrating multiple different Popups"),
SectionModel.Create<PopupPositionViewModel>("Custom Positioning Popup", Colors.Red, "Displays a basic popup anywhere on the screen using VerticalOptions and HorizontalOptions"),
SectionModel.Create<PopupAnchorViewModel>("Anchor Popup", Colors.Red, "Popups can be anchored to other view's on the screen"),

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

@ -10,10 +10,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" PrivateAssets="All" />
<PackageReference Include="coverlet.collector" Version="6.0.2" PrivateAssets="All" />

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

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Maui.Core.Extensions;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Handlers;
@ -56,6 +57,12 @@ public class MauiPopup : UIViewController
}
SetElementSize(new Size(View.Bounds.Width, View.Bounds.Height));
if (VirtualView is not null)
{
PopupExtensions.SetSize(this, VirtualView);
PopupExtensions.SetLayout(this, VirtualView);
}
}
/// <inheritdoc/>
@ -177,6 +184,11 @@ public class MauiPopup : UIViewController
view.Bounds = new CGRect(0, 0, PreferredContentSize.Width, PreferredContentSize.Height);
AddChildViewController(control.ViewController);
view.SafeTopAnchor().ConstraintEqualTo(control.ViewController.View.SafeTopAnchor()).Active = true;
view.SafeBottomAnchor().ConstraintEqualTo(control.ViewController.View.SafeBottomAnchor()).Active = true;
view.SafeLeadingAnchor().ConstraintEqualTo(control.ViewController.View.SafeLeadingAnchor()).Active = true;
view.SafeTrailingAnchor().ConstraintEqualTo(control.ViewController.View.SafeTrailingAnchor()).Active = true;
if (VirtualView is not null)
{
this.SetBackgroundColor(VirtualView);

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

@ -1,12 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using AVKit;
using CommunityToolkit.Maui.Core.Views;
using CommunityToolkit.Maui.Views;
using Microsoft.Maui.Controls.Handlers.Items;
using Microsoft.Maui.Handlers;
namespace CommunityToolkit.Maui.Core.Handlers;
public partial class MediaElementHandler : ViewHandler<MediaElement, MauiMediaElement>, IDisposable
{
AVPlayerViewController? playerViewController;
/// <inheritdoc/>
/// <exception cref="NullReferenceException">Thrown if <see cref="MauiContext"/> is <see langword="null"/>.</exception>
protected override MauiMediaElement CreatePlatformView()
@ -17,53 +22,79 @@ public partial class MediaElementHandler : ViewHandler<MediaElement, MauiMediaEl
}
mediaManager ??= new(MauiContext,
VirtualView,
Dispatcher.GetForCurrentThread() ?? throw new InvalidOperationException($"{nameof(IDispatcher)} cannot be null"));
VirtualView,
Dispatcher.GetForCurrentThread() ?? throw new InvalidOperationException($"{nameof(IDispatcher)} cannot be null"));
var (_, playerViewController) = mediaManager.CreatePlatformView();
(_, playerViewController) = mediaManager.CreatePlatformView();
if (VirtualView.TryFindParent<Page>(out var page))
if (VirtualView.TryFindParent<Page>(out var page)
&& page.Handler is PageHandler { ViewController: not null } pageHandler)
{
var parentViewController = (page.Handler as PageHandler)?.ViewController;
return new(playerViewController, parentViewController);
return new(playerViewController, pageHandler.ViewController);
}
// The top-most parent is null when MediaElement is placed in a DataTemplate because DataTemplates defer loading until they are about to be displayed on the screen
// Subscribe to ParentChanged and set the UIViewController once the DataTemplate's Parent has been set
VirtualView.GetTopMostParent().ParentChanged += HandleMediaElementParentChanged;
return new(playerViewController, null);
}
/// <inheritdoc/>
protected override void ConnectHandler(MauiMediaElement platformView)
{
base.ConnectHandler(platformView);
}
/// <inheritdoc/>
protected override void DisconnectHandler(MauiMediaElement platformView)
{
platformView.Dispose();
Dispose();
base.DisconnectHandler(platformView);
}
partial void PlatformDispose()
{
playerViewController?.Dispose();
playerViewController = null;
}
void HandleMediaElementParentChanged(object? sender, EventArgs e)
{
ArgumentNullException.ThrowIfNull(sender);
if (playerViewController is null)
{
throw new InvalidOperationException($"{nameof(playerViewController)} must be set in the {nameof(CreatePlatformView)} method");
}
if (VirtualView.TryFindParent<ItemsView>(out var itemsView) && itemsView.Handler is not null)
{
var parentViewController = itemsView.Handler switch
{
CarouselViewHandler carouselViewHandler => carouselViewHandler.ViewController ?? GetInternalController(carouselViewHandler),
CollectionViewHandler collectionViewHandler => collectionViewHandler.ViewController ?? GetInternalController(collectionViewHandler),
_ => throw new NotSupportedException($"{itemsView.Handler.GetType()} not yet supported")
};
PlatformView.UpdateParentViewController(playerViewController, parentViewController);
VirtualView.ParentChanged -= HandleMediaElementParentChanged;
}
// The Controller we need is a `protected internal` property in the ItemsViewContoller class: https://github.com/dotnet/maui/blob/cf002538cb73db4bf187a51e4786d7478a7025ee/src/Controls/src/Core/Handlers/Items/ItemsViewHandler.iOS.cs#L39
// In this method, we must use reflection to get the value of its backing field
static ItemsViewController<TItemsView> GetInternalController<TItemsView>(ItemsViewHandler<TItemsView> handler) where TItemsView : ItemsView
{
var nonPublicInstanceFields = typeof(ItemsViewHandler<TItemsView>).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
var controllerProperty = nonPublicInstanceFields.Single(x => x.FieldType == typeof(ItemsViewController<TItemsView>));
return (ItemsViewController<TItemsView>)(controllerProperty.GetValue(handler) ?? throw new InvalidOperationException($"Unable to get the value for the Controller property on {handler.GetType()}"));
}
}
}
static class ParentPage
{
/// <summary>
/// Extension method to find the Parent of <see cref="VisualElement"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="child"></param>
/// <param name="parent"></param>
/// <returns></returns>
public static bool TryFindParent<T>(this VisualElement? child, [NotNullWhen(true)] out T? parent) where T : VisualElement
{
while (true)
while (child is not null)
{
if (child is null)
{
parent = null;
return false;
}
if (child.Parent is T element)
{
parent = element;
@ -72,5 +103,18 @@ static class ParentPage
child = child.Parent as VisualElement;
}
parent = null;
return false;
}
public static Element GetTopMostParent(this Element child)
{
while (child.Parent is not null)
{
child = child.Parent;
}
return child;
}
}

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

@ -226,7 +226,10 @@ public partial class MediaElementHandler
{
mediaManager?.Dispose();
mediaManager = null;
PlatformDispose();
}
}
partial void PlatformDispose();
#endif
}

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

@ -40,4 +40,19 @@ public class MauiMediaElement : UIView
#endif
AddSubview(playerViewController.View);
}
/// <summary>
/// Adds PlayerViewController to the Parent ViewController
/// </summary>
/// <param name="playerViewController"></param>
/// <param name="parentViewController"></param>
public void UpdateParentViewController(AVPlayerViewController playerViewController, UIViewController parentViewController)
{
parentViewController.AddChildViewController(playerViewController);
if (playerViewController.View is not null)
{
AddSubview(playerViewController.View);
}
}
}

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

@ -9,9 +9,9 @@
<ItemGroup>
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" PrivateAssets="All" />
<PackageReference Include="coverlet.collector" Version="6.0.2" PrivateAssets="All" />

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

@ -0,0 +1,80 @@
using CommunityToolkit.Maui.Converters;
using Xunit;
namespace CommunityToolkit.Maui.UnitTests.Converters;
public class ColorToHexArgbStringConverterTests : BaseConverterTest<ColorToHexArgbStringConverter>
{
public static readonly IReadOnlyList<object[]> ValidInputData =
[
[int.MinValue, int.MinValue, int.MinValue, int.MinValue, "#00000000"],
[int.MaxValue, int.MinValue, int.MinValue, int.MinValue, "#FF000000"],
[int.MinValue, 0, 0, 0, "#00000000"],
[-0.5, 0, 0, 0, "#00000000"],
[0, 0, 0, 0, "#00000000"],
[0.5, 0, 0, 0, "#7F000000"],
[1, 0, 0, 0, "#FF000000"],
[int.MaxValue, 0, 0, 0, "#FF000000"],
[int.MinValue, int.MaxValue, int.MaxValue, int.MaxValue, "#00FFFFFF"],
[int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue, "#FFFFFFFF"],
[0, 1, 0, 0, "#00FF0000"],
[1, 1, 0, 0, "#FFFF0000"],
[0, 1, 1, 0, "#00FFFF00"],
[1, 1, 1, 0, "#FFFFFF00"],
[0, 0, 1, 0, "#0000FF00"],
[1, 0, 1, 0, "#FF00FF00"],
[0, 0, 1, 1, "#0000FFFF"],
[1, 0, 1, 1, "#FF00FFFF"],
[0, 1, 0, 1, "#00FF00FF"],
[1, 1, 0, 1, "#FFFF00FF"],
[0, 0, 0.5, 1, "#00007FFF"],
[0, 0, 0.5, 0, "#00007F00"],
[0.5, 0.5, 0.5, 1, "#7F7F7FFF"],
[0.5, 0.5, 0.5, 0, "#7F7F7F00"],
[0.25, 0.25, 0.25, 1, "#3F3F3FFF"],
[0.25, 0.25, 0.25, 0, "#3F3F3F00"],
[0.25, 1, 0.25, 1, "#3FFF3FFF"],
[0.25, 1, 0.25, 0, "#3FFF3F00"],
[0.75, 1, 0.25, 1, "#BFFF3FFF"],
[0.75, 1, 0.25, 0, "#BFFF3F00"],
[0.75, 0, 1, 1, "#BF00FFFF"],
[0.75, 0, 1, 0, "#BF00FF00"]
];
[Theory]
[MemberData(nameof(ValidInputData))]
public void ColorToHexArgbStringConverterValidInputTest(float alpha, float red, float green, float blue, string expectedResult)
{
var converter = new ColorToHexArgbStringConverter();
var color = new Color(red, green, blue, alpha);
var resultConvert = ((ICommunityToolkitValueConverter)converter).Convert(color, typeof(string), null, null);
var resultConvertFrom = converter.ConvertFrom(color);
Assert.Equal(expectedResult, resultConvert);
Assert.Equal(expectedResult, resultConvertFrom);
}
[Theory]
[MemberData(nameof(ValidInputData))]
public void ColorToHexArgbStringConverterConvertBackValidInputTest(float alpha, float red, float green, float blue, string expectedResult)
{
var converter = new ColorToHexArgbStringConverter();
var color = new Color(red, green, blue, alpha);
var resultConvert = ((ICommunityToolkitValueConverter)converter).ConvertBack(expectedResult, typeof(Color), null, null);
var resultConvertTo = converter.ConvertBackTo(expectedResult);
Assert.Equal(color, resultConvert);
Assert.Equal(color, resultConvertTo);
}
[Fact]
public void ColorToHexArgbStringConverterNullInputTest()
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
Assert.Throws<ArgumentNullException>(() => new ColorToHexArgbStringConverter().ConvertFrom(null));
Assert.Throws<ArgumentNullException>(() => ((ICommunityToolkitValueConverter)new ColorToHexArgbStringConverter()).Convert(null, typeof(string), null, null));
Assert.Throws<ArgumentNullException>(() => ((ICommunityToolkitValueConverter)new ColorToHexArgbStringConverter()).Convert(new Color(), null, null, null));
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}
}

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

@ -116,6 +116,32 @@ public class ColorToHexRgbaStringConverter : BaseConverter<Color, string>
}
}
/// <summary>
/// Converts the incoming value from <see cref="Color"/> and returns the object of a type <see cref="string"/> and vice-versa.
/// </summary>
public class ColorToHexArgbStringConverter : BaseConverter<Color, string>
{
/// <inheritdoc/>
public override string DefaultConvertReturnValue { get; set; } = string.Empty;
/// <inheritdoc/>
public override Color DefaultConvertBackReturnValue { get; set; } = Colors.Transparent;
/// <inheritdoc/>
public override string ConvertFrom(Color value, CultureInfo? culture = null)
{
ArgumentNullException.ThrowIfNull(value);
return value.ToArgbHex(true);
}
/// <inheritdoc/>
public override Color ConvertBackTo(string value, CultureInfo? culture = null)
{
ArgumentNullException.ThrowIfNull(value);
return Color.FromArgb(value);
}
}
/// <summary>
/// Converts the incoming value from <see cref="Color"/> and returns the object of a type <see cref="string"/>.
/// </summary>

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

@ -35,4 +35,29 @@ public partial class Popup
return (PageHandler)contentPage.ToHandler(mauiContext);
}
}
/// <summary>
/// Action that's triggered when the Popup is Closed.
/// </summary>
/// <param name="handler">An instance of <see cref="PopupHandler"/>.</param>
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
/// <param name="result">We don't need to provide the result parameter here.</param>
public static void MapOnClosed(PopupHandler handler, IPopup view, object? result)
{
var parent = view.Parent as Element;
if (parent is not null)
{
if (handler.VirtualView is Popup popup)
{
if (popup.Content is not null)
{
if (popup.Content.Parent is ContentPage contentPage)
{
parent.RemoveLogicalChild(contentPage);
}
}
parent.RemoveLogicalChild(popup);
}
}
}
}

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

@ -11,7 +11,8 @@ public partial class Popup
public static CommandMapper<IPopup, PopupHandler> ControlPopUpCommandMapper = new(PopupHandler.PopUpCommandMapper)
{
#if IOS || MACCATALYST
[nameof(IPopup.OnOpened)] = MapOnOpened
[nameof(IPopup.OnOpened)] = MapOnOpened,
[nameof(IPopup.OnClosed)] = MapOnClosed
#endif
};