Skip to content
Merged
58 changes: 51 additions & 7 deletions src/Files.App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using CommunityToolkit.WinUI.Helpers;
using CommunityToolkit.WinUI.Notifications;
using Files.App.Helpers;
using Files.App.Services.DateTimeFormatter;
using Files.App.Services.Settings;
using Files.App.Storage.FtpStorage;
Expand All @@ -21,6 +22,8 @@
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.Windows.AppLifecycle;
using System.IO;
using System.Text;
Expand All @@ -40,7 +43,7 @@ public partial class App : Application
private static bool ShowErrorNotification = false;
public static string OutputPath { get; set; }
public static CommandBarFlyout? LastOpenedFlyout { get; set; }
public static TaskCompletionSource? SplashScreenLoadingTCS { get; set; }
public static TaskCompletionSource? SplashScreenLoadingTCS { get; private set; }

public static StorageHistoryWrapper HistoryWrapper { get; } = new();
public static AppModel AppModel { get; private set; }
Expand Down Expand Up @@ -176,12 +179,7 @@ await Task.WhenAll(
FileTagsHelper.UpdateTagsDb();
});

// Check for required updates
var updateService = Ioc.Default.GetRequiredService<IUpdateService>();
await updateService.CheckForUpdates();
await updateService.DownloadMandatoryUpdates();
await updateService.CheckAndUpdateFilesLauncherAsync();
await updateService.CheckLatestReleaseNotesAsync();
await CheckForRequiredUpdates();

static async Task OptionalTask(Task task, bool condition)
{
Expand All @@ -190,6 +188,15 @@ static async Task OptionalTask(Task task, bool condition)
}
}

private static async Task CheckForRequiredUpdates()
{
var updateService = Ioc.Default.GetRequiredService<IUpdateService>();
await updateService.CheckForUpdates();
await updateService.DownloadMandatoryUpdates();
await updateService.CheckAndUpdateFilesLauncherAsync();
await updateService.CheckLatestReleaseNotesAsync();
}

/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
Expand Down Expand Up @@ -300,6 +307,43 @@ private async void Window_Closed(object sender, WindowEventArgs args)
return;
}

if (Ioc.Default.GetRequiredService<IUserSettingsService>().GeneralSettingsService.LeaveAppRunning &&
!Process.GetProcessesByName("Files").Any(x => x.Id != Process.GetCurrentProcess().Id))
{
// Close open content dialogs
UIHelpers.CloseAllDialogs();

// Cache the window instead of closing it
MainWindow.Instance.AppWindow.Hide();
args.Handled = true;

// Save and close all tabs
SaveSessionTabs();
MainPageViewModel.AppInstances.ForEach(tabItem => tabItem.Unload());
MainPageViewModel.AppInstances.Clear();
await Task.Delay(100);

// Wait for all properties windows to close
await FilePropertiesHelpers.WaitClosingAll();

// Sleep current instance
Program.Pool = new(0, 1, "Files-Instance");
Thread.Yield();
if (Program.Pool.WaitOne())
{
// Resume the instance
Program.Pool.Dispose();
MainWindow.Instance.AppWindow.Show();
MainWindow.Instance.Activate();

_ = CheckForRequiredUpdates();

MainWindow.Instance.EnsureWindowIsInitialized().Navigate(typeof(MainPage), null, new SuppressNavigationTransitionInfo());
}

return;
}

