diff --git a/UniSky.Services/Implementation/TypedSettingsService.cs b/UniSky.Services/Implementation/TypedSettingsService.cs index ab09c7b..d576bf2 100644 --- a/UniSky.Services/Implementation/TypedSettingsService.cs +++ b/UniSky.Services/Implementation/TypedSettingsService.cs @@ -73,6 +73,7 @@ public bool VideosInFeeds get => settings.Read(VIDEOS_IN_FEEDS, VIDEOS_IN_FEEDS_DEFAULT); set => settings.Save(VIDEOS_IN_FEEDS, value); } + public bool ShowFeedContext { get => settings.Read(SHOW_FEED_CONTEXT, SHOW_FEED_CONTEXT_DEFAULT); diff --git a/UniSky.Services/Overlay/IOverlayController.cs b/UniSky.Services/Overlay/IOverlayController.cs index 90e1da6..1c54ef6 100644 --- a/UniSky.Services/Overlay/IOverlayController.cs +++ b/UniSky.Services/Overlay/IOverlayController.cs @@ -3,10 +3,12 @@ namespace UniSky.Services; + public interface IOverlayController { UIElement Root { get; } bool IsStandalone { get; } ISafeAreaService SafeAreaService { get; } - Task TryHideSheetAsync(); + Task ShowAsync(object parameter); + Task TryHideAsync(); } diff --git a/UniSky/Controls/Compose/ComposeSheet.xaml.cs b/UniSky/Controls/Compose/ComposeSheet.xaml.cs index 0937783..10e94be 100644 --- a/UniSky/Controls/Compose/ComposeSheet.xaml.cs +++ b/UniSky/Controls/Compose/ComposeSheet.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using UniSky.Controls.Overlay; using UniSky.Controls.Sheet; @@ -9,9 +10,12 @@ using Windows.ApplicationModel.Resources; using Windows.Foundation; using Windows.Foundation.Metadata; +using Windows.System; +using Windows.UI.Core; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 @@ -38,6 +42,12 @@ public ComposeSheet() this.Shown += OnShown; this.Hiding += OnHiding; this.Hidden += OnHidden; + + if (ApiInformation.IsEventPresent(typeof(UIElement).FullName, "PreviewKeyDown")) + { + PrimaryTextBox.PreviewKeyDown += OnPreviewKeyDown; + } + this.strings = ResourceLoader.GetForCurrentView(); } @@ -136,5 +146,23 @@ private void PrimaryTextBox_Paste(object sender, TextControlPasteEventArgs e) { e.Handled = ViewModel.HandlePaste(); } + + private async void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + var coreWindow = CoreWindow.GetForCurrentThread(); + if (e.Key == VirtualKey.Enter) + { + var state = coreWindow.GetAsyncKeyState(VirtualKey.Control); + if (state.HasFlag(CoreVirtualKeyStates.Down)) + { + if (!ViewModel.CanPost) + return; + + e.Handled = true; + + await ViewModel.PostAsync(); + } + } + } } } diff --git a/UniSky/Controls/Gallery/GalleryControl.xaml b/UniSky/Controls/Gallery/GalleryControl.xaml index 84bb260..2f168e5 100644 --- a/UniSky/Controls/Gallery/GalleryControl.xaml +++ b/UniSky/Controls/Gallery/GalleryControl.xaml @@ -15,7 +15,8 @@ + SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" + Background="Transparent"> - + + + + + diff --git a/UniSky/Controls/Gallery/GalleryControl.xaml.cs b/UniSky/Controls/Gallery/GalleryControl.xaml.cs index 6763acf..12507b1 100644 --- a/UniSky/Controls/Gallery/GalleryControl.xaml.cs +++ b/UniSky/Controls/Gallery/GalleryControl.xaml.cs @@ -1,8 +1,14 @@ using System; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Uwp.UI.Extensions; using UniSky.Controls.Overlay; using UniSky.Services; using UniSky.ViewModels.Gallery; +using Windows.Foundation.Metadata; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Animation; +using Windows.UI.Xaml.Media.Imaging; namespace UniSky.Controls.Gallery; @@ -22,4 +28,35 @@ protected override void OnShowing(OverlayShowingEventArgs args) DataContext = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, gallery); } + + protected override void OnShown(RoutedEventArgs args) + { + base.OnShown(args); + } + + private void MainImage_Loaded(object sender, RoutedEventArgs e) + { + if (Controller.IsStandalone) + return; + + var vm = (GalleryViewModel)DataContext; + var selected = vm.Images[vm.SelectedIndex]; + var source = (BitmapImage)sender; + + if (source.UriSource.AbsolutePath != new Uri(selected.ImageUrl).AbsolutePath) + return; + + var container = FlippyView.ContainerFromIndex(vm.SelectedIndex); + var mainImage = container.FindDescendantByName("MainImage"); + + var animation = ConnectedAnimationService.GetForCurrentView() + .GetAnimation("GalleryView"); + + if (animation != null) + { + if (ApiInformation.IsTypePresent(typeof(DirectConnectedAnimationConfiguration).FullName)) + animation.Configuration = new DirectConnectedAnimationConfiguration(); + animation.TryStart(mainImage); + } + } } diff --git a/UniSky/Controls/Overlay/OverlayRootControl.xaml b/UniSky/Controls/Overlay/OverlayRootControl.xaml new file mode 100644 index 0000000..9fbd95b --- /dev/null +++ b/UniSky/Controls/Overlay/OverlayRootControl.xaml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UniSky/Controls/Overlay/OverlayRootControl.xaml.cs b/UniSky/Controls/Overlay/OverlayRootControl.xaml.cs new file mode 100644 index 0000000..ad8bdf1 --- /dev/null +++ b/UniSky/Controls/Overlay/OverlayRootControl.xaml.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Microsoft.Extensions.DependencyInjection; +using System.Threading.Tasks; +using UniSky.Services; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +using System.Threading; +using Microsoft.Toolkit.Uwp.UI; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace UniSky.Controls.Overlay +{ + public sealed partial class OverlayRootControl : UserControl, IOverlayRootControl + { + private IOverlayController _controller; + + public OverlayRootControl() + { + this.InitializeComponent(); + + VisualStateManager.GoToState(this, "Closed", false); + + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); + safeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; + } + + private void ShowOverlay(IOverlayController controller, IOverlayControl control, object parameter) + { + if (_controller != null) + { + throw new InvalidOperationException("Attempting to show two overlays at once!"); + } + + _controller = controller; + + SheetRoot.Child = (FrameworkElement)control; + control.InvokeShowing(parameter); + + VisualStateManager.GoToState(this, "Open", true); + + var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); + systemNavigationManager.BackRequested += OnBackRequested; + } + + private Task HideOverlayAsync() + { + VisualStateManager.GoToState(this, "Closed", true); + + var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); + systemNavigationManager.BackRequested -= OnBackRequested; + + _controller = null; + + return Task.FromResult(true); + } + + private async void OnBackRequested(object sender, BackRequestedEventArgs e) + { + if (this._controller == null) return; + + e.Handled = true; + await _controller.TryHideAsync(); + } + + private void OnSafeAreaUpdated(object sender, SafeAreaUpdatedEventArgs e) + { + HostControl.Margin = e.SafeArea.Bounds; + } + + private async void PrimaryTitleBarButton_Click(object sender, RoutedEventArgs e) + { + if (this._controller == null) return; + await _controller.TryHideAsync(); + } + + private void ShowOverlayStoryboard_Completed(object sender, object e) + { + if (SheetRoot.Child is IOverlayControl control) + { + control.InvokeShown(); + } + } + + private void HideOverlayStoryboard_Completed(object sender, object e) + { + if (SheetRoot.Child is IOverlayControl control) + { + control.InvokeHidden(); + SheetRoot.Child = null; + } + } + + Task IOverlayRootControl.ShowAsync(IOverlayController controller, IOverlayControl control, object param) + { + ShowOverlay(controller, control, param); + return Task.CompletedTask; + } + + Task IOverlayRootControl.HideAsync() + { + return HideOverlayAsync(); + } + } +} diff --git a/UniSky/Controls/Overlay/StandardOverlayControl.cs b/UniSky/Controls/Overlay/StandardOverlayControl.cs index 7db485b..17d31b4 100644 --- a/UniSky/Controls/Overlay/StandardOverlayControl.cs +++ b/UniSky/Controls/Overlay/StandardOverlayControl.cs @@ -1,4 +1,5 @@ -using Microsoft.Toolkit.Uwp.UI.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Toolkit.Uwp.UI.Extensions; using UniSky.Services; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -12,9 +13,10 @@ public StandardOverlayControl() this.DefaultStyleKey = typeof(StandardOverlayControl); } - protected override void OnShown(RoutedEventArgs args) + protected override void OnShowing(OverlayShowingEventArgs args) { - base.OnShown(args); + base.OnShowing(args); + this.ApplyTemplate(); if (Controller.IsStandalone) { @@ -24,9 +26,13 @@ protected override void OnShown(RoutedEventArgs args) { VisualStateManager.GoToState(this, "Standard", false); } + } - var TitleBarDragArea = this.FindDescendantByName("TitleBarDragArea"); - Controller.SafeAreaService.SetTitleBar(TitleBarDragArea); + protected override void OnShown(RoutedEventArgs args) + { + base.OnShown(args); + //var TitleBarDragArea = this.FindDescendantByName("TitleBarDragArea"); + //Controller.SafeAreaService.SetTitleBar(TitleBarDragArea); Controller.SafeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; } diff --git a/UniSky/Controls/Overlay/StandardOverlayControl.xaml b/UniSky/Controls/Overlay/StandardOverlayControl.xaml index 36f7314..84827a5 100644 --- a/UniSky/Controls/Overlay/StandardOverlayControl.xaml +++ b/UniSky/Controls/Overlay/StandardOverlayControl.xaml @@ -8,20 +8,18 @@ - - - - - - + + + + + - + - - - + - - - + - + diff --git a/UniSky/Controls/Sheet/SheetControl.cs b/UniSky/Controls/Sheet/SheetControl.cs index a665be8..8e68de1 100644 --- a/UniSky/Controls/Sheet/SheetControl.cs +++ b/UniSky/Controls/Sheet/SheetControl.cs @@ -108,8 +108,8 @@ public SheetControl() this.DefaultStyleKey = typeof(SheetControl); // default hide - this.PrimaryButtonCommand = new AsyncRelayCommand(() => Controller?.TryHideSheetAsync()); - this.SecondaryButtonCommand = new AsyncRelayCommand(() => Controller?.TryHideSheetAsync()); + this.PrimaryButtonCommand = new AsyncRelayCommand(() => Controller?.TryHideAsync()); + this.SecondaryButtonCommand = new AsyncRelayCommand(() => Controller?.TryHideAsync()); } protected override void OnHidden(RoutedEventArgs args) @@ -129,9 +129,9 @@ protected override void OnApplyTemplate() if (Controller != null && Controller.IsStandalone) { VisualStateManager.GoToState(this, "FullWindow", false); - var titleBarDragArea = this.FindDescendantByName("TitleBarDragArea"); + //var titleBarDragArea = this.FindDescendantByName("TitleBarDragArea"); Controller.SafeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; - Controller.SafeAreaService.SetTitleBar(titleBarDragArea); + //Controller.SafeAreaService.SetTitleBar(titleBarDragArea); this.SizeChanged += OnSizeChanged; } diff --git a/UniSky/Controls/Sheet/SheetRootControl.xaml b/UniSky/Controls/Sheet/SheetRootControl.xaml index 18f9472..bd5ee8e 100644 --- a/UniSky/Controls/Sheet/SheetRootControl.xaml +++ b/UniSky/Controls/Sheet/SheetRootControl.xaml @@ -30,15 +30,15 @@ - - + + - - + + + ui:Effects.Shadow="{StaticResource CommonShadow}" + TabFocusNavigation="Cycle"> diff --git a/UniSky/Controls/Sheet/SheetRootControl.xaml.cs b/UniSky/Controls/Sheet/SheetRootControl.xaml.cs index 8eb8546..b152d59 100644 --- a/UniSky/Controls/Sheet/SheetRootControl.xaml.cs +++ b/UniSky/Controls/Sheet/SheetRootControl.xaml.cs @@ -5,18 +5,18 @@ using Microsoft.Toolkit.Uwp.UI; using UniSky.Services; using Windows.Foundation; +using Windows.Foundation.Metadata; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Markup; using MUXC = Microsoft.UI.Xaml.Controls; -// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 - namespace UniSky.Controls.Sheet { [ContentProperty(Name = nameof(ContentElement))] - public sealed partial class SheetRootControl : UserControl + public sealed partial class SheetRootControl : UserControl, IOverlayRootControl { public FrameworkElement ContentElement { @@ -37,7 +37,9 @@ public double TotalHeight public static readonly DependencyProperty TotalHeightProperty = DependencyProperty.Register("TotalHeight", typeof(double), typeof(SheetRootControl), new PropertyMetadata(0.0)); - private SemaphoreSlim _hideSemaphore = new SemaphoreSlim(1, 1); + private IOverlayController _controller; + private TaskCompletionSource _showTcs; + private TaskCompletionSource _hideTcs; public SheetRootControl() { @@ -61,8 +63,16 @@ protected override Size ArrangeOverride(Size finalSize) return base.ArrangeOverride(finalSize); } - internal void ShowSheet(ISheetControl control, object parameter) + + private void ShowSheet(IOverlayController controller, IOverlayControl control, object parameter) { + if (_controller != null) + { + throw new InvalidOperationException("Attempting to show two sheets at once!"); + } + + _controller = controller; + SheetRoot.Child = (FrameworkElement)control; control.InvokeShowing(parameter); @@ -75,39 +85,22 @@ internal void ShowSheet(ISheetControl control, object parameter) systemNavigationManager.BackRequested += OnBackRequested; } - private async void OnBackRequested(object sender, BackRequestedEventArgs e) + private bool HideSheet() { - e.Handled = true; - await HideSheetAsync(); - } - - internal async Task HideSheetAsync() - { - if (!await _hideSemaphore.WaitAsync(100)) + if (_controller == null) return false; - try - { - if (SheetRoot.Child is ISheetControl control) - { - if (!await control.InvokeHidingAsync()) - return false; - } + VisualStateManager.GoToState(this, "Closed", true); - VisualStateManager.GoToState(this, "Closed", true); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); + safeAreaService.SafeAreaUpdated -= OnSafeAreaUpdated; - var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); - safeAreaService.SafeAreaUpdated -= OnSafeAreaUpdated; + var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); + systemNavigationManager.BackRequested -= OnBackRequested; - var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); - systemNavigationManager.BackRequested -= OnBackRequested; + _controller = null; - return true; - } - finally - { - _hideSemaphore.Release(); - } + return true; } private void OnSafeAreaUpdated(object sender, SafeAreaUpdatedEventArgs e) @@ -126,27 +119,50 @@ private void OnSafeAreaUpdated(object sender, SafeAreaUpdatedEventArgs e) } } + private async void OnBackRequested(object sender, BackRequestedEventArgs e) + { + if (this._controller == null) return; + + e.Handled = true; + await this._controller.TryHideAsync(); + } + private async void RefreshContainer_RefreshRequested(MUXC.RefreshContainer sender, MUXC.RefreshRequestedEventArgs args) { + if (this._controller == null) return; + var deferral = args.GetDeferral(); - await HideSheetAsync(); + await this._controller.TryHideAsync(); deferral.Complete(); } - private void ShowSheetStoryboard_Completed(object sender, object e) + private async void ShowSheetStoryboard_Completed(object sender, object e) { - if (SheetRoot.Child is ISheetControl control) + var elementToFocus = FocusManager.FindFirstFocusableElement(SheetRoot.Child); + if (ApiInformation.IsMethodPresent(typeof(FocusManager).FullName, "TryFocusAsync") + && elementToFocus is DependencyObject dep) + { + await FocusManager.TryFocusAsync(dep, FocusState.Programmatic); + } + else if (elementToFocus is Control controlToFocus) + { + controlToFocus.Focus(FocusState.Programmatic); + } + + if (SheetRoot.Child is IOverlayControl control) { control.InvokeShown(); } CommonShadow.CastTo = CompositionBackdropContainer; Effects.SetShadow(SheetBorder, CommonShadow); + + _showTcs.SetResult(null); } private void HideSheetStoryboard_Completed(object sender, object e) { - if (SheetRoot.Child is ISheetControl control) + if (SheetRoot.Child is IOverlayControl control) { control.InvokeHidden(); SheetRoot.Child = null; @@ -154,5 +170,21 @@ private void HideSheetStoryboard_Completed(object sender, object e) Effects.SetShadow(SheetBorder, null); } + + async Task IOverlayRootControl.ShowAsync(IOverlayController controller, IOverlayControl control, object param) + { + _hideTcs?.TrySetResult(false); + + _showTcs = new TaskCompletionSource(); + ShowSheet(controller, control, param); + await _showTcs.Task; + } + + Task IOverlayRootControl.HideAsync() + { + _showTcs?.TrySetResult(null); + + return Task.FromResult(HideSheet()); + } } } diff --git a/UniSky/Controls/VideoPlayer/VideoPlayerOverlay.xaml.cs b/UniSky/Controls/VideoPlayer/VideoPlayerOverlay.xaml.cs index 1c135fe..8a31732 100644 --- a/UniSky/Controls/VideoPlayer/VideoPlayerOverlay.xaml.cs +++ b/UniSky/Controls/VideoPlayer/VideoPlayerOverlay.xaml.cs @@ -10,12 +10,14 @@ using UniSky.ViewModels.VideoPlayer; using Windows.Foundation; using Windows.Foundation.Collections; +using Windows.Foundation.Metadata; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Navigation; namespace UniSky.Controls.VideoPlayer; @@ -35,6 +37,16 @@ protected override void OnShowing(OverlayShowingEventArgs args) throw new InvalidOperationException("Must specify gallery arguments"); DataContext = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, video); + + var animation = ConnectedAnimationService.GetForCurrentView() + .GetAnimation("VideoPlayerView"); + + if (animation != null) + { + if (ApiInformation.IsTypePresent(typeof(DirectConnectedAnimationConfiguration).FullName)) + animation.Configuration = new DirectConnectedAnimationConfiguration(); + animation.TryStart(MediaPlayer); + } } protected override void OnHidden(RoutedEventArgs args) diff --git a/UniSky/DataTemplates/FeedTemplates.xaml b/UniSky/DataTemplates/FeedTemplates.xaml index 63dfd77..c7b2500 100644 --- a/UniSky/DataTemplates/FeedTemplates.xaml +++ b/UniSky/DataTemplates/FeedTemplates.xaml @@ -374,7 +374,8 @@ Padding="0" Style="{ThemeResource CleanButtonStyle}" Command="{Binding ShowImageGalleryCommand}" - CommandParameter="{Binding Image1}"> + CommandParameter="{Binding ElementName=PART_Image1}" + Tag="{Binding Image1}"> + CommandParameter="{Binding ElementName=PART_Image2}" + Tag="{Binding Image2}"> + CommandParameter="{Binding ElementName=PART_Image3}" + Tag="{Binding Image3}"> + CommandParameter="{Binding ElementName=PART_Image4}" + Tag="{Binding Image4}"> + Command="{Binding ShowVideoPlayerCommand}" + CommandParameter="{Binding ElementName=ImageElement}"> + + + diff --git a/UniSky/RootPage.xaml b/UniSky/RootPage.xaml index 1b0c038..f4400a6 100644 --- a/UniSky/RootPage.xaml +++ b/UniSky/RootPage.xaml @@ -7,14 +7,15 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewmodels="using:UniSky.ViewModels" xmlns:pages="using:UniSky.Pages" - xmlns:sheet="using:UniSky.Controls.Sheet" + xmlns:sheet="using:UniSky.Controls.Sheet" xmlns:overlay="using:UniSky.Controls.Overlay" mc:Ignorable="d" NavigationCacheMode="Required" Background="{ThemeResource ApplicationRootPageBackgroundBrush}"> + - + + + diff --git a/UniSky/Services/Overlay/AppWindowOverlayController.cs b/UniSky/Services/Overlay/AppWindowOverlayController.cs index 7e77c67..e62f6dd 100644 --- a/UniSky/Services/Overlay/AppWindowOverlayController.cs +++ b/UniSky/Services/Overlay/AppWindowOverlayController.cs @@ -8,6 +8,7 @@ using Windows.UI.ViewManagement; using Windows.UI.WindowManagement; using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; namespace UniSky.Services.Overlay; @@ -16,6 +17,7 @@ internal class AppWindowOverlayController : IOverlayController private readonly AppWindow appWindow; private readonly IOverlayControl control; private readonly ISettingsService settingsService; + private readonly IOverlaySizeProvider overlaySizeProvider; private readonly string settingsKey; private readonly long titlePropertyChangedRef; @@ -28,23 +30,15 @@ public AppWindowOverlayController(AppWindow window, IOverlayControl control, IOv this.control = control; this.settingsService = ServiceContainer.Scoped.GetRequiredService(); this.SafeAreaService = new AppWindowSafeAreaService(appWindow); - this.settingsKey = "CoreWindow_LastSize_" + control.GetType().FullName.Replace(".", "_"); - var initialSize = control.PreferredWindowSize; - if (overlaySizeProvider != null) - { - var size = overlaySizeProvider.GetDesiredSize(); - if (size != null) - initialSize = size.Value; - } + this.overlaySizeProvider = overlaySizeProvider; + + this.control.SetOverlayController(this); appWindow.PersistedStateId = settingsKey; appWindow.CloseRequested += OnCloseRequested; appWindow.Closed += OnClosed; appWindow.Changed += OnChanged; - appWindow.RequestSize(initialSize); - - PlaceAppWindow(initialSize); this.titlePropertyChangedRef = this.Control.RegisterPropertyChangedCallback(OverlayControl.TitleContentProperty, OnTitleChanged); OnTitleChanged(Control, OverlayControl.TitleContentProperty); @@ -54,7 +48,33 @@ public AppWindowOverlayController(AppWindow window, IOverlayControl control, IOv public bool IsStandalone => true; public ISafeAreaService SafeAreaService { get; } - public async Task TryHideSheetAsync() + public async Task ShowAsync(object parameter) + { + control.InvokeShowing(parameter); + + ElementCompositionPreview.SetAppWindowContent(appWindow, (UIElement)control); + + var initialSize = control.PreferredWindowSize; + if (overlaySizeProvider != null) + { + var size = overlaySizeProvider.GetDesiredSize(); + if (size != null) + initialSize = size.Value; + } + + appWindow.RequestSize(initialSize); + + PlaceAppWindow(initialSize); + + if (!await appWindow.TryShowAsync()) + { + throw new InvalidOperationException("Failed to create app window! Falling back!"); + } + + control.InvokeShown(); + } + + public async Task TryHideAsync() { if (await control.InvokeHidingAsync()) { @@ -143,7 +163,7 @@ private void PlaceAppWindow(Size initialSize) Math.Max(offsetFromLeftEdge, offsetFromRightEdge) < ((displaySize.Width / 3.0) * 1.0)) // not enough space { var windowCenter = new Point( - (visibleBounds.X - currentRegion.WorkAreaOffset.X + visibleBounds.Width / 2.0) + 21, + (visibleBounds.X - currentRegion.WorkAreaOffset.X + visibleBounds.Width / 2.0) + 21, visibleBounds.Y - currentRegion.WorkAreaOffset.Y + visibleBounds.Height / 2.0); var windowSize = new Size(visibleBounds.Width, visibleBounds.Height); diff --git a/UniSky/Services/Overlay/ApplicationViewOverlayController.cs b/UniSky/Services/Overlay/ApplicationViewOverlayController.cs index 74d5675..c8592fb 100644 --- a/UniSky/Services/Overlay/ApplicationViewOverlayController.cs +++ b/UniSky/Services/Overlay/ApplicationViewOverlayController.cs @@ -38,6 +38,8 @@ public ApplicationViewOverlayController(IOverlayControl control, this.settingsService = ServiceContainer.Scoped.GetRequiredService(); this.settingsKey = "CoreWindow_LastSize_" + control.GetType().FullName.Replace(".", "_"); + this.control.SetOverlayController(this); + // a surprise tool that'll help us later // (instanciating this now so it handles min. window sizes, etc.) this.SafeAreaService = ServiceContainer.Scoped.GetRequiredService(); @@ -101,7 +103,16 @@ private void OnActivated(CoreWindow sender, WindowActivatedEventArgs args) public bool IsStandalone => true; public ISafeAreaService SafeAreaService { get; } - public async Task TryHideSheetAsync() + public Task ShowAsync(object parameter) + { + control.InvokeShowing(parameter); + Window.Current.Activate(); + control.InvokeShown(); + + return Task.CompletedTask; + } + + public async Task TryHideAsync() { if (await control.InvokeHidingAsync()) { @@ -116,7 +127,7 @@ public async Task TryHideSheetAsync() private async void OnBackRequested(object sender, BackRequestedEventArgs e) { e.Handled = true; - await TryHideSheetAsync(); + await TryHideAsync(); } private async void OnCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e) diff --git a/UniSky/Services/Overlay/OverlayRootController.cs b/UniSky/Services/Overlay/OverlayRootController.cs new file mode 100644 index 0000000..31ea2d2 --- /dev/null +++ b/UniSky/Services/Overlay/OverlayRootController.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using UniSky.Controls.Sheet; +using Windows.Foundation.Metadata; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; + +namespace UniSky.Services; + +interface IOverlayRootControl +{ + Task ShowAsync(IOverlayController controller, IOverlayControl control, object param); + Task HideAsync(); +} + +internal class OverlayRootController : IOverlayController +{ + private readonly IOverlayControl control; + private readonly IOverlayRootControl rootControl; + private readonly ISafeAreaService safeAreaService; + private readonly SemaphoreSlim hideSemaphore = new SemaphoreSlim(1, 1); + + private object previousFocus = null; + + public OverlayRootController(IOverlayControl control, + IOverlayRootControl rootControl, + ISafeAreaService safeAreaService) + { + this.control = control; + this.rootControl = rootControl; + this.safeAreaService = safeAreaService; + + this.control.SetOverlayController(this); + } + + public UIElement Root => (UIElement)rootControl; + public bool IsStandalone => false; + public ISafeAreaService SafeAreaService => safeAreaService; + + public async Task ShowAsync(object param) + { + previousFocus = FocusManager.GetFocusedElement(); + + await rootControl.ShowAsync(this, control, param); + } + + public async Task TryHideAsync() + { + if (!await hideSemaphore.WaitAsync(100)) + return false; + + try + { + if (!await control.InvokeHidingAsync()) + return false; + + var retVal = await rootControl.HideAsync(); + if (retVal) + { + if (ApiInformation.IsMethodPresent(typeof(FocusManager).FullName, "TryFocusAsync") + && previousFocus is DependencyObject dep) + { + await FocusManager.TryFocusAsync(dep, FocusState.Programmatic); + } + else if (previousFocus is Control control) + { + control.Focus(FocusState.Programmatic); + } + } + + return retVal; + } + finally + { + hideSemaphore.Release(); + } + } +} diff --git a/UniSky/Services/Overlay/OverlayService.cs b/UniSky/Services/Overlay/OverlayService.cs index 84d1b13..ffdd268 100644 --- a/UniSky/Services/Overlay/OverlayService.cs +++ b/UniSky/Services/Overlay/OverlayService.cs @@ -1,7 +1,11 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using UniSky.Controls.Overlay; +using UniSky.Controls.Sheet; using Windows.ApplicationModel.Core; using Windows.Foundation; using Windows.Foundation.Metadata; @@ -43,15 +47,7 @@ protected async Task ShowOverlayForAppWindow(Func fact throw new InvalidOperationException("Failed to create app window! Falling back!"); var controller = new AppWindowOverlayController(appWindow, control, parameter as IOverlaySizeProvider); - control.SetOverlayController(controller); - control.InvokeShowing(parameter); - - ElementCompositionPreview.SetAppWindowContent(appWindow, control); - - await appWindow.TryShowAsync(); - - control.InvokeShown(); - + await controller.ShowAsync(parameter); return controller; } @@ -62,18 +58,13 @@ protected async Task ShowOverlayForCoreWindow(Func fac var currentViewId = ApplicationView.GetForCurrentView().Id; var view = CoreApplication.CreateNewView(); var newViewId = 0; - await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => { newViewId = ApplicationView.GetForCurrentView().Id; var control = factory(); controller = new ApplicationViewOverlayController(control, currentViewId, newViewId, parameter as IOverlaySizeProvider); - control.SetOverlayController(controller); - - Window.Current.Activate(); - - control.InvokeShowing(parameter); - control.InvokeShown(); + await controller.ShowAsync(parameter); }); await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId, ViewSizePreference.UseMinimum); diff --git a/UniSky/Services/Overlay/StandardOverlayService.cs b/UniSky/Services/Overlay/StandardOverlayService.cs index 9aa736a..2b826d7 100644 --- a/UniSky/Services/Overlay/StandardOverlayService.cs +++ b/UniSky/Services/Overlay/StandardOverlayService.cs @@ -1,6 +1,8 @@ using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.UI.Extensions; using UniSky.Controls.Overlay; using Windows.Foundation; +using Windows.UI.Xaml; namespace UniSky.Services.Overlay; @@ -14,10 +16,19 @@ public interface IStandardOverlayService Task ShowAsync(object parameter = null) where T : StandardOverlayControl, new(); } -internal class StandardOverlayService : OverlayService, IStandardOverlayService +internal class StandardOverlayService(ITypedSettings settingsService, ISafeAreaService safeAreaService) : OverlayService, IStandardOverlayService { - public Task ShowAsync(object parameter = null) where T : StandardOverlayControl, new() + private readonly OverlayRootControl overlayRoot + = Window.Current.Content.FindDescendant(); + + public async Task ShowAsync(object parameter = null) where T : StandardOverlayControl, new() { - return base.ShowOverlayForWindow(() => new T(), parameter); + if (overlayRoot == null || settingsService.UseMultipleWindows) + return await base.ShowOverlayForWindow(() => new T(), parameter); + + var control = new T(); + var controller = new OverlayRootController(control, overlayRoot, safeAreaService); + await controller.ShowAsync(parameter); + return controller; } } diff --git a/UniSky/Services/Sheet/SheetRootController.cs b/UniSky/Services/Sheet/SheetRootController.cs deleted file mode 100644 index ad2af48..0000000 --- a/UniSky/Services/Sheet/SheetRootController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading.Tasks; -using UniSky.Controls.Sheet; -using Windows.UI.Xaml; - -namespace UniSky.Services; - -internal class SheetRootController(SheetRootControl rootControl, - ISafeAreaService safeAreaService) : IOverlayController -{ - public UIElement Root => rootControl; - public bool IsStandalone => false; - public ISafeAreaService SafeAreaService => safeAreaService; - - public async Task TryHideSheetAsync() - { - return await rootControl.HideSheetAsync(); - } -} diff --git a/UniSky/Services/Sheet/SheetService.cs b/UniSky/Services/Sheet/SheetService.cs index ea8a24d..2e26bd1 100644 --- a/UniSky/Services/Sheet/SheetService.cs +++ b/UniSky/Services/Sheet/SheetService.cs @@ -20,11 +20,8 @@ public async Task ShowAsync(Func factory, object param return await ShowOverlayForWindow(factory, parameter); var control = factory(); - var controller = new SheetRootController(sheetRoot, safeAreaService); - - control.SetOverlayController(controller); - - sheetRoot.ShowSheet(control, parameter); + var controller = new OverlayRootController(control, sheetRoot, safeAreaService); + await controller.ShowAsync(parameter); return controller; } } diff --git a/UniSky/UniSky.csproj b/UniSky/UniSky.csproj index 22da17b..8aa8f85 100644 --- a/UniSky/UniSky.csproj +++ b/UniSky/UniSky.csproj @@ -168,6 +168,9 @@ GalleryControl.xaml + + OverlayRootControl.xaml + PreviewPaneAuroraControl.xaml @@ -287,7 +290,7 @@ - + @@ -495,6 +498,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/UniSky/ViewModels/Compose/ComposeViewModel.cs b/UniSky/ViewModels/Compose/ComposeViewModel.cs index 16d6a2d..82ef2d8 100644 --- a/UniSky/ViewModels/Compose/ComposeViewModel.cs +++ b/UniSky/ViewModels/Compose/ComposeViewModel.cs @@ -165,7 +165,7 @@ private async Task LoadAsync() } [RelayCommand] - private async Task PostAsync() + public async Task PostAsync() { this.SetErrored(null); using var ctx = this.GetLoadingContext(); @@ -283,7 +283,7 @@ private async Task UploadImageEmbedAsync() [RelayCommand] private async Task Hide() { - await this.SheetController.TryHideSheetAsync(); + await this.SheetController.TryHideAsync(); } [RelayCommand] diff --git a/UniSky/ViewModels/Gallery/GalleryViewModel.cs b/UniSky/ViewModels/Gallery/GalleryViewModel.cs index 189af9c..3138894 100644 --- a/UniSky/ViewModels/Gallery/GalleryViewModel.cs +++ b/UniSky/ViewModels/Gallery/GalleryViewModel.cs @@ -53,7 +53,7 @@ public GalleryImageViewModel(ViewImage image) public GalleryImageViewModel(ATIdentifier id, Image image) { - // TODO: this + // TODO: this ImageUrl = $"https://cdn.bsky.app/img/feed_fullsize/plain/{id}/{image.ImageValue.Ref.Link}@jpeg"; } } diff --git a/UniSky/ViewModels/Posts/PostEmbedImagesViewModel.cs b/UniSky/ViewModels/Posts/PostEmbedImagesViewModel.cs index 4d4eee8..95518ce 100644 --- a/UniSky/ViewModels/Posts/PostEmbedImagesViewModel.cs +++ b/UniSky/ViewModels/Posts/PostEmbedImagesViewModel.cs @@ -12,6 +12,8 @@ using UniSky.Services; using UniSky.Services.Overlay; using UniSky.ViewModels.Gallery; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Animation; namespace UniSky.ViewModels.Posts; @@ -100,10 +102,22 @@ private void SetAspectRatio(AspectRatio firstRatio) [RelayCommand] private async Task ShowImageGalleryAsync(object parameter) { + var settingsService = ServiceContainer.Scoped.GetRequiredService(); + if (parameter is Control control) + parameter = control.Tag; + else + control = null; + var idx = Array.IndexOf(Images, parameter); if (idx == -1) idx = 0; + if (control != null && !settingsService.UseMultipleWindows) + { + ConnectedAnimationService.GetForCurrentView() + .PrepareToAnimate("GalleryView", control); + } + var genericOverlay = ServiceContainer.Scoped.GetRequiredService(); await genericOverlay.ShowAsync(new ShowGalleryArgs(id, embedView, embed, idx)); } diff --git a/UniSky/ViewModels/Posts/PostEmbedVideoViewModel.cs b/UniSky/ViewModels/Posts/PostEmbedVideoViewModel.cs index c46d203..4a344b0 100644 --- a/UniSky/ViewModels/Posts/PostEmbedVideoViewModel.cs +++ b/UniSky/ViewModels/Posts/PostEmbedVideoViewModel.cs @@ -12,6 +12,9 @@ using Windows.Media.Core; using Windows.Media.Playback; using Windows.Media.Streaming.Adaptive; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Animation; namespace UniSky.ViewModels.Posts; @@ -49,6 +52,13 @@ private async Task LoadAsync() [RelayCommand] private async Task ShowVideoPlayerAsync(object parameter) { + var settingsService = ServiceContainer.Scoped.GetRequiredService(); + if (parameter is UIElement control && !settingsService.UseMultipleWindows) + { + ConnectedAnimationService.GetForCurrentView() + .PrepareToAnimate("VideoPlayerView", control); + } + var genericOverlay = ServiceContainer.Scoped.GetRequiredService(); await genericOverlay.ShowAsync(new ShowVideoPlayerArgs(video)); }