From f0ccb6cb7d12145b857b7ab26689bc59974b1708 Mon Sep 17 00:00:00 2001 From: Thomas May Date: Thu, 5 Dec 2024 23:37:06 +0000 Subject: [PATCH 01/11] Windowed sheets --- UniSky/App.xaml.cs | 9 +- UniSky/Controls/Compose/ComposeSheet.xaml.cs | 25 ++- .../Controls/RichTextBlock/RichTextBlock.cs | 3 +- UniSky/Controls/Sheet/SheetControl.cs | 61 +++++++ UniSky/Controls/Sheet/SheetControl.xaml | 135 +++++++++----- .../Controls/Sheet/SheetRootControl.xaml.cs | 6 +- UniSky/Extensions/Hairline.cs | 27 ++- UniSky/Package.appxmanifest | 6 +- UniSky/Pages/FeedsPage.xaml.cs | 4 +- UniSky/Pages/HomePage.xaml.cs | 6 +- UniSky/Pages/LoginPage.xaml.cs | 3 +- UniSky/Pages/ProfilePage.xaml.cs | 10 +- UniSky/RootPage.xaml.cs | 5 +- UniSky/Services/ISafeAreaService.cs | 13 -- UniSky/Services/ISheetService.cs | 3 +- UniSky/Services/NavigationServiceLocator.cs | 2 +- UniSky/Services/ProtocolService.cs | 3 +- .../SafeArea/AppWindowSafeAreaService.cs | 120 ++++++++++++ .../CoreWindowSafeAreaService.cs} | 17 +- UniSky/Services/SafeArea/ISafeAreaService.cs | 14 ++ UniSky/Services/ServiceContainer.cs | 171 ++++++++++++++++++ .../Sheet/AppWindowSheetController.cs | 26 +++ .../Sheet/ApplicationViewSheetController.cs | 27 +++ UniSky/Services/Sheet/ISheetController.cs | 13 ++ UniSky/Services/Sheet/SheetRootController.cs | 19 ++ UniSky/Services/Sheet/SheetService.cs | 130 +++++++++++++ UniSky/Services/SheetService.cs | 36 ---- UniSky/Themes/ThemeResources.cs | 3 +- .../Compose/ComposeViewAttachmentViewModel.cs | 4 + UniSky/ViewModels/Compose/ComposeViewModel.cs | 10 +- UniSky/ViewModels/FeedsViewModel.cs | 3 +- UniSky/ViewModels/HomeViewModel.cs | 3 +- UniSky/ViewModels/Post/PostViewModel.cs | 8 +- .../Profile/ProfilePageViewModel.cs | 9 +- 34 files changed, 775 insertions(+), 159 deletions(-) delete mode 100644 UniSky/Services/ISafeAreaService.cs create mode 100644 UniSky/Services/SafeArea/AppWindowSafeAreaService.cs rename UniSky/Services/{SafeAreaService.cs => SafeArea/CoreWindowSafeAreaService.cs} (94%) create mode 100644 UniSky/Services/SafeArea/ISafeAreaService.cs create mode 100644 UniSky/Services/ServiceContainer.cs create mode 100644 UniSky/Services/Sheet/AppWindowSheetController.cs create mode 100644 UniSky/Services/Sheet/ApplicationViewSheetController.cs create mode 100644 UniSky/Services/Sheet/ISheetController.cs create mode 100644 UniSky/Services/Sheet/SheetRootController.cs create mode 100644 UniSky/Services/Sheet/SheetService.cs delete mode 100644 UniSky/Services/SheetService.cs diff --git a/UniSky/App.xaml.cs b/UniSky/App.xaml.cs index 2c52097..a68c7fc 100644 --- a/UniSky/App.xaml.cs +++ b/UniSky/App.xaml.cs @@ -53,17 +53,18 @@ private void ConfigureServices() collection.AddLogging(c => c.AddDebug() .SetMinimumLevel(LogLevel.Trace)); + collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(); - collection.AddSingleton(); - collection.AddSingleton(); + collection.AddScoped(); + collection.AddScoped(); collection.AddTransient(); collection.AddTransient(); - Ioc.Default.ConfigureServices(collection.BuildServiceProvider()); + ServiceContainer.Default.ConfigureServices(collection.BuildServiceProvider()); + Configurator.Formatters.Register("en", (locale) => new ShortTimespanFormatter("en")); } diff --git a/UniSky/Controls/Compose/ComposeSheet.xaml.cs b/UniSky/Controls/Compose/ComposeSheet.xaml.cs index b83c5eb..71694a4 100644 --- a/UniSky/Controls/Compose/ComposeSheet.xaml.cs +++ b/UniSky/Controls/Compose/ComposeSheet.xaml.cs @@ -4,14 +4,17 @@ using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using CommunityToolkit.Mvvm.DependencyInjection; +using Ipfs; using Microsoft.Extensions.DependencyInjection; using UniSky.Controls.Sheet; +using UniSky.Services; using UniSky.ViewModels.Compose; using UniSky.ViewModels.Posts; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.Resources; using Windows.Foundation; using Windows.Foundation.Collections; +using Windows.Foundation.Metadata; using Windows.UI.Popups; using Windows.UI.ViewManagement; using Windows.UI.Xaml; @@ -50,6 +53,11 @@ public ComposeSheet() this.strings = ResourceLoader.GetForCurrentView(); } + protected override void OnBottomInsetsChanged(double leftInset, double rightInset) + { + FooterContainer.Padding = new Thickness(leftInset, 0, rightInset, 2); + } + public bool Not(bool b, bool a) => !a && !b; @@ -70,11 +78,11 @@ private void OnShowing(SheetControl sender, SheetShowingEventArgs e) if (e.Parameter is PostViewModel replyTo) { - this.ViewModel = ActivatorUtilities.CreateInstance(Ioc.Default, replyTo); + this.ViewModel = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, replyTo, Controller); } else { - this.ViewModel = ActivatorUtilities.CreateInstance(Ioc.Default); + this.ViewModel = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, Controller); } } @@ -104,10 +112,17 @@ private async void OnHiding(SheetControl sender, SheetHidingEventArgs e) var deferral = e.GetDeferral(); try { - if (ViewModel.IsDirty && await new ComposeDiscardDraftDialog().ShowAsync() != ContentDialogResult.Primary) + if (ViewModel.IsDirty) { - e.Cancel = true; - return; + var discardDraftDialog = new ComposeDiscardDraftDialog(); + if (Controller != null && ApiInformation.IsApiContractPresent(typeof(UniversalApiContract).FullName, 8)) + discardDraftDialog.XamlRoot = Controller.Root.XamlRoot; + + if (await discardDraftDialog.ShowAsync() != ContentDialogResult.Primary) + { + e.Cancel = true; + return; + } } } finally diff --git a/UniSky/Controls/RichTextBlock/RichTextBlock.cs b/UniSky/Controls/RichTextBlock/RichTextBlock.cs index d51c79a..064eae0 100644 --- a/UniSky/Controls/RichTextBlock/RichTextBlock.cs +++ b/UniSky/Controls/RichTextBlock/RichTextBlock.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Toolkit.Uwp.UI.Extensions; using UniSky.Pages; using UniSky.Services; @@ -139,7 +140,7 @@ private void Hyperlink_Click(Hyperlink sender, HyperlinkClickEventArgs args) if (sender.GetValue(HyperlinkUrlProperty) is not Uri { Scheme: "unisky" } uri) return; - var service = Ioc.Default.GetRequiredService() + var service = ServiceContainer.Scoped.GetRequiredService() .GetNavigationService("Home"); var path = uri.PathAndQuery.Split('/', StringSplitOptions.RemoveEmptyEntries); diff --git a/UniSky/Controls/Sheet/SheetControl.cs b/UniSky/Controls/Sheet/SheetControl.cs index 10b5ef4..f662517 100644 --- a/UniSky/Controls/Sheet/SheetControl.cs +++ b/UniSky/Controls/Sheet/SheetControl.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection.PortableExecutable; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using System.Windows.Input; using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Toolkit.Uwp.Deferred; using Microsoft.Toolkit.Uwp.UI.Extensions; using UniSky.Services; using Windows.ApplicationModel; using Windows.Foundation; +using Windows.UI.WindowManagement; +using Windows.UI.WindowManagement.Preview; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; @@ -193,11 +197,43 @@ public bool IsSecondaryButtonEnabled public event TypedEventHandler Hiding; public event TypedEventHandler Hidden; + public ISheetController Controller { get; private set; } + public SheetControl() { this.DefaultStyleKey = typeof(SheetControl); } + internal void SetSheetController(ISheetController controller) + { + Controller = controller; + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (Controller.IsFullWindow) + { + VisualStateManager.GoToState(this, "FullWindow", false); + var titleBarDragArea = this.FindDescendantByName("TitleBarDragArea"); + Controller.SafeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; + Controller.SafeAreaService.SetTitleBar(titleBarDragArea); + + this.SizeChanged += OnSizeChanged; + } + else + { + VisualStateManager.GoToState(this, "Standard", false); + } + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + var rightButton = this.FindDescendantByName("PrimaryTitleBarButton"); + this.OnBottomInsetsChanged(0, rightButton.ActualWidth + 16); + } + internal void InvokeShowing(object parameter) { Showing?.Invoke(this, new SheetShowingEventArgs(parameter)); @@ -220,7 +256,32 @@ internal void InvokeShown() internal void InvokeHidden() { + if (Controller.IsFullWindow) + { + Controller.SafeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; + } + Hidden?.Invoke(this, new RoutedEventArgs()); } + + private void OnSafeAreaUpdated(object sender, SafeAreaUpdatedEventArgs e) + { + var titleBarGrid = (Grid)this.FindDescendantByName("TitleBarGrid"); + + if (e.SafeArea.HasTitleBar) + { + titleBarGrid.Height = e.SafeArea.Bounds.Top; + titleBarGrid.Padding = new Thickness(); + } + else + { + titleBarGrid.Height = 42; + titleBarGrid.Padding = new Thickness(0, e.SafeArea.Bounds.Top, 0, 0); + } + + Margin = new Thickness(e.SafeArea.Bounds.Left, 0, e.SafeArea.Bounds.Right, e.SafeArea.Bounds.Bottom); + } + + protected virtual void OnBottomInsetsChanged(double leftInset, double rightInset) { } } } diff --git a/UniSky/Controls/Sheet/SheetControl.xaml b/UniSky/Controls/Sheet/SheetControl.xaml index 5ee46e2..80f6bbb 100644 --- a/UniSky/Controls/Sheet/SheetControl.xaml +++ b/UniSky/Controls/Sheet/SheetControl.xaml @@ -10,37 +10,69 @@ - + + Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - + + - - - - - - - + + + Grid.ColumnSpan="3" + VerticalAlignment="Center" + HorizontalAlignment="Center" + Content="{TemplateBinding TitleContent}" + ContentTemplate="{TemplateBinding TitleContentTemplate}" + Margin="16,0"/> + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UniSky/Controls/Sheet/SheetRootControl.xaml.cs b/UniSky/Controls/Sheet/SheetRootControl.xaml.cs index 716ef95..f0f000f 100644 --- a/UniSky/Controls/Sheet/SheetRootControl.xaml.cs +++ b/UniSky/Controls/Sheet/SheetRootControl.xaml.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Toolkit.Uwp.UI; using Microsoft.Toolkit.Uwp.UI.Extensions; using UniSky.Pages; @@ -86,7 +87,7 @@ internal void ShowSheet(SheetControl control, object parameter) Window.Current.SetTitleBar(TitleBar); - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); safeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); @@ -106,7 +107,6 @@ internal async Task HideSheetAsync() try { - // TODO: allow deferrals if (SheetRoot.Child is SheetControl control) { if (!await control.InvokeHidingAsync()) @@ -115,7 +115,7 @@ internal async Task HideSheetAsync() VisualStateManager.GoToState(this, "Closed", true); - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); safeAreaService.SafeAreaUpdated -= OnSafeAreaUpdated; var systemNavigationManager = SystemNavigationManager.GetForCurrentView(); diff --git a/UniSky/Extensions/Hairline.cs b/UniSky/Extensions/Hairline.cs index 7ccd099..804ec8f 100644 --- a/UniSky/Extensions/Hairline.cs +++ b/UniSky/Extensions/Hairline.cs @@ -6,6 +6,7 @@ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml; using Windows.Graphics.Display; +using System.Threading; namespace UniSky.Extensions { @@ -37,15 +38,23 @@ public static void SetMargin(DependencyObject obj, Thickness value) public static readonly DependencyProperty MarginProperty = DependencyProperty.RegisterAttached("Margin", typeof(Thickness), typeof(Hairline), new PropertyMetadata(DependencyProperty.UnsetValue, OnMarginPropertyChanged)); - private static List> Elements { get; set; } = []; - private static DisplayInformation DisplayInfo { get; set; } + private static ThreadLocal>> Elements { get; } + = new ThreadLocal>>(() => new List>(), true); + + private static ThreadLocal DisplayInfo { get; } + = new ThreadLocal(() => + { + var info = DisplayInformation.GetForCurrentView(); + info.DpiChanged += OnDpiChanged; + return info; + }, true); private static void OnThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not (Grid or StackPanel or Control or Border)) return; - Elements.Add(new WeakReference(d)); + Elements.Value.Add(new WeakReference(d)); if (DisplayInfo == null) { @@ -53,7 +62,7 @@ private static void OnThicknessPropertyChanged(DependencyObject d, DependencyPro } var newValue = (Thickness)(e.NewValue); - ApplyBorderThickness(d, newValue, DisplayInfo); + ApplyBorderThickness(d, newValue, DisplayInfo.Value); } private static void OnMarginPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -61,7 +70,7 @@ private static void OnMarginPropertyChanged(DependencyObject d, DependencyProper if (d is not FrameworkElement) return; - Elements.Add(new WeakReference(d)); + Elements.Value.Add(new WeakReference(d)); if (DisplayInfo == null) { @@ -69,7 +78,7 @@ private static void OnMarginPropertyChanged(DependencyObject d, DependencyProper } var newValue = (Thickness)(e.NewValue); - ApplyMargin(d, newValue, DisplayInfo); + ApplyMargin(d, newValue, DisplayInfo.Value); } private static void ApplyBorderThickness(DependencyObject d, Thickness newValue, DisplayInformation info) @@ -117,14 +126,12 @@ private static void ApplyMargin(DependencyObject d, Thickness newValue, DisplayI public static void Initialize() { - DisplayInfo = DisplayInformation.GetForCurrentView(); - DisplayInfo.DpiChanged += OnDpiChanged; - ApplyResources(DisplayInfo); + ApplyResources(DisplayInfo.Value); } private static void OnDpiChanged(DisplayInformation sender, object args) { - foreach (var item in Elements) + foreach (var item in Elements.Value) { if (item.TryGetTarget(out var element)) { diff --git a/UniSky/Package.appxmanifest b/UniSky/Package.appxmanifest index 3f8936e..a2935d5 100644 --- a/UniSky/Package.appxmanifest +++ b/UniSky/Package.appxmanifest @@ -4,8 +4,9 @@ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" - xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" - IgnorableNamespaces="uap mp"> + xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" + xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" + IgnorableNamespaces="uap mp uap5 rescap"> + \ No newline at end of file diff --git a/UniSky/Pages/FeedsPage.xaml.cs b/UniSky/Pages/FeedsPage.xaml.cs index 6fd112f..45112e9 100644 --- a/UniSky/Pages/FeedsPage.xaml.cs +++ b/UniSky/Pages/FeedsPage.xaml.cs @@ -39,9 +39,9 @@ protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); - this.ViewModel = ActivatorUtilities.CreateInstance(Ioc.Default); + this.ViewModel = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped); - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); safeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; } diff --git a/UniSky/Pages/HomePage.xaml.cs b/UniSky/Pages/HomePage.xaml.cs index b27dbc4..de80b63 100644 --- a/UniSky/Pages/HomePage.xaml.cs +++ b/UniSky/Pages/HomePage.xaml.cs @@ -39,16 +39,16 @@ protected override void OnNavigatedTo(NavigationEventArgs e) Window.Current.SetTitleBar(TitleBarDrag); - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); safeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; - var serviceLocator = Ioc.Default.GetRequiredService(); + var serviceLocator = ServiceContainer.Scoped.GetRequiredService(); var service = serviceLocator.GetNavigationService("Home"); service.Frame = NavigationFrame; if (e.Parameter is string session || e.Parameter is ATDid did && (session = did.Handler) != null) { - ViewModel = ActivatorUtilities.CreateInstance(Ioc.Default, session); + ViewModel = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, session); } } diff --git a/UniSky/Pages/LoginPage.xaml.cs b/UniSky/Pages/LoginPage.xaml.cs index 7858183..4b37c5e 100644 --- a/UniSky/Pages/LoginPage.xaml.cs +++ b/UniSky/Pages/LoginPage.xaml.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices.WindowsRuntime; using CommunityToolkit.Mvvm.DependencyInjection; using Microsoft.Extensions.DependencyInjection; +using UniSky.Services; using UniSky.ViewModels; using Windows.Foundation; using Windows.Foundation.Collections; @@ -32,7 +33,7 @@ public LoginViewModel ViewModel public LoginPage() { this.InitializeComponent(); - this.ViewModel = ActivatorUtilities.CreateInstance(Ioc.Default); + this.ViewModel = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped); } protected override void OnNavigatedTo(NavigationEventArgs e) diff --git a/UniSky/Pages/ProfilePage.xaml.cs b/UniSky/Pages/ProfilePage.xaml.cs index d7ecb12..da79024 100644 --- a/UniSky/Pages/ProfilePage.xaml.cs +++ b/UniSky/Pages/ProfilePage.xaml.cs @@ -64,15 +64,15 @@ protected override void OnNavigatedTo(NavigationEventArgs e) if (e.Parameter is Uri { Scheme: "unisky" } uri) HandleUniskyProtocol(uri); else if (e.Parameter is ATObject basic) - this.DataContext = ActivatorUtilities.CreateInstance(Ioc.Default, basic); + this.DataContext = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, basic); - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); safeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; } protected override void OnNavigatedFrom(NavigationEventArgs e) { - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); safeAreaService.SafeAreaUpdated -= OnSafeAreaUpdated; safeAreaService.SetTitlebarTheme(ElementTheme.Default); } @@ -86,7 +86,7 @@ private void HandleUniskyProtocol(Uri uri) } if (ATDid.TryCreate(path[1], out var did)) - this.DataContext = ActivatorUtilities.CreateInstance(Ioc.Default, did); + this.DataContext = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, did); } private void Page_Loaded(object sender, RoutedEventArgs e) @@ -227,7 +227,7 @@ private void UpdateSizeDependentProperties() if (ProfileContainer.ActualHeight == 0) return; - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); var titleBarHeight = (float)safeAreaService.State.Bounds.Top + 4; var stickyHeight = (float)StickyFooter.ActualHeight; diff --git a/UniSky/RootPage.xaml.cs b/UniSky/RootPage.xaml.cs index 660a65b..e8791e6 100644 --- a/UniSky/RootPage.xaml.cs +++ b/UniSky/RootPage.xaml.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices.WindowsRuntime; using System.Xml; using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Geometry; using UniSky.Helpers.Composition; @@ -51,11 +52,11 @@ protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); - var serviceLocator = Ioc.Default.GetRequiredService(); + var serviceLocator = ServiceContainer.Scoped.GetRequiredService(); var service = serviceLocator.GetNavigationService("Root"); service.Frame = RootFrame; - var sessionService = Ioc.Default.GetRequiredService(); + var sessionService = ServiceContainer.Scoped.GetRequiredService(); if (ApplicationData.Current.LocalSettings.Values.TryGetValue("LastUsedUser", out var userObj) && userObj is string user) { diff --git a/UniSky/Services/ISafeAreaService.cs b/UniSky/Services/ISafeAreaService.cs deleted file mode 100644 index 329a9f9..0000000 --- a/UniSky/Services/ISafeAreaService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Windows.UI.Xaml; - -namespace UniSky.Services -{ - internal interface ISafeAreaService - { - SafeAreaInfo State { get; } - event EventHandler SafeAreaUpdated; - - void SetTitlebarTheme(ElementTheme theme); - } -} \ No newline at end of file diff --git a/UniSky/Services/ISheetService.cs b/UniSky/Services/ISheetService.cs index 792fde2..629d5ab 100644 --- a/UniSky/Services/ISheetService.cs +++ b/UniSky/Services/ISheetService.cs @@ -5,7 +5,6 @@ namespace UniSky.Services { public interface ISheetService { - Task ShowAsync(object parameter = null) where T : SheetControl, new(); - Task TryCloseAsync(); + Task ShowAsync(object parameter = null) where T : SheetControl, new(); } } \ No newline at end of file diff --git a/UniSky/Services/NavigationServiceLocator.cs b/UniSky/Services/NavigationServiceLocator.cs index b3cc916..d087c06 100644 --- a/UniSky/Services/NavigationServiceLocator.cs +++ b/UniSky/Services/NavigationServiceLocator.cs @@ -14,7 +14,7 @@ public INavigationService GetNavigationService(string name) if (_services.TryGetValue(name, out var service)) return service; - service = ActivatorUtilities.CreateInstance(Ioc.Default); + service = ActivatorUtilities.CreateInstance(ServiceContainer.Scoped); _services.Add(name, service); return service; diff --git a/UniSky/Services/ProtocolService.cs b/UniSky/Services/ProtocolService.cs index 6594cfa..7dee517 100644 --- a/UniSky/Services/ProtocolService.cs +++ b/UniSky/Services/ProtocolService.cs @@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.DependencyInjection; using FishyFlip; using FishyFlip.Events; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using UniSky.Models; @@ -34,7 +35,7 @@ private void OnSessionUpdated(object sender, SessionUpdatedEventArgs e) logger.LogInformation("Session updated, saving new tokens!"); var session = new SessionModel(true, e.InstanceUri.Host.ToLowerInvariant(), e.Session.Session, e.Session); - var sessionService = Ioc.Default.GetRequiredService(); + var sessionService = ServiceContainer.Scoped.GetRequiredService(); sessionService.SaveSession(session); } } diff --git a/UniSky/Services/SafeArea/AppWindowSafeAreaService.cs b/UniSky/Services/SafeArea/AppWindowSafeAreaService.cs new file mode 100644 index 0000000..8505d6e --- /dev/null +++ b/UniSky/Services/SafeArea/AppWindowSafeAreaService.cs @@ -0,0 +1,120 @@ +using System; +using Microsoft.Toolkit.Uwp.UI.Helpers; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.WindowManagement; +using Windows.Foundation; +using Windows.UI.WindowManagement.Preview; +using System.Linq; + +namespace UniSky.Services; + +internal class AppWindowSafeAreaService : ISafeAreaService +{ + private readonly AppWindow _appWindow; + private readonly ThemeListener _themeListener; + + private SafeAreaInfo _state; + private event EventHandler _event; + + public SafeAreaInfo State + => _state; + + public event EventHandler SafeAreaUpdated + { + add + { + _event += value; + Update(); + } + + remove => _event -= value; + } + + public AppWindowSafeAreaService(AppWindow appWindow) + { + this._appWindow = appWindow; + this._themeListener = new ThemeListener(); + + WindowManagementPreview.SetPreferredMinSize(appWindow, new Size(320, 320)); + + var titleBar = appWindow.TitleBar; + titleBar.ExtendsContentIntoTitleBar = true; + + appWindow.Changed += OnChanged; + + _state = new SafeAreaInfo(true, true, new Thickness(), ElementTheme.Default); + SetTitlebarTheme(ElementTheme.Default); + Update(); + } + + private void OnChanged(AppWindow sender, AppWindowChangedEventArgs args) + { + Update(); + } + + private void Update() + { + if (_appWindow.TitleBar.IsVisible) + { + _state = _state with { HasTitleBar = true }; + } + else + { + _state = _state with { HasTitleBar = false }; + } + + UpdateBounds(); + } + + private void UpdateBounds() + { + var top = 0.0f; + var left = 0.0f; + var right = 0.0f; + var bottom = 0.0f; + + var titleBar = _appWindow.TitleBar; + if (titleBar.IsVisible) + { + top += (float)titleBar.GetTitleBarOcclusions().Max(t => t.OccludingRect.Height); + } + + _state = _state with { Bounds = new Thickness(left, top, right, bottom) }; + _event?.Invoke(this, new SafeAreaUpdatedEventArgs() { SafeArea = _state }); + } + + public void SetTitlebarTheme(ElementTheme theme) + { + var actualTheme = theme switch + { + ElementTheme.Default => _themeListener.CurrentTheme == ApplicationTheme.Dark + ? ElementTheme.Dark + : ElementTheme.Light, + _ => theme + }; + + var appTitleBar = _appWindow.TitleBar; + appTitleBar.ButtonBackgroundColor = Colors.Transparent; + appTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; + + if (actualTheme == ElementTheme.Dark) + { + appTitleBar.ButtonForegroundColor = Colors.White; + appTitleBar.ButtonInactiveForegroundColor = Colors.LightGray; + } + else + { + appTitleBar.ButtonForegroundColor = Colors.Black; + appTitleBar.ButtonInactiveForegroundColor = Colors.DarkGray; + } + + _state = _state with { Theme = theme }; + _event?.Invoke(this, new SafeAreaUpdatedEventArgs() { SafeArea = _state }); + } + + public void SetTitleBar(UIElement uiElement) + { + // N/A afaict? + } +} \ No newline at end of file diff --git a/UniSky/Services/SafeAreaService.cs b/UniSky/Services/SafeArea/CoreWindowSafeAreaService.cs similarity index 94% rename from UniSky/Services/SafeAreaService.cs rename to UniSky/Services/SafeArea/CoreWindowSafeAreaService.cs index e98bad4..56b1440 100644 --- a/UniSky/Services/SafeAreaService.cs +++ b/UniSky/Services/SafeArea/CoreWindowSafeAreaService.cs @@ -1,21 +1,13 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Humanizer; -using Microsoft.Toolkit.Uwp.UI.Extensions; using Microsoft.Toolkit.Uwp.UI.Helpers; using Windows.ApplicationModel.Core; using Windows.Foundation; using Windows.Foundation.Metadata; -using Windows.Graphics.Display; using Windows.Phone; using Windows.UI; using Windows.UI.Core; using Windows.UI.ViewManagement; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; namespace UniSky.Services; @@ -26,7 +18,7 @@ public class SafeAreaUpdatedEventArgs : EventArgs public SafeAreaInfo SafeArea { get; init; } } -internal class SafeAreaService : ISafeAreaService +internal class CoreWindowSafeAreaService : ISafeAreaService { private readonly CoreWindow _window; private readonly ApplicationView _applicationView; @@ -40,7 +32,7 @@ internal class SafeAreaService : ISafeAreaService public SafeAreaInfo State => _state; - public SafeAreaService() + public CoreWindowSafeAreaService() { _window = CoreWindow.GetForCurrentThread(); _window.SizeChanged += CoreWindow_SizeChanged; @@ -205,4 +197,9 @@ private void OnThemeChanged(ThemeListener sender) { SetTitlebarTheme(_state.Theme); } + + public void SetTitleBar(UIElement uiElement) + { + Window.Current.SetTitleBar(uiElement); + } } \ No newline at end of file diff --git a/UniSky/Services/SafeArea/ISafeAreaService.cs b/UniSky/Services/SafeArea/ISafeAreaService.cs new file mode 100644 index 0000000..6e87e6e --- /dev/null +++ b/UniSky/Services/SafeArea/ISafeAreaService.cs @@ -0,0 +1,14 @@ +using System; +using Windows.UI.Xaml; + +namespace UniSky.Services; + +public interface ISafeAreaService +{ + SafeAreaInfo State { get; } + + event EventHandler SafeAreaUpdated; + + void SetTitleBar(UIElement uiElement); + void SetTitlebarTheme(ElementTheme theme); +} \ No newline at end of file diff --git a/UniSky/Services/ServiceContainer.cs b/UniSky/Services/ServiceContainer.cs new file mode 100644 index 0000000..d67157d --- /dev/null +++ b/UniSky/Services/ServiceContainer.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; + +namespace UniSky.Services; + +#nullable enable + +/// +/// A type that facilitates the use of the type. +/// The provides the ability to configure services in a singleton, thread-safe +/// service provider instance, which can then be used to resolve service instances. +/// The first step to use this feature is to declare some services, for instance: +/// +/// public interface ILogger +/// { +/// void Log(string text); +/// } +/// +/// +/// public class ConsoleLogger : ILogger +/// { +/// void Log(string text) => Console.WriteLine(text); +/// } +/// +/// Then the services configuration should then be done at startup, by calling the +/// method and passing an instance with the services to use. That instance can +/// be from any library offering dependency injection functionality, such as Microsoft.Extensions.DependencyInjection. +/// For instance, using that library, can be used as follows in this example: +/// +/// ServiceContainer.Scoped.ConfigureServices( +/// new ServiceCollection() +/// .AddSingleton<ILogger, Logger>() +/// .BuildServiceProvider()); +/// +/// Finally, you can use the instance (which implements ) +/// to retrieve the service instances from anywhere in your application, by doing as follows: +/// +/// ServiceContainer.Scoped.GetService<ILogger>().Log("Hello world!"); +/// +/// +public sealed partial class ServiceContainer : IServiceProvider +{ + /// + /// Gets the default instance. + /// + public static ServiceContainer Default { get; } = new(); + private static ThreadLocal _scoped = new ThreadLocal(() => Default.CreateScope(), true); + public static IServiceProvider Scoped => _scoped.Value.ServiceProvider; + + /// + /// The instance to use, if initialized. + /// + private volatile IServiceProvider? serviceProvider; + + /// + public object? GetService(Type serviceType) + { + // As per section I.12.6.6 of the official CLI ECMA-335 spec: + // "[...] read and write access to properly aligned memory locations no larger than the native + // word size is atomic when all the write accesses to a location are the same size. Atomic writes + // shall alter no bits other than those written. Unless explicit layout control is used [...], + // data elements no larger than the natural word size [...] shall be properly aligned. + // Object references shall be treated as though they are stored in the native word size." + // The field being accessed here is of native int size (reference type), and is only ever accessed + // directly and atomically by a compare exchange instruction (see below), or here. We can therefore + // assume this read is thread safe with respect to accesses to this property or to invocations to one + // of the available configuration methods. So we can just read the field directly and make the necessary + // check with our local copy, without the need of paying the locking overhead from this get accessor. + IServiceProvider? provider = this.serviceProvider; + + if (provider is null) + { + ThrowInvalidOperationExceptionForMissingInitialization(); + } + + return provider!.GetService(serviceType); + } + + /// + /// Tries to resolve an instance of a specified service type. + /// + /// The type of service to resolve. + /// An instance of the specified service, or . + /// Thrown if the current instance has not been initialized. + public T? GetService() + where T : class + { + IServiceProvider? provider = this.serviceProvider; + + if (provider is null) + { + ThrowInvalidOperationExceptionForMissingInitialization(); + } + + return (T?)provider!.GetService(typeof(T)); + } + + /// + /// Resolves an instance of a specified service type. + /// + /// The type of service to resolve. + /// An instance of the specified service, or . + /// + /// Thrown if the current instance has not been initialized, or if the + /// requested service type was not registered in the service provider currently in use. + /// + public T GetRequiredService() + where T : class + { + IServiceProvider? provider = this.serviceProvider; + + if (provider is null) + { + ThrowInvalidOperationExceptionForMissingInitialization(); + } + + T? service = (T?)provider!.GetService(typeof(T)); + + if (service is null) + { + ThrowInvalidOperationExceptionForUnregisteredType(); + } + + return service!; + } + + /// + /// Initializes the shared instance. + /// + /// The input instance to use. + /// Thrown if is . + public void ConfigureServices(IServiceProvider serviceProvider) + { + IServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, serviceProvider, null); + + if (oldServices is not null) + { + ThrowInvalidOperationExceptionForRepeatedConfiguration(); + } + } + + /// + /// Throws an when the property is used before initialization. + /// + private static void ThrowInvalidOperationExceptionForMissingInitialization() + { + throw new InvalidOperationException("The service provider has not been configured yet."); + } + + /// + /// Throws an when the property is missing a type registration. + /// + private static void ThrowInvalidOperationExceptionForUnregisteredType() + { + throw new InvalidOperationException("The requested service type was not registered."); + } + + /// + /// Throws an when a configuration is attempted more than once. + /// + private static void ThrowInvalidOperationExceptionForRepeatedConfiguration() + { + throw new InvalidOperationException("The default service provider has already been configured."); + } +} diff --git a/UniSky/Services/Sheet/AppWindowSheetController.cs b/UniSky/Services/Sheet/AppWindowSheetController.cs new file mode 100644 index 0000000..48559c2 --- /dev/null +++ b/UniSky/Services/Sheet/AppWindowSheetController.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; +using UniSky.Controls.Sheet; +using Windows.UI.WindowManagement; +using Windows.UI.Xaml; + +namespace UniSky.Services; + +public class AppWindowSheetController(AppWindow appWindow, + SheetControl control) : ISheetController +{ + public UIElement Root => control; + public bool IsFullWindow => true; + public ISafeAreaService SafeAreaService { get; } = new AppWindowSafeAreaService(appWindow); + + public async Task TryHideSheetAsync() + { + if (await control.InvokeHidingAsync()) + { + await appWindow.CloseAsync(); + return true; + } + + return false; + } +} diff --git a/UniSky/Services/Sheet/ApplicationViewSheetController.cs b/UniSky/Services/Sheet/ApplicationViewSheetController.cs new file mode 100644 index 0000000..c0b1ff0 --- /dev/null +++ b/UniSky/Services/Sheet/ApplicationViewSheetController.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using UniSky.Controls.Sheet; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; + +namespace UniSky.Services; + +internal class ApplicationViewSheetController(SheetControl control, + ApplicationView appView, + ISafeAreaService safeAreaService) : ISheetController +{ + public UIElement Root => control; + public bool IsFullWindow => true; + public ISafeAreaService SafeAreaService => safeAreaService; + + public async Task TryHideSheetAsync() + { + if (await control.InvokeHidingAsync()) + { + await appView.TryConsolidateAsync(); + return true; + } + + return false; + } +} diff --git a/UniSky/Services/Sheet/ISheetController.cs b/UniSky/Services/Sheet/ISheetController.cs new file mode 100644 index 0000000..371088d --- /dev/null +++ b/UniSky/Services/Sheet/ISheetController.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using UniSky.Controls.Sheet; +using Windows.UI.Xaml; + +namespace UniSky.Services; + +public interface ISheetController +{ + UIElement Root { get; } + bool IsFullWindow { get; } + ISafeAreaService SafeAreaService { get; } + Task TryHideSheetAsync(); +} diff --git a/UniSky/Services/Sheet/SheetRootController.cs b/UniSky/Services/Sheet/SheetRootController.cs new file mode 100644 index 0000000..847a19a --- /dev/null +++ b/UniSky/Services/Sheet/SheetRootController.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using UniSky.Controls.Sheet; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml; + +namespace UniSky.Services; + +public class SheetRootController(SheetRootControl rootControl, + ISafeAreaService safeAreaService) : ISheetController +{ + public UIElement Root => rootControl; + public bool IsFullWindow => 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 new file mode 100644 index 0000000..649dee1 --- /dev/null +++ b/UniSky/Services/Sheet/SheetService.cs @@ -0,0 +1,130 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using UniSky.Controls.Sheet; +using Windows.ApplicationModel.Core; +using Windows.Foundation; +using Windows.Foundation.Metadata; +using Windows.UI.Core; +using Windows.UI.Core.Preview; +using Windows.UI.ViewManagement; +using Windows.UI.WindowManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Hosting; + +namespace UniSky.Services; + +internal class SheetService : ISheetService +{ + private readonly SheetRootControl sheetRoot; + public SheetService() + { + //this.sheetRoot = Window.Current.Content.FindDescendant(); + } + + public async Task ShowAsync(object parameter = null) where T : SheetControl, new() + { + if (sheetRoot != null) + { + var safeArea = ServiceContainer.Scoped.GetRequiredService(); + + var control = new T(); + var controller = new SheetRootController(sheetRoot, safeArea); + + control.SetSheetController(controller); + + sheetRoot.ShowSheet(control, parameter); + return controller; + } + else + { + if (ApiInformation.IsApiContractPresent(typeof(UniversalApiContract).FullName, 8, 0)) + { + return await ShowSheetForAppWindow(parameter); + } + else + { + return await ShowSheetForCoreWindow(parameter); + } + } + } + + private static async Task ShowSheetForAppWindow(object parameter) where T : SheetControl, new() + { + var control = new T(); + var appWindow = await AppWindow.TryCreateAsync(); + var controller = new AppWindowSheetController(appWindow, control); + control.SetSheetController(controller); + control.InvokeShowing(parameter); + + ElementCompositionPreview.SetAppWindowContent(appWindow, control); + + appWindow.CloseRequested += async (o, e) => + { + var deferral = e.GetDeferral(); + if (!await control.InvokeHidingAsync()) + e.Cancel = true; + + deferral.Complete(); + }; + + appWindow.Closed += (o, e) => + { + control.InvokeHidden(); + }; + + appWindow.RequestSize(new Size(320, 400)); + appWindow.RequestMoveAdjacentToCurrentView(); + await appWindow.TryShowAsync(); + + control.InvokeShown(); + + return controller; + } + + private static async Task ShowSheetForCoreWindow(object parameter) where T : SheetControl, new() + { + ISheetController controller = null; + + var view = CoreApplication.CreateNewView(); + var newViewId = 0; + await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + // a surprise tool that'll help us later + // (instanciating this now so it handles min. window sizes, etc.) + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); + + var control = new T(); + controller = new ApplicationViewSheetController(control, ApplicationView.GetForCurrentView(), safeAreaService); + control.SetSheetController(controller); + + SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += async (o, e) => + { + var deferral = e.GetDeferral(); + if (!await control.InvokeHidingAsync()) + e.Handled = true; + + deferral.Complete(); + }; + + Window.Current.Closed += (o, ev) => + { + control.InvokeHidden(); + }; + + Window.Current.Content = control; + Window.Current.Activate(); + + control.InvokeShowing(parameter); + control.InvokeShown(); + + newViewId = ApplicationView.GetForCurrentView().Id; + }); + + var prefs = ViewModePreferences.CreateDefault(ApplicationViewMode.Default); + await ApplicationViewSwitcher.TryShowAsViewModeAsync(newViewId, ApplicationViewMode.Default, prefs); + + return controller; + } +} diff --git a/UniSky/Services/SheetService.cs b/UniSky/Services/SheetService.cs deleted file mode 100644 index 09264c3..0000000 --- a/UniSky/Services/SheetService.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using UniSky.Controls.Sheet; -using Windows.UI.Xaml; - -namespace UniSky.Services -{ - internal class SheetService : ISheetService - { - private readonly SheetRootControl sheetRoot; - - public SheetService() - { - this.sheetRoot = Window.Current.Content.FindDescendant(); - } - - public async Task ShowAsync(object parameter = null) where T : SheetControl, new() - { - var control = new T(); - sheetRoot.ShowSheet(control, parameter); - } - - /// - /// Tries to close the currently active sheet, returns a boolean to indicate if the sheet was hidden or not - /// - /// - public async Task TryCloseAsync() - { - return await sheetRoot.HideSheetAsync(); - } - } -} diff --git a/UniSky/Themes/ThemeResources.cs b/UniSky/Themes/ThemeResources.cs index 7c6271c..d177def 100644 --- a/UniSky/Themes/ThemeResources.cs +++ b/UniSky/Themes/ThemeResources.cs @@ -1,5 +1,6 @@ using System; using CommunityToolkit.Mvvm.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using UniSky.Services; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls.Primitives; @@ -10,7 +11,7 @@ internal class ThemeResources : ResourceDictionary { public ThemeResources() { - var theme = Ioc.Default.GetRequiredService() + var theme = ServiceContainer.Scoped.GetRequiredService() .GetTheme(); Uri uri = theme switch diff --git a/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs b/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs index 7ee069c..4811fcf 100644 --- a/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs +++ b/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs @@ -7,6 +7,7 @@ using UniSky.Controls.Compose; using UniSky.Extensions; using UniSky.Helpers; +using Windows.Foundation; using Windows.Foundation.Metadata; using Windows.Graphics.Imaging; using Windows.Storage; @@ -108,6 +109,9 @@ private async Task AddAltTextAsync() { var previousAltText = AltText; var altTextDialog = new ComposeAddAltTextDialog(this); + if (parent.SheetController != null && ApiInformation.IsApiContractPresent(typeof(UniversalApiContract).FullName, 8)) + altTextDialog.XamlRoot = parent.SheetController.Root.XamlRoot; + if (await altTextDialog.ShowAsync() != ContentDialogResult.Primary) AltText = previousAltText; } diff --git a/UniSky/ViewModels/Compose/ComposeViewModel.cs b/UniSky/ViewModels/Compose/ComposeViewModel.cs index 5992da9..f30488e 100644 --- a/UniSky/ViewModels/Compose/ComposeViewModel.cs +++ b/UniSky/ViewModels/Compose/ComposeViewModel.cs @@ -67,15 +67,20 @@ public bool HasReply => ReplyTo != null; public ObservableCollection AttachedFiles { get; } + public bool HasAttachments => AttachedFiles.Count > 0; + public ISheetController SheetController { get; } + public ComposeViewModel(IProtocolService protocolService, + ISheetController sheetController, ILogger logger, PostViewModel replyTo = null) { this.protocolService = protocolService; this.logger = logger; + this.SheetController = sheetController; this.resources = ResourceLoader.GetForCurrentView(); this.Text = ""; @@ -179,8 +184,7 @@ private async Task GetImageEmbed() [RelayCommand] private async Task Hide() { - var sheetService = Ioc.Default.GetRequiredService(); - await sheetService.TryCloseAsync(); + await this.SheetController.TryHideSheetAsync(); } [RelayCommand] @@ -354,7 +358,7 @@ private void AddFile(IStorageFile storageFile, bool isTemporary) throw new InvalidOperationException(resources.GetString("E_VideosUnsupported")); } - AttachedFiles.Add(ActivatorUtilities.CreateInstance(Ioc.Default, this, storageFile, type, isTemporary)); + AttachedFiles.Add(ActivatorUtilities.CreateInstance(ServiceContainer.Scoped, this, storageFile, type, isTemporary)); } internal void UpdateLoading(ComposeViewAttachmentViewModel attachmentViewModel, bool value) diff --git a/UniSky/ViewModels/FeedsViewModel.cs b/UniSky/ViewModels/FeedsViewModel.cs index 7c049fa..d932248 100644 --- a/UniSky/ViewModels/FeedsViewModel.cs +++ b/UniSky/ViewModels/FeedsViewModel.cs @@ -8,6 +8,7 @@ using FishyFlip.Lexicon.App.Bsky.Feed; using FishyFlip.Models; using FishyFlip.Tools; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Toolkit.Uwp.UI.Extensions; using UniSky.Controls.Compose; @@ -42,7 +43,7 @@ public FeedsViewModel( [RelayCommand] public async Task Post() { - var sheetsService = Ioc.Default.GetRequiredService(); + var sheetsService = ServiceContainer.Scoped.GetRequiredService(); await sheetsService.ShowAsync(); } diff --git a/UniSky/ViewModels/HomeViewModel.cs b/UniSky/ViewModels/HomeViewModel.cs index e7df101..0195910 100644 --- a/UniSky/ViewModels/HomeViewModel.cs +++ b/UniSky/ViewModels/HomeViewModel.cs @@ -10,6 +10,7 @@ using FishyFlip.Lexicon.Com.Atproto.Server; using FishyFlip.Models; using FishyFlip.Tools; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using UniSky.Extensions; using UniSky.Helpers; @@ -190,7 +191,7 @@ private async Task RefreshSessionAsync(ATProtocol protocol) throw new InvalidOperationException("Authentication failed!"); var sessionModel2 = new SessionModel(true, sessionModel.Service, authSession2.Session, authSession2); - var sessionService = Ioc.Default.GetRequiredService(); + var sessionService = ServiceContainer.Scoped.GetRequiredService(); sessionService.SaveSession(sessionModel2); protocolService.SetProtocol(protocol); diff --git a/UniSky/ViewModels/Post/PostViewModel.cs b/UniSky/ViewModels/Post/PostViewModel.cs index ea68da8..aecb80c 100644 --- a/UniSky/ViewModels/Post/PostViewModel.cs +++ b/UniSky/ViewModels/Post/PostViewModel.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using System.Web; using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using FishyFlip.Lexicon; using FishyFlip.Lexicon.App.Bsky.Actor; @@ -15,6 +14,7 @@ using FishyFlip.Models; using FishyFlip.Tools; using Humanizer; +using Microsoft.Extensions.DependencyInjection; using UniSky.Controls.Compose; using UniSky.Helpers; using UniSky.Pages; @@ -161,7 +161,7 @@ public PostViewModel(PostView view, bool hasChild = false) [RelayCommand] private async Task LikeAsync() { - var protocol = Ioc.Default.GetRequiredService() + var protocol = ServiceContainer.Scoped.GetRequiredService() .Protocol; if (IsLiked) @@ -189,7 +189,7 @@ private async Task LikeAsync() [RelayCommand] private void OpenProfile(UIElement element) { - var service = Ioc.Default.GetRequiredService() + var service = ServiceContainer.Scoped.GetRequiredService() .GetNavigationService("Home"); service.Navigate(this.view.Author, new ContinuumNavigationTransitionInfo() { ExitElement = element }); @@ -199,7 +199,7 @@ private void OpenProfile(UIElement element) [RelayCommand] private async Task ReplyAsync() { - var service = Ioc.Default.GetRequiredService(); + var service = ServiceContainer.Scoped.GetRequiredService(); await service.ShowAsync(this); } diff --git a/UniSky/ViewModels/Profile/ProfilePageViewModel.cs b/UniSky/ViewModels/Profile/ProfilePageViewModel.cs index 3444bfe..852ad88 100644 --- a/UniSky/ViewModels/Profile/ProfilePageViewModel.cs +++ b/UniSky/ViewModels/Profile/ProfilePageViewModel.cs @@ -13,6 +13,7 @@ using FishyFlip.Models; using FishyFlip.Tools; using Humanizer; +using Microsoft.Extensions.DependencyInjection; using UniSky.Extensions; using UniSky.Helpers.Interop; using UniSky.Services; @@ -82,7 +83,7 @@ public ProfilePageViewModel(ATDid did) public ProfilePageViewModel(ATObject profile) : base(profile) { - var protocol = Ioc.Default.GetRequiredService(); + var protocol = ServiceContainer.Scoped.GetRequiredService(); if (profile is ProfileViewDetailed detailed) { Populate(detailed); @@ -108,7 +109,7 @@ private async Task LoadAsync() try { - var protocol = Ioc.Default.GetRequiredService(); + var protocol = ServiceContainer.Scoped.GetRequiredService(); var profile = (await protocol.Protocol.GetProfileAsync(this.id).ConfigureAwait(false)) .HandleResult(); @@ -180,7 +181,7 @@ private async Task CalculateBannerLightnessAsync(ProfileViewDetailed profile) { if (string.IsNullOrWhiteSpace(profile.Banner)) return; - var protocol = Ioc.Default.GetRequiredService(); + var protocol = ServiceContainer.Scoped.GetRequiredService(); using var banner = await protocol.Protocol.Client.GetStreamAsync(profile.Banner) .ConfigureAwait(false); using var memoryStream = new MemoryStream(); @@ -195,7 +196,7 @@ private async Task CalculateBannerLightnessAsync(ProfileViewDetailed profile) syncContext.Post(() => { - var safeAreaService = Ioc.Default.GetRequiredService(); + var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); if (IsLight == true) { safeAreaService.SetTitlebarTheme(ElementTheme.Dark); From 7ef4708edfa4550c96853ae6ecd8ae0535ce5e3e Mon Sep 17 00:00:00 2001 From: Thomas May Date: Fri, 6 Dec 2024 00:21:03 +0000 Subject: [PATCH 02/11] More sheet windowing --- UniSky/App.xaml.cs | 2 ++ UniSky/Controls/Sheet/SheetControl.cs | 21 ++++++++++++++++++- UniSky/Controls/Sheet/SheetControl.xaml | 2 ++ UniSky/Helpers/Composition/BirdAnimation.cs | 3 +++ UniSky/Package.appxmanifest | 2 +- .../Sheet/ApplicationViewSheetController.cs | 5 +++-- UniSky/Services/Sheet/SheetService.cs | 9 ++++---- UniSky/Templates/custom-twitter/Icons.xaml | 2 +- UniSky/UniSky.csproj | 12 ++++++++--- 9 files changed, 46 insertions(+), 12 deletions(-) diff --git a/UniSky/App.xaml.cs b/UniSky/App.xaml.cs index a68c7fc..375cbd5 100644 --- a/UniSky/App.xaml.cs +++ b/UniSky/App.xaml.cs @@ -66,6 +66,8 @@ private void ConfigureServices() ServiceContainer.Default.ConfigureServices(collection.BuildServiceProvider()); Configurator.Formatters.Register("en", (locale) => new ShortTimespanFormatter("en")); + Configurator.Formatters.Register("en-GB", (locale) => new ShortTimespanFormatter("en")); + Configurator.Formatters.Register("en-US", (locale) => new ShortTimespanFormatter("en")); } protected override void OnActivated(IActivatedEventArgs args) diff --git a/UniSky/Controls/Sheet/SheetControl.cs b/UniSky/Controls/Sheet/SheetControl.cs index f662517..f7d79d2 100644 --- a/UniSky/Controls/Sheet/SheetControl.cs +++ b/UniSky/Controls/Sheet/SheetControl.cs @@ -12,6 +12,7 @@ using UniSky.Services; using Windows.ApplicationModel; using Windows.Foundation; +using Windows.UI.ViewManagement; using Windows.UI.WindowManagement; using Windows.UI.WindowManagement.Preview; using Windows.UI.Xaml; @@ -220,6 +221,10 @@ protected override void OnApplyTemplate() Controller.SafeAreaService.SafeAreaUpdated += OnSafeAreaUpdated; Controller.SafeAreaService.SetTitleBar(titleBarDragArea); + var inputPane = InputPane.GetForCurrentView(); + inputPane.Showing += OnInputPaneShowing; + inputPane.Hiding += OnInputPaneHiding; + this.SizeChanged += OnSizeChanged; } else @@ -264,6 +269,20 @@ internal void InvokeHidden() Hidden?.Invoke(this, new RoutedEventArgs()); } + private void OnInputPaneShowing(InputPane sender, InputPaneVisibilityEventArgs args) + { + var ButtonsGrid = (Grid)this.FindDescendantByName("ButtonsGrid"); + ButtonsGrid.Margin = new Thickness(0, 0, 0, args.OccludedRect.Height); + args.EnsuredFocusedElementInView = true; + } + + private void OnInputPaneHiding(InputPane sender, InputPaneVisibilityEventArgs args) + { + var ButtonsGrid = (Grid)this.FindDescendantByName("ButtonsGrid"); + ButtonsGrid.Margin = new Thickness(0, 0, 0, args.OccludedRect.Height); + args.EnsuredFocusedElementInView = true; + } + private void OnSafeAreaUpdated(object sender, SafeAreaUpdatedEventArgs e) { var titleBarGrid = (Grid)this.FindDescendantByName("TitleBarGrid"); @@ -276,7 +295,7 @@ private void OnSafeAreaUpdated(object sender, SafeAreaUpdatedEventArgs e) else { titleBarGrid.Height = 42; - titleBarGrid.Padding = new Thickness(0, e.SafeArea.Bounds.Top, 0, 0); + titleBarGrid.Padding = new Thickness(0, e.SafeArea.Bounds.Top, 0, 4); } Margin = new Thickness(e.SafeArea.Bounds.Left, 0, e.SafeArea.Bounds.Right, e.SafeArea.Bounds.Bottom); diff --git a/UniSky/Controls/Sheet/SheetControl.xaml b/UniSky/Controls/Sheet/SheetControl.xaml index 80f6bbb..15868ef 100644 --- a/UniSky/Controls/Sheet/SheetControl.xaml +++ b/UniSky/Controls/Sheet/SheetControl.xaml @@ -72,6 +72,7 @@ Visibility="{TemplateBinding PrimaryButtonVisibility}" Command="{TemplateBinding PrimaryButtonCommand}" IsEnabled="{TemplateBinding IsPrimaryButtonEnabled}" + Padding="{ThemeResource ButtonPadding}" Margin="8"> @@ -88,6 +89,7 @@ Visibility="{TemplateBinding SecondaryButtonVisibility}" Command="{TemplateBinding SecondaryButtonCommand}" IsEnabled="{TemplateBinding IsSecondaryButtonEnabled}" + Padding="{ThemeResource ButtonPadding}" Margin="8"> diff --git a/UniSky/Helpers/Composition/BirdAnimation.cs b/UniSky/Helpers/Composition/BirdAnimation.cs index 8b5b071..cfbcb5e 100644 --- a/UniSky/Helpers/Composition/BirdAnimation.cs +++ b/UniSky/Helpers/Composition/BirdAnimation.cs @@ -59,12 +59,14 @@ public static void RunBirdAnimation(FrameworkElement frame) var scaleAnimation = compositor.CreateVector2KeyFrameAnimation(); scaleAnimation.InsertKeyFrame(1.0f, new Vector2(scale, scale), ease); + scaleAnimation.DelayTime = TimeSpan.FromSeconds(0.15); scaleAnimation.Duration = TimeSpan.FromSeconds(0.15); scaleAnimation.Target = "Scale"; group.Add(scaleAnimation); var offsetAnimation = compositor.CreateVector2KeyFrameAnimation(); offsetAnimation.InsertKeyFrame(1.0f, new Vector2(offsetX, offsetY - (6 * scale)), ease); + offsetAnimation.DelayTime = TimeSpan.FromSeconds(0.15); offsetAnimation.Duration = TimeSpan.FromSeconds(0.15); offsetAnimation.Target = "Offset"; group.Add(offsetAnimation); @@ -72,6 +74,7 @@ public static void RunBirdAnimation(FrameworkElement frame) var scaleAnimation2 = compositor.CreateVector3KeyFrameAnimation(); scaleAnimation2.InsertKeyFrame(0.5f, new Vector3(1.1f), ease); scaleAnimation2.InsertKeyFrame(1.0f, new Vector3(1.0f), ease2); + scaleAnimation2.DelayTime = TimeSpan.FromSeconds(0.15); scaleAnimation2.Duration = TimeSpan.FromSeconds(0.3); scaleAnimation2.Target = "Scale"; diff --git a/UniSky/Package.appxmanifest b/UniSky/Package.appxmanifest index a2935d5..bc9adf4 100644 --- a/UniSky/Package.appxmanifest +++ b/UniSky/Package.appxmanifest @@ -11,7 +11,7 @@ + Version="1.0.139.0" /> diff --git a/UniSky/Services/Sheet/ApplicationViewSheetController.cs b/UniSky/Services/Sheet/ApplicationViewSheetController.cs index c0b1ff0..837350d 100644 --- a/UniSky/Services/Sheet/ApplicationViewSheetController.cs +++ b/UniSky/Services/Sheet/ApplicationViewSheetController.cs @@ -7,7 +7,8 @@ namespace UniSky.Services; internal class ApplicationViewSheetController(SheetControl control, - ApplicationView appView, + int hostViewId, + int viewId, ISafeAreaService safeAreaService) : ISheetController { public UIElement Root => control; @@ -18,7 +19,7 @@ public async Task TryHideSheetAsync() { if (await control.InvokeHidingAsync()) { - await appView.TryConsolidateAsync(); + await ApplicationViewSwitcher.SwitchAsync(hostViewId, viewId, ApplicationViewSwitchingOptions.ConsolidateViews); return true; } diff --git a/UniSky/Services/Sheet/SheetService.cs b/UniSky/Services/Sheet/SheetService.cs index 649dee1..91b3794 100644 --- a/UniSky/Services/Sheet/SheetService.cs +++ b/UniSky/Services/Sheet/SheetService.cs @@ -87,16 +87,19 @@ public SheetService() { ISheetController controller = null; + var currentViewId = ApplicationView.GetForCurrentView().Id; var view = CoreApplication.CreateNewView(); var newViewId = 0; await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { + newViewId = ApplicationView.GetForCurrentView().Id; + // a surprise tool that'll help us later // (instanciating this now so it handles min. window sizes, etc.) var safeAreaService = ServiceContainer.Scoped.GetRequiredService(); var control = new T(); - controller = new ApplicationViewSheetController(control, ApplicationView.GetForCurrentView(), safeAreaService); + controller = new ApplicationViewSheetController(control, currentViewId, newViewId, safeAreaService); control.SetSheetController(controller); SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += async (o, e) => @@ -119,11 +122,9 @@ await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => control.InvokeShowing(parameter); control.InvokeShown(); - newViewId = ApplicationView.GetForCurrentView().Id; }); - var prefs = ViewModePreferences.CreateDefault(ApplicationViewMode.Default); - await ApplicationViewSwitcher.TryShowAsViewModeAsync(newViewId, ApplicationViewMode.Default, prefs); + await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId, ViewSizePreference.UseLess); return controller; } diff --git a/UniSky/Templates/custom-twitter/Icons.xaml b/UniSky/Templates/custom-twitter/Icons.xaml index 1288865..c95a87c 100644 --- a/UniSky/Templates/custom-twitter/Icons.xaml +++ b/UniSky/Templates/custom-twitter/Icons.xaml @@ -4,6 +4,6 @@ m64.67 6.525c-1.74 2.61-4.06 4.93-6.525 6.815 0 .58 0 1.16 0 1.74 0 17.4-13.195 37.555-37.555 37.555-7.395 0-14.355-2.175-20.155-5.945 1.015.145 2.03.145 3.19.145 6.235 0 11.89-2.175 16.385-5.655-5.8-.145-10.585-3.915-12.325-9.135.87.145 1.595.29 2.465.29 1.16 0 2.32-.145 3.48-.435C7.54 30.595 2.9 25.23 2.9 18.85c0 0 0-.145 0-.145 1.74 1.015 3.77 1.595 5.945 1.595C5.365 17.98 3.045 13.92 3.045 9.425c0-2.465.58-4.64 1.74-6.67 6.525 7.975 16.24 13.195 27.115 13.775-.29-1.015-.29-2.03-.29-3.045 0-7.25 5.945-13.195 13.195-13.195 3.77 0 7.25 1.595 9.57 4.205 3.045-.58 5.8-1.74 8.41-3.19-1.015 3.045-3.045 5.655-5.8 7.25 2.61-.29 5.22-1.015 7.54-2.03z 64 - 38 + 48 diff --git a/UniSky/UniSky.csproj b/UniSky/UniSky.csproj index 3bebc9d..0a6ba14 100644 --- a/UniSky/UniSky.csproj +++ b/UniSky/UniSky.csproj @@ -221,7 +221,6 @@ - @@ -229,9 +228,16 @@ FeedTemplates.xaml - + + + + - + + + + + From ff7003ff9149c99c2a588dfb7007c1ba09426d73 Mon Sep 17 00:00:00 2001 From: Thomas May Date: Fri, 6 Dec 2024 00:54:39 +0000 Subject: [PATCH 03/11] playing with fire :) --- UniSky/Package.appxmanifest | 2 +- UniSky/UniSky.csproj | 13 +++++++++++++ .../Compose/ComposeViewAttachmentViewModel.cs | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/UniSky/Package.appxmanifest b/UniSky/Package.appxmanifest index bc9adf4..0f35a74 100644 --- a/UniSky/Package.appxmanifest +++ b/UniSky/Package.appxmanifest @@ -11,7 +11,7 @@ + Version="1.0.140.0" /> diff --git a/UniSky/UniSky.csproj b/UniSky/UniSky.csproj index 0a6ba14..f6c96af 100644 --- a/UniSky/UniSky.csproj +++ b/UniSky/UniSky.csproj @@ -553,4 +553,17 @@ --> + + + + 10.0.15063.0 + + + + + 10.0.16299.0 + + \ No newline at end of file diff --git a/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs b/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs index 4811fcf..a4b0d68 100644 --- a/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs +++ b/UniSky/ViewModels/Compose/ComposeViewAttachmentViewModel.cs @@ -155,6 +155,8 @@ private async Task CompressImageAsync(IStorageFile input, int size = 2048) encoder.SetSoftwareBitmap(await decoder.GetSoftwareBitmapAsync()); encoder.BitmapTransform.ScaledWidth = (uint)Math.Ceiling(width); encoder.BitmapTransform.ScaledHeight = (uint)Math.Ceiling(height); + encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant; + await encoder.FlushAsync(); } From 447fd4d51b165fbf418f51a798e04a38242d5b0f Mon Sep 17 00:00:00 2001 From: Thomas May Date: Fri, 6 Dec 2024 01:04:32 +0000 Subject: [PATCH 04/11] Consistent window title casing --- UniSky/Resources/en-GB/Resources.resw | 2 +- UniSky/Resources/en-GB/custom-twitter/Resources.resw | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UniSky/Resources/en-GB/Resources.resw b/UniSky/Resources/en-GB/Resources.resw index 0ea176d..1fd284e 100644 --- a/UniSky/Resources/en-GB/Resources.resw +++ b/UniSky/Resources/en-GB/Resources.resw @@ -154,7 +154,7 @@ Unisky - Unisky + UNISKY d diff --git a/UniSky/Resources/en-GB/custom-twitter/Resources.resw b/UniSky/Resources/en-GB/custom-twitter/Resources.resw index 9eea7ef..d19e258 100644 --- a/UniSky/Resources/en-GB/custom-twitter/Resources.resw +++ b/UniSky/Resources/en-GB/custom-twitter/Resources.resw @@ -133,7 +133,7 @@ Retweet - Twitter + TWITTER Trying to reply to something that isn't a tweet? From baece310d08f7f67d9d2e1309a22d0663fce9776 Mon Sep 17 00:00:00 2001 From: Thomas May Date: Fri, 6 Dec 2024 01:47:18 +0000 Subject: [PATCH 05/11] Revert "Consistent window title casing" This reverts commit 447fd4d51b165fbf418f51a798e04a38242d5b0f. --- UniSky/Resources/en-GB/Resources.resw | 2 +- UniSky/Resources/en-GB/custom-twitter/Resources.resw | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UniSky/Resources/en-GB/Resources.resw b/UniSky/Resources/en-GB/Resources.resw index 1fd284e..0ea176d 100644 --- a/UniSky/Resources/en-GB/Resources.resw +++ b/UniSky/Resources/en-GB/Resources.resw @@ -154,7 +154,7 @@ Unisky - UNISKY + Unisky d diff --git a/UniSky/Resources/en-GB/custom-twitter/Resources.resw b/UniSky/Resources/en-GB/custom-twitter/Resources.resw index d19e258..9eea7ef 100644 --- a/UniSky/Resources/en-GB/custom-twitter/Resources.resw +++ b/UniSky/Resources/en-GB/custom-twitter/Resources.resw @@ -133,7 +133,7 @@ Retweet - TWITTER + Twitter Trying to reply to something that isn't a tweet? From 7ebd6813e0dee643359403ca2727544f2eab19b9 Mon Sep 17 00:00:00 2001 From: Thomas May Date: Fri, 6 Dec 2024 02:04:05 +0000 Subject: [PATCH 06/11] don't always scale damnit --- UniSky/Helpers/SizeHelpers.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UniSky/Helpers/SizeHelpers.cs b/UniSky/Helpers/SizeHelpers.cs index 678a548..cc66f1d 100644 --- a/UniSky/Helpers/SizeHelpers.cs +++ b/UniSky/Helpers/SizeHelpers.cs @@ -10,6 +10,9 @@ internal class SizeHelpers { public static void Scale(ref double width, ref double height, double maxWidth, double maxHeight, StretchMode mode = StretchMode.Uniform) { + if (width <= maxWidth && height <= maxHeight) + return; + if (mode == StretchMode.None) { return; From a5c5adfb236e6a6d152d477689626c0222ab1389 Mon Sep 17 00:00:00 2001 From: Thomas May Date: Fri, 6 Dec 2024 17:37:15 +0000 Subject: [PATCH 07/11] bug fixes and improved placement logic --- UniSky/Controls/Compose/ComposeSheet.xaml | 24 +++----- UniSky/Controls/Sheet/SheetControl.cs | 2 +- UniSky/Controls/Sheet/SheetControl.xaml | 8 +-- UniSky/DataTemplates/FeedTemplates.xaml | 2 +- UniSky/Services/SettingsService.cs | 8 +++ UniSky/Services/Sheet/SheetService.cs | 68 +++++++++++++++++++++-- UniSky/Themes/ThemeResources.cs | 14 ++++- UniSky/UniSky.csproj | 42 ++++++++++++-- 8 files changed, 131 insertions(+), 37 deletions(-) diff --git a/UniSky/Controls/Compose/ComposeSheet.xaml b/UniSky/Controls/Compose/ComposeSheet.xaml index 6edd777..71623ba 100644 --- a/UniSky/Controls/Compose/ComposeSheet.xaml +++ b/UniSky/Controls/Compose/ComposeSheet.xaml @@ -11,7 +11,7 @@ xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:compose="using:UniSky.ViewModels.Compose" - mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="320" + mc:Ignorable="d" d:DesignHeight="350" d:DesignWidth="480" Style="{StaticResource DefaultSheetControlStyle}" DataContext="{x:Bind ViewModel, Mode=OneWay}" PrimaryButtonCommand="{x:Bind ViewModel.PostCommand}" @@ -96,7 +96,7 @@ Height="48" Margin="0,0,8,0" VerticalAlignment="Top" - ProfilePicture="{x:Bind ViewModel.ReplyTo.Author.AvatarUrl, Mode=OneWay}"/> + ProfilePicture="{Binding ReplyTo.Author.AvatarUrl, Mode=OneWay}"/> - - - - - - - - - + @@ -68,7 +66,7 @@