diff --git a/UniSky/App.xaml b/UniSky/App.xaml index f3418e3..ad310bd 100644 --- a/UniSky/App.xaml +++ b/UniSky/App.xaml @@ -4,7 +4,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:UniSky" xmlns:controls="using:Microsoft.UI.Xaml.Controls" - xmlns:media="using:Microsoft.UI.Xaml.Media"> + xmlns:media="using:Microsoft.UI.Xaml.Media" + xmlns:sheets="using:UniSky.Controls.Sheet"> @@ -14,18 +15,35 @@ + - + + + + + - + #10FFFFFF + #FF404040 + + - + #FFDEDEDE + #10000000 + + @@ -37,11 +55,18 @@ 64 38--> - 0,1,0,0 + 1 0,0,0,0 14 - 14 + 14 + + + + + + + \ No newline at end of file diff --git a/UniSky/Controls/Sheet/SheetControl.cs b/UniSky/Controls/Sheet/SheetControl.cs new file mode 100644 index 0000000..10b5ef4 --- /dev/null +++ b/UniSky/Controls/Sheet/SheetControl.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using System.Windows.Input; +using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.Toolkit.Uwp.Deferred; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using UniSky.Services; +using Windows.ApplicationModel; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Documents; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; + +// The Templated Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234235 + +namespace UniSky.Controls.Sheet +{ + public class SheetShowingEventArgs : RoutedEventArgs + { + public object Parameter { get; } + + public SheetShowingEventArgs(object parameter) + { + Parameter = parameter; + } + } + + public class SheetHidingEventArgs : RoutedEventArgs + { + private Deferral _deferral; + private TaskCompletionSource _deferralCompletion; + + public Deferral GetDeferral() + { + _deferralCompletion = new TaskCompletionSource(); + return (_deferral ??= new Deferral(OnDeferralCompleted)); + } + + public bool Cancel { get; set; } = false; + + internal Task WaitOnDeferral() + { + if (_deferral == null) + return Task.CompletedTask; + else + return _deferralCompletion.Task; + } + + private void OnDeferralCompleted() + { + _deferralCompletion?.SetResult(null); + } + } + + [ContentProperty(Name = nameof(SheetContent))] + public class SheetControl : Control + { + public object SheetContent + { + get => (object)GetValue(SheetContentProperty); + set => SetValue(SheetContentProperty, value); + } + + public static readonly DependencyProperty SheetContentProperty = + DependencyProperty.Register("SheetContent", typeof(object), typeof(SheetControl), new PropertyMetadata(null)); + + public DataTemplate SheetContentTemplate + { + get => (DataTemplate)GetValue(SheetContentTemplateProperty); + set => SetValue(SheetContentTemplateProperty, value); + } + + public static readonly DependencyProperty SheetContentTemplateProperty = + DependencyProperty.Register("SheetContentTemplate", typeof(DataTemplate), typeof(SheetControl), new PropertyMetadata(null)); + + public object TitleContent + { + get => (object)GetValue(TitleContentProperty); + set => SetValue(TitleContentProperty, value); + } + + public static readonly DependencyProperty TitleContentProperty = + DependencyProperty.Register("TitleContent", typeof(object), typeof(SheetControl), new PropertyMetadata(null)); + + public DataTemplate TitleContentTemplate + { + get => (DataTemplate)GetValue(TitleContentTemplateProperty); + set => SetValue(TitleContentTemplateProperty, value); + } + + public static readonly DependencyProperty TitleContentTemplateProperty = + DependencyProperty.Register("TitleContentTemplate", typeof(DataTemplate), typeof(SheetControl), new PropertyMetadata(null)); + + public object PrimaryButtonContent + { + get => (object)GetValue(PrimaryButtonContentProperty); + set => SetValue(PrimaryButtonContentProperty, value); + } + + public static readonly DependencyProperty PrimaryButtonContentProperty = + DependencyProperty.Register("PrimaryButtonContent", typeof(object), typeof(SheetControl), new PropertyMetadata(null)); + + public DataTemplate PrimaryButtonContentTemplate + { + get => (DataTemplate)GetValue(PrimaryButtonContentTemplateProperty); + set => SetValue(PrimaryButtonContentTemplateProperty, value); + } + + public static readonly DependencyProperty PrimaryButtonContentTemplateProperty = + DependencyProperty.Register("PrimaryButtonContentTemplate", typeof(DataTemplate), typeof(SheetControl), new PropertyMetadata(null)); + + public Visibility PrimaryButtonVisibility + { + get => (Visibility)GetValue(PrimaryButtonVisibilityProperty); + set => SetValue(PrimaryButtonVisibilityProperty, value); + } + + public static readonly DependencyProperty PrimaryButtonVisibilityProperty = + DependencyProperty.Register("PrimaryButtonVisibility", typeof(Visibility), typeof(SheetControl), new PropertyMetadata(Visibility.Visible)); + + public ICommand PrimaryButtonCommand + { + get => (ICommand)GetValue(PrimaryButtonCommandProperty); + set => SetValue(PrimaryButtonCommandProperty, value); + } + + public static readonly DependencyProperty PrimaryButtonCommandProperty = + DependencyProperty.Register("PrimaryButtonCommand", typeof(ICommand), typeof(SheetControl), new PropertyMetadata(null)); + + public bool IsPrimaryButtonEnabled + { + get => (bool)GetValue(IsPrimaryButtonEnabledProperty); + set => SetValue(IsPrimaryButtonEnabledProperty, value); + } + + public static readonly DependencyProperty IsPrimaryButtonEnabledProperty = + DependencyProperty.Register("IsPrimaryButtonEnabled", typeof(bool), typeof(SheetControl), new PropertyMetadata(true)); + + public object SecondaryButtonContent + { + get => (object)GetValue(SecondaryButtonContentProperty); + set => SetValue(SecondaryButtonContentProperty, value); + } + + public static readonly DependencyProperty SecondaryButtonContentProperty = + DependencyProperty.Register("SecondaryButtonContent", typeof(object), typeof(SheetControl), new PropertyMetadata(null)); + + public DataTemplate SecondaryButtonContentTemplate + { + get => (DataTemplate)GetValue(SecondaryButtonContentTemplateProperty); + set => SetValue(SecondaryButtonContentTemplateProperty, value); + } + + public static readonly DependencyProperty SecondaryButtonContentTemplateProperty = + DependencyProperty.Register("SecondaryButtonContentTemplate", typeof(DataTemplate), typeof(SheetControl), new PropertyMetadata(null)); + + public Visibility SecondaryButtonVisibility + { + get => (Visibility)GetValue(SecondaryButtonVisibilityProperty); + set => SetValue(SecondaryButtonVisibilityProperty, value); + } + + public static readonly DependencyProperty SecondaryButtonVisibilityProperty = + DependencyProperty.Register("SecondaryButtonVisibility", typeof(Visibility), typeof(SheetControl), new PropertyMetadata(Visibility.Visible)); + + public ICommand SecondaryButtonCommand + { + get => (ICommand)GetValue(SecondaryButtonCommandProperty); + set => SetValue(SecondaryButtonCommandProperty, value); + } + + public static readonly DependencyProperty SecondaryButtonCommandProperty = + DependencyProperty.Register("SecondaryButtonCommand", typeof(ICommand), typeof(SheetControl), new PropertyMetadata(null)); + + public bool IsSecondaryButtonEnabled + { + get => (bool)GetValue(IsSecondaryButtonEnabledProperty); + set => SetValue(IsSecondaryButtonEnabledProperty, value); + } + + public static readonly DependencyProperty IsSecondaryButtonEnabledProperty = + DependencyProperty.Register("IsSecondaryButtonEnabled", typeof(bool), typeof(SheetControl), new PropertyMetadata(true)); + + public event TypedEventHandler Showing; + public event TypedEventHandler Shown; + public event TypedEventHandler Hiding; + public event TypedEventHandler Hidden; + + public SheetControl() + { + this.DefaultStyleKey = typeof(SheetControl); + } + + internal void InvokeShowing(object parameter) + { + Showing?.Invoke(this, new SheetShowingEventArgs(parameter)); + } + + internal async Task InvokeHidingAsync() + { + var ev = new SheetHidingEventArgs(); + Hiding?.Invoke(this, ev); + + await ev.WaitOnDeferral(); + + return !ev.Cancel; + } + + internal void InvokeShown() + { + Shown?.Invoke(this, new RoutedEventArgs()); + } + + internal void InvokeHidden() + { + Hidden?.Invoke(this, new RoutedEventArgs()); + } + } +} diff --git a/UniSky/Controls/Sheet/SheetControl.xaml b/UniSky/Controls/Sheet/SheetControl.xaml new file mode 100644 index 0000000..5ee46e2 --- /dev/null +++ b/UniSky/Controls/Sheet/SheetControl.xaml @@ -0,0 +1,86 @@ + + + + diff --git a/UniSky/Controls/Sheet/SheetRootControl.xaml b/UniSky/Controls/Sheet/SheetRootControl.xaml new file mode 100644 index 0000000..7f2b39a --- /dev/null +++ b/UniSky/Controls/Sheet/SheetRootControl.xaml @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UniSky/Controls/Sheet/SheetRootControl.xaml.cs b/UniSky/Controls/Sheet/SheetRootControl.xaml.cs new file mode 100644 index 0000000..50b8343 --- /dev/null +++ b/UniSky/Controls/Sheet/SheetRootControl.xaml.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.Toolkit.Uwp.UI; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using UniSky.Pages; +using UniSky.Services; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Composition; +using Windows.UI.Core; +using Windows.UI.Core.Preview; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Hosting; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +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 FrameworkElement ContentElement + { + get => (FrameworkElement)GetValue(ContentElementProperty); + set => SetValue(ContentElementProperty, value); + } + + public static readonly DependencyProperty ContentElementProperty = + DependencyProperty.Register("ContentElement", typeof(FrameworkElement), typeof(SheetRootControl), new PropertyMetadata(null)); + + public double TotalHeight + { + get => (double)GetValue(TotalHeightProperty); + set => SetValue(TotalHeightProperty, value); + } + + // Using a DependencyProperty as the backing store for TotalHeight. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TotalHeightProperty = + DependencyProperty.Register("TotalHeight", typeof(double), typeof(SheetRootControl), new PropertyMetadata(0.0)); + + public SheetRootControl() + { + this.InitializeComponent(); + VisualStateManager.GoToState(this, "Closed", false); + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (!double.IsInfinity(HostControl.MaxHeight)) + { + TotalHeight = 64; + SheetRoot.Height = Math.Max(0, HostControl.MaxHeight - (SheetBorder.Margin.Top + SheetBorder.Margin.Bottom) - (HostControl.Margin.Top + HostControl.Margin.Bottom)); + } + else + { + TotalHeight = finalSize.Height; + SheetRoot.Height = Math.Max(0, finalSize.Height - (SheetBorder.Margin.Top + SheetBorder.Margin.Bottom) - (HostControl.Margin.Top + HostControl.Margin.Bottom)); + } + + return base.ArrangeOverride(finalSize); + } + + internal void ShowSheet(SheetControl control, object parameter) + { + SheetRoot.Child = control; + control.InvokeShowing(parameter); + + VisualStateManager.GoToState(this, "Open", true); + + Window.Current.SetTitleBar(TitleBar); + + var safeAreaService = Ioc.Default.GetRequiredService(); + safeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; + + var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); + systemNavigationManager.BackRequested += OnBackRequested; + } + + private async void OnBackRequested(object sender, BackRequestedEventArgs e) + { + e.Handled = true; + await HideSheetAsync(); + } + + internal async Task HideSheetAsync() + { + // TODO: allow deferrals + if (SheetRoot.Child is SheetControl control) + { + if (!await control.InvokeHidingAsync()) + return false; + } + + VisualStateManager.GoToState(this, "Closed", true); + + var safeAreaService = Ioc.Default.GetRequiredService(); + safeAreaService.SafeAreaUpdated -= OnSafeAreaUpdated; + + var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); + systemNavigationManager.BackRequested -= OnBackRequested; + + return true; + } + + private void OnSafeAreaUpdated(object sender, SafeAreaUpdatedEventArgs e) + { + TitleBar.Height = e.SafeArea.Bounds.Top; + SheetBorder.Margin = new Thickness(0, 16 + e.SafeArea.Bounds.Top, 0, 0); + HostControl.Margin = new Thickness(e.SafeArea.Bounds.Left, 0, e.SafeArea.Bounds.Right, e.SafeArea.Bounds.Bottom); + + if (!double.IsInfinity(HostControl.MaxHeight)) + { + SheetRoot.Height = Math.Max(0, HostControl.MaxHeight - (SheetBorder.Margin.Top + SheetBorder.Margin.Bottom) - (HostControl.Margin.Top + HostControl.Margin.Bottom)); + } + else + { + SheetRoot.Height = Math.Max(0, ActualHeight - (SheetBorder.Margin.Top + SheetBorder.Margin.Bottom) - (HostControl.Margin.Top + HostControl.Margin.Bottom)); + } + } + + private async void RefreshContainer_RefreshRequested(MUXC.RefreshContainer sender, MUXC.RefreshRequestedEventArgs args) + { + var deferral = args.GetDeferral(); + await HideSheetAsync(); + deferral.Complete(); + } + + private void ShowSheetStoryboard_Completed(object sender, object e) + { + if (SheetRoot.Child is SheetControl control) + { + control.InvokeShown(); + } + + CommonShadow.CastTo = CompositionBackdropContainer; + Effects.SetShadow(SheetBorder, CommonShadow); + } + + private void HideSheetStoryboard_Completed(object sender, object e) + { + if (SheetRoot.Child is SheetControl control) + { + control.InvokeHidden(); + SheetRoot.Child = null; + } + + Effects.SetShadow(SheetBorder, null); + } + } +} diff --git a/UniSky/Converters/Static.cs b/UniSky/Converters/Static.cs index 45df4bc..460ae07 100644 --- a/UniSky/Converters/Static.cs +++ b/UniSky/Converters/Static.cs @@ -12,10 +12,10 @@ public static bool Equals(int a, int b) => a == b; public static bool AtLeast(int a, int b) => a >= b; - + public static bool Not(bool x) + => !x; public static bool NotNull(object x) => x is not null; - public static bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s); } diff --git a/UniSky/DataTemplates/FeedTemplates.xaml b/UniSky/DataTemplates/FeedTemplates.xaml index 14f566a..4764200 100644 --- a/UniSky/DataTemplates/FeedTemplates.xaml +++ b/UniSky/DataTemplates/FeedTemplates.xaml @@ -9,13 +9,33 @@ xmlns:converters="using:UniSky.Converters" xmlns:extensions="using:UniSky.Extensions" xmlns:feeds="using:UniSky.ViewModels.Feeds" - xmlns:posts="using:UniSky.ViewModels.Posts" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:posts="using:UniSky.ViewModels.Posts" + xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" x:DefaultBindMode="OneWay"> #ea4298 #5cefaa + + + + + + + + + + + + + + + @@ -84,11 +87,17 @@ - - - -