// Method can take a long time, make sure the window is hidden
await Task.Yield();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using CommunityToolkit.Mvvm.ComponentModel;
using Files.App.UserControls.MultitaskingControl;
using Files.App.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Files.App.Data.Contexts
{
Expand Down Expand Up @@ -46,7 +43,8 @@ private void AppInstances_CollectionChanged(object? sender, NotifyCollectionChan
}
private void AppModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
UpdateCurrentTabIndex();
if (e.PropertyName is nameof(AppModel.TabStripSelectedIndex))
UpdateCurrentTabIndex();
}
private void BaseMultitaskingControl_OnLoaded(object? sender, IMultitaskingControl control)
{
Expand Down
18 changes: 6 additions & 12 deletions src/Files.App/Data/Models/AppModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,13 @@ public int TabStripSelectedIndex
get => tabStripSelectedIndex;
set
{
if (value >= 0)
{
if (tabStripSelectedIndex != value)
{
SetProperty(ref tabStripSelectedIndex, value);
}
SetProperty(ref tabStripSelectedIndex, value);

if (value < MainPageViewModel.AppInstances.Count)
{
Frame rootFrame = (Frame)MainWindow.Instance.Content;
var mainView = (MainPage)rootFrame.Content;
mainView.ViewModel.SelectedTabItem = MainPageViewModel.AppInstances[value];
}
if (value >= 0 && value < MainPageViewModel.AppInstances.Count)
{
Frame rootFrame = (Frame)MainWindow.Instance.Content;
var mainView = (MainPage)rootFrame.Content;
mainView.ViewModel.SelectedTabItem = MainPageViewModel.AppInstances[value];
}
}
}
Expand Down
12 changes: 1 addition & 11 deletions src/Files.App/Helpers/UI/UIHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,10 @@
// Licensed under the MIT License. See the LICENSE.

using CommunityToolkit.WinUI.Notifications;
using Files.App.Extensions;
using Files.App.Utils.Shell;
using Files.Core.ViewModels.Dialogs;
using Files.Shared;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Notifications;

namespace Files.App.Helpers
Expand Down Expand Up @@ -130,7 +120,7 @@ private static ContentDialog SetContentDialogRoot(ContentDialog contentDialog)

public static void CloseAllDialogs()
{
var openedDialogs = VisualTreeHelper.GetOpenPopups(MainWindow.Instance);
var openedDialogs = VisualTreeHelper.GetOpenPopupsForXamlRoot(MainWindow.Instance.Content.XamlRoot);

foreach (var item in openedDialogs)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Files.App/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
using Files.App.UserControls.MultitaskingControl;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
using System.IO;
using System.Runtime.InteropServices;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Storage;
Expand Down Expand Up @@ -176,7 +176,7 @@ public async Task InitializeApplication(object activatedEventArgs)
rootFrame.Navigate(typeof(MainPage), null, new SuppressNavigationTransitionInfo());
}

private Frame EnsureWindowIsInitialized()
public Frame EnsureWindowIsInitialized()
{
// NOTE:
// Do not repeat app initialization when the Window already has content,
Expand Down
14 changes: 14 additions & 0 deletions src/Files.App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ namespace Files.App
{
internal class Program
{
public static Semaphore Pool;

static Program()
{
Pool = new(0, 1, "Files-Instance", out var isNew);
if (!isNew)
{
// Resume cached instance
Pool.Release();
Environment.Exit(0);
}
Pool.Dispose();
}

// Note:
// We can't declare Main to be async because in a WinUI app
// This prevents Narrator from reading XAML elements
Expand Down
7 changes: 7 additions & 0 deletions src/Files.App/Services/Settings/GeneralSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ public bool ShowOpenInNewPane
set => Set(value);
}

public bool LeaveAppRunning
{
get => Get(true);
set => Set(value);
}

public FileNameConflictResolveOptionType ConflictsResolveOption
{
get => (FileNameConflictResolveOptionType)Get((long)FileNameConflictResolveOptionType.GenerateNewName);
Expand Down Expand Up @@ -254,6 +260,7 @@ protected override void RaiseOnSettingChangedEvent(object sender, SettingChanged
case nameof(ShowOpenInNewTab):
case nameof(ShowOpenInNewWindow):
case nameof(ShowOpenInNewPane):
case nameof(LeaveAppRunning):
case nameof(ConflictsResolveOption):
case nameof(ShowHashesDictionary):
Analytics.TrackEvent($"Set {e.SettingName} to {e.NewValue}");
Expand Down
3 changes: 3 additions & 0 deletions src/Files.App/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -3447,4 +3447,7 @@
<data name="FilesRunningAsAdminContent" xml:space="preserve">
<value>Due to platform limitations, drag and drop isn't available when running Files as administrator.</value>
</data>
<data name="SettingsLeaveAppRunning" xml:space="preserve">
<value>Leave app running in the background when the window is closed</value>
</data>
</root>
22 changes: 21 additions & 1 deletion src/Files.App/Utils/Storage/Helpers/FilePropertiesHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public static class FilePropertiesHelpers
public static nint GetWindowHandle(Window w)
=> WinRT.Interop.WindowNative.GetWindowHandle(w);

private static int WindowCount = 0;
private static TaskCompletionSource? PropertiesWindowsClosingTCS;
private static BlockingCollection<WinUIEx.WindowEx> WindowCache = new();

/// <summary>
Expand Down Expand Up @@ -140,8 +142,10 @@ public static void OpenPropertiesWindow(object item, IShellPage associatedInstan
+ Math.Max(0, Math.Min(displayArea.WorkArea.Height - appWindow.Size.Height, pointerPosition.Y - displayArea.WorkArea.Y)),
};

appWindow.Move(appWindowPos);
if (Interlocked.Increment(ref WindowCount) == 1)
PropertiesWindowsClosingTCS = new();

appWindow.Move(appWindowPos);
appWindow.Show();
}

Expand All @@ -156,9 +160,19 @@ private static void PropertiesWindow_Closed(object sender, WindowEventArgs args)
window.AppWindow.Hide();
window.Content = null;
WindowCache.Add(window);

if (Interlocked.Decrement(ref WindowCount) == 0)
{
PropertiesWindowsClosingTCS!.TrySetResult();
PropertiesWindowsClosingTCS = null;
}
}
}

/// <summary>
/// Destroy all cached properties windows
/// </summary>
/// <returns></returns>
public static void DestroyCachedWindows()
{
while (WindowCache.TryTake(out var window))
Expand All @@ -167,5 +181,11 @@ public static void DestroyCachedWindows()
window.Close();
}
}

/// <summary>
/// Returns task to wait for all properties windows to close
/// </summary>
/// <returns>Task to wait</returns>
public static Task WaitClosingAll() => PropertiesWindowsClosingTCS?.Task ?? Task.CompletedTask;
}
}
14 changes: 14 additions & 0 deletions src/Files.App/ViewModels/Settings/AdvancedViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,20 @@ public bool CanOpenOnWindowsStartup
set => SetProperty(ref canOpenOnWindowsStartup, value);
}

public bool LeaveAppRunning
{
get => UserSettingsService.GeneralSettingsService.LeaveAppRunning;
set
{
if (value != UserSettingsService.GeneralSettingsService.LeaveAppRunning)
{
UserSettingsService.GeneralSettingsService.LeaveAppRunning = value;

OnPropertyChanged();
}
}
}

public async Task OpenFilesOnWindowsStartup()
{
var stateMode = await ReadState();
Expand Down
15 changes: 5 additions & 10 deletions src/Files.App/Views/Properties/MainPropertiesPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Files.App.Data.Parameters;
using Files.App.Helpers;
using Files.App.ViewModels;
using Files.App.ViewModels.Properties;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Threading.Tasks;
using Windows.System;
using Windows.UI;

Expand Down Expand Up @@ -45,16 +38,15 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
AppWindow = parameter.AppWindow;
Window = parameter.Window;

AppSettings = Ioc.Default.GetRequiredService<SettingsViewModel>();
AppSettings.ThemeModeChanged += AppSettings_ThemeModeChanged;

base.OnNavigatedTo(e);

MainPropertiesViewModel = new(Window, AppWindow, MainContentFrame, BaseProperties, parameter);
}

private void Page_Loaded(object sender, RoutedEventArgs e)
{
AppSettings = Ioc.Default.GetRequiredService<SettingsViewModel>();
AppSettings.ThemeModeChanged += AppSettings_ThemeModeChanged;
Window.Closed += Window_Closed;

UpdatePageLayout();
Expand Down Expand Up @@ -89,6 +81,9 @@ private void UpdatePageLayout()

private async void AppSettings_ThemeModeChanged(object? sender, EventArgs e)
{
if (Parent is null)
return;

await DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
((Frame)Parent).RequestedTheme = ThemeHelper.RootTheme;
Expand Down
11 changes: 11 additions & 0 deletions src/Files.App/Views/Settings/AdvancedPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@
</ToggleSwitch>
</local:SettingsBlockControl>

<!-- Leave App Running -->
<local:SettingsBlockControl Title="{helpers:ResourceString Name=SettingsLeaveAppRunning}" HorizontalAlignment="Stretch">
<local:SettingsBlockControl.Icon>
<FontIcon Glyph="&#xE8E6;" />
</local:SettingsBlockControl.Icon>
<ToggleSwitch
AutomationProperties.Name="{helpers:ResourceString Name=SettingsLeaveAppRunning}"
IsOn="{x:Bind ViewModel.LeaveAppRunning, Mode=TwoWay}"
Style="{StaticResource RightAlignedToggleSwitchStyle}" />
</local:SettingsBlockControl>

<!-- Experimental Settings -->
<TextBlock
Padding="0,16,0,4"
Expand Down
Loading