diff --git a/src/WinUI3/MainWindow.Dialogs.cs b/src/WinUI3/MainWindow.Dialogs.cs index 53f9e2e..d60a86f 100644 --- a/src/WinUI3/MainWindow.Dialogs.cs +++ b/src/WinUI3/MainWindow.Dialogs.cs @@ -1,12 +1,10 @@ -using System.Diagnostics; -using Microsoft.UI; +using Microsoft.UI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Windows.System; -using WinRT.Interop; namespace ModFolderCopier.WinUI; @@ -263,32 +261,6 @@ private async void OnNotificationRequested(object? sender, StatusNotification no } } - private async Task OpenExternalUrlAsync(string? url, string failureTitle) - { - if (string.IsNullOrWhiteSpace(url)) - { - await ShowMessageAsync( - T("Dialogs.ThisEntryDoesNotHaveA"), - failureTitle); - return; - } - - try - { - Process.Start(new ProcessStartInfo - { - FileName = url, - UseShellExecute = true - }); - } - catch (Exception ex) - { - await ShowMessageAsync( - T("Common.FailedToOpenLink") + ex.Message, - failureTitle); - } - } - private UIElement CreateInfoCard(string title, string body) { return new Border @@ -318,64 +290,16 @@ private UIElement CreateInfoCard(string title, string body) }; } - private Task PickFolderAsync(string? preferredPath = null) => - Task.FromResult(NativeFileDialog.PickFolder(WindowNative.GetWindowHandle(this))); + // Stage J finish, step 2: these live in DialogService; the window keeps one-line forwards for its own callers. + private Task OpenExternalUrlAsync(string? url, string failureTitle) => _services.Dialogs.OpenExternalUrlAsync(url, failureTitle); - private Task PickFileAsync(string[] extensions) => - Task.FromResult(NativeFileDialog.PickFile(WindowNative.GetWindowHandle(this), extensions)); + private Task PickFolderAsync(string? preferredPath = null) => _services.Dialogs.PickFolderAsync(preferredPath); - private void OpenDirectory(string? directory, string title) - { - try - { - if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory)) - { - _ = ShowMessageAsync( - title + T("Dialogs.DoesNotExistPleaseChooseIt"), - T("Common.InvalidPath")); - return; - } + private Task PickFileAsync(string[] extensions) => _services.Dialogs.PickFileAsync(extensions); - Process.Start(new ProcessStartInfo - { - FileName = directory, - UseShellExecute = true - }); - } - catch (Exception ex) - { - _ = ShowMessageAsync( - T("Dialogs.FailedToOpenFolder") + ex.Message, - T("Dialogs.OperationFailed")); - } - } + private void OpenDirectory(string? directory, string title) => _services.Dialogs.OpenDirectory(directory, title); - private void OpenFileLocation(string? filePath, string title) - { - try - { - if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath)) - { - _ = ShowMessageAsync( - title + T("Dialogs.DoesNotExistPleaseChooseIt"), - T("Common.InvalidPath")); - return; - } - - Process.Start(new ProcessStartInfo - { - FileName = "explorer.exe", - Arguments = "/select,\"" + filePath + "\"", - UseShellExecute = true - }); - } - catch (Exception ex) - { - _ = ShowMessageAsync( - T("Dialogs.FailedToOpenFileLocation") + ex.Message, - T("Dialogs.OperationFailed")); - } - } + private void OpenFileLocation(string? filePath, string title) => _services.Dialogs.OpenFileLocation(filePath, title); // The common dialogs, shown by DialogService (stage J8). private Task ShowMessageAsync(string content, string title) => _services.Dialogs.MessageAsync(title, content); diff --git a/src/WinUI3/MainWindow.OnlineHost.cs b/src/WinUI3/MainWindow.OnlineHost.cs index 05e14a8..7aa9914 100644 --- a/src/WinUI3/MainWindow.OnlineHost.cs +++ b/src/WinUI3/MainWindow.OnlineHost.cs @@ -25,13 +25,9 @@ private static HttpClient CreateHttpClient() bool IOnlineHost.IsOnlinePageShown => _currentPrimarySection == PrimarySection.Online; FrameworkElement IOnlineHost.Root => RootGrid; Windows.Foundation.Rect IOnlineHost.Bounds => Bounds; - Brush IOnlineHost.GetAppThemeBrush(string resourceKey) => GetAppThemeBrush(resourceKey); string IOnlineHost.Density => _interfaceDensity.ToString(); bool IOnlineHost.ReduceMotion => _reduceMotion; - bool IOnlineHost.ShouldReduceAnimations => ShouldReduceAnimations; void IOnlineHost.AnimateImageReveal(UIElement image) => _workspaceView.AnimateImageReveal(image); - Task IOnlineHost.PickFolderAsync(string? preferredPath) => PickFolderAsync(preferredPath); - Task IOnlineHost.OpenExternalUrlAsync(string? url, string failureTitle) => OpenExternalUrlAsync(url, failureTitle); Task IOnlineHost.ShowImageViewerAsync(ImageSource? source, string title, IReadOnlyList? imageUrls, int selectedIndex) => ShowImageViewerAsync(source, title, imageUrls, selectedIndex); @@ -42,7 +38,6 @@ Task IOnlineHost.ShowImageViewerAsync(ImageSource? source, string title, IReadOn void IOnlineHost.SaveShellConfig(bool throwOnError) => SaveShellConfig(throwOnError); bool IOnlineHost.SaveConfig() => SaveConfig(); - WorkspaceRepository? IOnlineHost.GetSelectedRepository() => GetSelectedRepository(); Task IOnlineHost.RefreshListsAsync() => _workspaceView.RefreshListsAsync(); void IOnlineHost.SelectSecondLevelByPath(string? path) => _workspaceView.SelectSecondLevelByPath(path); void IOnlineHost.SetBusyState(bool busy) => _workspaceView.SetBusyState(busy); diff --git a/src/WinUI3/MainWindow.UpdatesHost.cs b/src/WinUI3/MainWindow.UpdatesHost.cs index 0dd5816..57cc805 100644 --- a/src/WinUI3/MainWindow.UpdatesHost.cs +++ b/src/WinUI3/MainWindow.UpdatesHost.cs @@ -15,7 +15,6 @@ private void InitializeUpdatesHost() { UpdatesView.ActionRequested += OnUpdatesActionRequested; UpdatesView.PropertyChanged += OnUpdatesViewChanged; - _services.Appearance.ReduceAnimations = () => ShouldReduceAnimations; _services.Downloads.IsRetryBlocked = () => _onlineView._onlineDownloadActions.IsPreparing; // The navigation shows the running downloads; the Updates page lists the finished ones itself. _services.Downloads.Changed += (_, _) => diff --git a/src/WinUI3/MainWindow.WorkspaceHost.cs b/src/WinUI3/MainWindow.WorkspaceHost.cs index 5f3daec..9962475 100644 --- a/src/WinUI3/MainWindow.WorkspaceHost.cs +++ b/src/WinUI3/MainWindow.WorkspaceHost.cs @@ -33,19 +33,13 @@ private void AttachWorkspacePageEvents() Windows.Foundation.Rect IWorkspaceHost.Bounds => Bounds; object IWorkspaceHost.OnlineNavButton => OnlineNavButton; void IWorkspaceHost.AttachTrackedTextInput(TextBox box) => AttachTrackedTextInput(box); - Brush IWorkspaceHost.GetAppThemeBrush(string resourceKey) => GetAppThemeBrush(resourceKey); void IWorkspaceHost.OnOnlineNavClicked(object sender, RoutedEventArgs e) => OnOnlineNavClicked(sender, e); (List Items, int SecondCount) IWorkspaceHost.LoadFolderItemsUsingIndex(string repositoryId, string sourceDir, string targetDir, CancellationToken cancellationToken, out int warningCount) => LoadFolderItemsUsingIndex(repositoryId, sourceDir, targetDir, cancellationToken, out warningCount); void IWorkspaceHost.LogApplicationIssue(string operation, Exception exception) => LogApplicationIssue(operation, exception); - bool IWorkspaceHost.ShouldReduceAnimations => ShouldReduceAnimations; void IWorkspaceHost.ReportUiActionFailure(string operation, Exception exception) => ReportUiActionFailure(operation, exception); Task IWorkspaceHost.RunUiActionAsync(string operation, Func action) => RunUiActionAsync(operation, action); - void IWorkspaceHost.OpenDirectory(string? directory, string title) => OpenDirectory(directory, title); - void IWorkspaceHost.OpenFileLocation(string? filePath, string title) => OpenFileLocation(filePath, title); - Task IWorkspaceHost.PickFileAsync(string[] extensions) => PickFileAsync(extensions); - Task IWorkspaceHost.PickFolderAsync(string? preferredPath) => PickFolderAsync(preferredPath); Task IWorkspaceHost.ShowImageViewerAsync(ImageSource? source, string title, IReadOnlyList? imageUrls, int selectedIndex) => ShowImageViewerAsync(source, title, imageUrls, selectedIndex); bool IWorkspaceHost.SaveConfig() => SaveConfig(); @@ -54,7 +48,6 @@ private void AttachWorkspacePageEvents() void IWorkspaceHost.ApplyShortcutPlaceholders() => ApplyShortcutPlaceholders(); void IWorkspaceHost.RefreshLocalizedStates() => RefreshLocalizedStates(); - WorkspaceRepository? IWorkspaceHost.GetSelectedRepository() => GetSelectedRepository(); void IWorkspaceHost.OnEditRepositoryDetailsClicked(object sender, RoutedEventArgs e) => OnEditRepositoryDetailsClicked(sender, e); void IWorkspaceHost.SwitchRepository(string repositoryId, bool openManager) => SwitchRepository(repositoryId, openManager); diff --git a/src/WinUI3/MainWindow.xaml.cs b/src/WinUI3/MainWindow.xaml.cs index e741ad2..6754b3a 100644 --- a/src/WinUI3/MainWindow.xaml.cs +++ b/src/WinUI3/MainWindow.xaml.cs @@ -143,6 +143,8 @@ public MainWindow() AttachWorkspacePageEvents(); _services.Images.Attach(_httpClient, LogApplicationIssue); _services.Dialogs.Attach(() => RootGrid.XamlRoot, () => RootGrid.ActualTheme); + _services.Appearance.ReduceAnimations = () => ShouldReduceAnimations; + _services.Dialogs.AttachWindow(() => WinRT.Interop.WindowNative.GetWindowHandle(this)); _services.Status.NotificationRequested += OnNotificationRequested; InitializeSettingsHost(); InitializeUpdatesHost(); diff --git a/src/WinUI3/Pages/Online/IOnlineHost.cs b/src/WinUI3/Pages/Online/IOnlineHost.cs index d6a993f..9ef0e2f 100644 --- a/src/WinUI3/Pages/Online/IOnlineHost.cs +++ b/src/WinUI3/Pages/Online/IOnlineHost.cs @@ -19,14 +19,10 @@ internal interface IOnlineHost bool IsOnlinePageShown { get; } FrameworkElement Root { get; } Windows.Foundation.Rect Bounds { get; } - Brush GetAppThemeBrush(string resourceKey); // The density name, part of the page's layout cache key. string Density { get; } bool ReduceMotion { get; } - bool ShouldReduceAnimations { get; } void AnimateImageReveal(UIElement image); - Task PickFolderAsync(string? preferredPath = null); - Task OpenExternalUrlAsync(string? url, string failureTitle); Task ShowImageViewerAsync(ImageSource? source, string title, IReadOnlyList? imageUrls = null, int selectedIndex = 0); // Errors and the error log. @@ -39,7 +35,6 @@ internal interface IOnlineHost bool SaveConfig(); // The repository and the Mod workspace: the selected repository, refreshing and selecting after an install. - WorkspaceRepository? GetSelectedRepository(); Task RefreshListsAsync(); void SelectSecondLevelByPath(string? path); void SetBusyState(bool busy); diff --git a/src/WinUI3/Pages/Online/OnlinePage.AuthorWorkspace.cs b/src/WinUI3/Pages/Online/OnlinePage.AuthorWorkspace.cs index 6f31c61..e806ac6 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.AuthorWorkspace.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.AuthorWorkspace.cs @@ -22,7 +22,7 @@ private void OnOnlineWorkFilterClicked(object sender, RoutedEventArgs e) private async Task ShowOnlineLibraryAsync() { UiCrashDiagnostics.RecordAction("Author and saved workspace opened"); - var repo = _host.GetSelectedRepository(); + var repo = _services.Repositories.Selected; if (repo is null) return; string category = GetEffectiveOnlineCategoryId(repo); string contextKey = GetOnlineDetailContextKey(); diff --git a/src/WinUI3/Pages/Online/OnlinePage.BatchDownloads.cs b/src/WinUI3/Pages/Online/OnlinePage.BatchDownloads.cs index dd71528..cbab63f 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.BatchDownloads.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.BatchDownloads.cs @@ -96,7 +96,7 @@ private async Task PrepareSavedBatchAsync(OnlineSavedMod[] saved, WorkspaceRepos enabled, isChecked, files, options, folderIndex, note)); } if (await BatchDownloadDialogs.ReviewAsync(_services, context.RepositoryName, context.RepositoryPath, reviewRows, - (key, file) => GetOnlineDuplicateKind(((PreparedSavedDownload)key).Mod, file, context), _host.GetAppThemeBrush("AppNavSelectedBorderBrush"), + (key, file) => GetOnlineDuplicateKind(((PreparedSavedDownload)key).Mod, file, context), GetAppThemeBrush("AppNavSelectedBorderBrush"), _host.Root.ActualWidth, _host.Root.ActualHeight) is not { } review || _onlineWindowClosed || contextKey != GetOnlineDetailContextKey()) return; var chosen = review.Rows.Select(plan => (Data: (PreparedSavedDownload)plan.Key, plan.File, Choice: plan.Folder)).ToArray(); diff --git a/src/WinUI3/Pages/Online/OnlinePage.BrowseSessions.cs b/src/WinUI3/Pages/Online/OnlinePage.BrowseSessions.cs index 976578f..206e83e 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.BrowseSessions.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.BrowseSessions.cs @@ -17,7 +17,7 @@ public sealed partial class OnlinePage private string? CurrentBrowseSessionKey() { - var repository = _host.GetSelectedRepository(); + var repository = _services.Repositories.Selected; return repository is null ? null : WorkspaceIdentity.Online(repository.Id, repository.SourcePath, GetEffectiveOnlineSource(repository), GetEffectiveOnlineCategoryId(repository)); } @@ -30,7 +30,7 @@ private void EnsureOnlineBrowseSession() CancelOnlineModListRequest(); _lastLoadedOnlineConfigKey = null; _onlinePage.Mods.Clear(); - EnsureOnlineCharacterOptionsForRepository(_host.GetSelectedRepository()); + EnsureOnlineCharacterOptionsForRepository(_services.Repositories.Selected); ++_browseRestoreRevision; var state = key is not null && _onlinePreferences.BrowseSessions.TryGetValue(key, out var saved) && saved is not null ? saved.Sanitize() : new OnlineBrowseSession { Layout = _onlinePage.CardLayoutMode.ToString() }; diff --git a/src/WinUI3/Pages/Online/OnlinePage.Fields.cs b/src/WinUI3/Pages/Online/OnlinePage.Fields.cs index c0f59d6..ffbc081 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.Fields.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.Fields.cs @@ -13,17 +13,17 @@ public sealed partial class OnlinePage internal void ApplySecondaryNavButtonVisual(Button button, bool isSelected) { Brush backgroundBrush = isSelected - ? _host.GetAppThemeBrush("AppSecondarySelectedBrush") - : _host.GetAppThemeBrush("AppSecondaryDefaultBrush"); + ? GetAppThemeBrush("AppSecondarySelectedBrush") + : GetAppThemeBrush("AppSecondaryDefaultBrush"); Brush borderBrush = isSelected - ? _host.GetAppThemeBrush("AppNavSelectedBorderBrush") - : _host.GetAppThemeBrush("AppCardBorderBrush"); + ? GetAppThemeBrush("AppNavSelectedBorderBrush") + : GetAppThemeBrush("AppCardBorderBrush"); Brush foregroundBrush = isSelected - ? _host.GetAppThemeBrush("AppSecondarySelectedForegroundBrush") - : _host.GetAppThemeBrush("AppSecondaryDefaultForegroundBrush"); - Brush disabledForegroundBrush = _host.GetAppThemeBrush("AppDisabledForegroundBrush"); - Brush disabledBackgroundBrush = _host.GetAppThemeBrush("AppSecondaryDefaultBrush"); - Brush disabledBorderBrush = _host.GetAppThemeBrush("AppCardBorderBrush"); + ? GetAppThemeBrush("AppSecondarySelectedForegroundBrush") + : GetAppThemeBrush("AppSecondaryDefaultForegroundBrush"); + Brush disabledForegroundBrush = GetAppThemeBrush("AppDisabledForegroundBrush"); + Brush disabledBackgroundBrush = GetAppThemeBrush("AppSecondaryDefaultBrush"); + Brush disabledBorderBrush = GetAppThemeBrush("AppCardBorderBrush"); button.Background = backgroundBrush; button.BorderBrush = borderBrush; @@ -122,10 +122,10 @@ internal FrameworkElement WrapCharacterButtonWithFavorite(Button characterButton ? VerticalAlignment.Top : VerticalAlignment.Center, CornerRadius = new CornerRadius(15), - Background = _host.GetAppThemeBrush(isFavorite ? "AppAccentSoftBrush" : "AppSecondaryDefaultBrush"), - BorderBrush = _host.GetAppThemeBrush(isFavorite ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"), + Background = GetAppThemeBrush(isFavorite ? "AppAccentSoftBrush" : "AppSecondaryDefaultBrush"), + BorderBrush = GetAppThemeBrush(isFavorite ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"), BorderThickness = new Thickness(1), - Foreground = _host.GetAppThemeBrush(isFavorite ? "AppAccentBrush" : "AppSecondaryDefaultForegroundBrush"), + Foreground = GetAppThemeBrush(isFavorite ? "AppAccentBrush" : "AppSecondaryDefaultForegroundBrush"), Content = new FontIcon { Glyph = isFavorite ? "\uE735" : "\uE734", diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineBrowse.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineBrowse.cs index 0b8528f..1afbad4 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineBrowse.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineBrowse.cs @@ -87,12 +87,12 @@ private HyperlinkButton CreateOnlineAuthorLink(OnlineModCard mod) private OnlineCardStatus GetOnlineCardStatus(OnlineModCard mod, TrackedModOrigin? installed, string defaultStatus) { - bool active = _onlineDownloadActions.IsActive(CaptureOnlineDownloadContext(_host.GetSelectedRepository()), + bool active = _onlineDownloadActions.IsActive(CaptureOnlineDownloadContext(_services.Repositories.Selected), GetOnlineDownloadIdentity(mod)); DownloadTaskItem? task = _services.Downloads.Tasks.FirstOrDefault(item => item.Session is not null && item.SourceIdentity == GetOnlineDownloadIdentity(mod) - && string.Equals(item.Session.Context.RepositoryId, _host.GetSelectedRepository()?.Id, StringComparison.OrdinalIgnoreCase) - && string.Equals(item.Session.Context.RepositoryPath, _host.GetSelectedRepository()?.SourcePath, StringComparison.OrdinalIgnoreCase)); + && string.Equals(item.Session.Context.RepositoryId, _services.Repositories.Selected?.Id, StringComparison.OrdinalIgnoreCase) + && string.Equals(item.Session.Context.RepositoryPath, _services.Repositories.Selected?.SourcePath, StringComparison.OrdinalIgnoreCase)); // A retry can be preparing while its previous terminal task is still in history. if (active && task is not null && DownloadProgressPolicy.IsTerminal(task.State)) task = null; string text = task?.State switch diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineCards.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineCards.cs index ad5c98a..f7b51ba 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineCards.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineCards.cs @@ -220,8 +220,8 @@ private EmptyState CreateOnlineEmptyState(string glyph, string title, string hin // Placeholder cards shaped like the current view (rows or tiles), shown under the loading ring. private void ShowOnlineLoadingPlaceholders() { - Brush fill = _host.GetAppThemeBrush("AppInsetBackgroundBrush"); - Brush shade = _host.GetAppThemeBrush("AppAccentSoftBrush"); + Brush fill = GetAppThemeBrush("AppInsetBackgroundBrush"); + Brush shade = GetAppThemeBrush("AppAccentSoftBrush"); if (_onlinePage.CardLayoutMode == OnlineCardLayoutMode.Grid) { double width = OnlineResultsHost.ActualWidth > 0 ? OnlineResultsHost.ActualWidth - 12 : OnlineLayoutPolicy.PreferredTileWidth; @@ -286,7 +286,7 @@ private void AnimateOnlineCardsEntrance() FrameworkElement resultsElement = GetOnlineResultsElement(); var visual = ElementCompositionPreview.GetElementVisual(resultsElement); visual.StopAnimation("Opacity"); - if (_host.ShouldReduceAnimations) + if (_services.Appearance.ReduceAnimations()) { visual.Opacity = 1f; return; @@ -469,7 +469,7 @@ private void UpdateOnlineGridCardMetrics(double availableWidth) private OnlineModTileViewModel CreateOnlineTileModel(OnlineModCard mod) { var model = new OnlineModTileViewModel(mod, - openPage: () => _host.RunUiActionAsync("_host.OpenExternalUrlAsync", () => _host.OpenExternalUrlAsync(mod.ProfileUrl, T("Common.OpenPageFailed"))), + openPage: () => _host.RunUiActionAsync("_host.OpenExternalUrlAsync", () => _services.Dialogs.OpenExternalUrlAsync(mod.ProfileUrl, T("Common.OpenPageFailed"))), download: () => _host.RunUiActionAsync("DownloadAndExtractOnlineModAsync", () => DownloadAndExtractOnlineModAsync(mod))) { CharacterLabel = GetOnlineCharacterDisplayName(mod.CharacterName), @@ -519,8 +519,8 @@ private async void OnOnlineResultItemClick(object sender, ItemClickEventArgs e) private void AnimateOnlineModCardHover(Border card, bool isPointerOver) { - card.Background = _host.GetAppThemeBrush(isPointerOver ? "AppSecondarySelectedBrush" : "AppInsetBackgroundBrush"); - if (_host.ShouldReduceAnimations) + card.Background = GetAppThemeBrush(isPointerOver ? "AppSecondarySelectedBrush" : "AppInsetBackgroundBrush"); + if (_services.Appearance.ReduceAnimations()) { return; } @@ -564,8 +564,8 @@ private Border CreateOnlineInstalledIndicator(string installedPath) var indicator = new Border { HorizontalAlignment = HorizontalAlignment.Left, - Background = _host.GetAppThemeBrush("AppAccentSoftBrush"), - BorderBrush = _host.GetAppThemeBrush("AppNavSelectedBorderBrush"), + Background = GetAppThemeBrush("AppAccentSoftBrush"), + BorderBrush = GetAppThemeBrush("AppNavSelectedBorderBrush"), BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(4), Padding = new Thickness(8, 4, 8, 4), diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineCharacters.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineCharacters.cs index 0b1ecda..6e14a4a 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineCharacters.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineCharacters.cs @@ -16,7 +16,7 @@ public sealed partial class OnlinePage { internal void PopulateOnlineCharacterOptions() { - EnsureOnlineCharacterOptionsForRepository(_host.GetSelectedRepository()); + EnsureOnlineCharacterOptionsForRepository(_services.Repositories.Selected); IReadOnlyList visibleCharacters = GetVisibleOnlineCharacterOptions(); _isApplyingOnlineCharacterSelection = true; try @@ -246,7 +246,7 @@ private string GetOnlineCharacterDisplayName(OnlineCharacterOption character) internal void RefreshOnlineCharacterAvatarStrip() { - EnsureOnlineCharacterOptionsForRepository(_host.GetSelectedRepository()); + EnsureOnlineCharacterOptionsForRepository(_services.Repositories.Selected); IReadOnlyList visibleCharacters = GetVisibleOnlineCharacterOptions(); string characterSignature = string.Join(";", visibleCharacters.Select(item => $"{item.Key}:{item.DisplayNameZh}:{item.DisplayNameEn}:{item.CategoryId}:{item.AvatarUrl}:{item.WikiOrder}")); string signature = $"{_services.Language.Current}|{_services.Appearance.IsDark}|{_isLoadingOnlineMods}|{_onlinePage.CharacterFilter}|{_onlinePage.TotalCount}|horizontal={_useHorizontalOnlineCharacterRail}|{characterSignature}"; @@ -309,8 +309,8 @@ private FrameworkElement CreateOnlineCharacterButton( VerticalAlignment = VerticalAlignment.Center, CornerRadius = new CornerRadius(avatarSize / 2), BorderThickness = new Thickness(isSelected ? 2 : 1), - BorderBrush = _host.GetAppThemeBrush(isSelected ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"), - Background = _host.GetAppThemeBrush("AppInsetBackgroundBrush") + BorderBrush = GetAppThemeBrush(isSelected ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"), + Background = GetAppThemeBrush("AppInsetBackgroundBrush") }; if (!string.IsNullOrWhiteSpace(avatarUrl)) @@ -439,9 +439,9 @@ private FrameworkElement CreateOnlineCharacterButton( HorizontalContentAlignment = _useHorizontalOnlineCharacterRail ? HorizontalAlignment.Center : HorizontalAlignment.Stretch, - Background = _host.GetAppThemeBrush(isSelected ? "AppSecondarySelectedBrush" : "AppSecondaryDefaultBrush"), - BorderBrush = _host.GetAppThemeBrush(isSelected ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"), - Foreground = _host.GetAppThemeBrush(isSelected ? "AppSecondarySelectedForegroundBrush" : "AppSecondaryDefaultForegroundBrush") + Background = GetAppThemeBrush(isSelected ? "AppSecondarySelectedBrush" : "AppSecondaryDefaultBrush"), + BorderBrush = GetAppThemeBrush(isSelected ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"), + Foreground = GetAppThemeBrush(isSelected ? "AppSecondarySelectedForegroundBrush" : "AppSecondaryDefaultForegroundBrush") }; OnlineCharacterOption? characterOption = _onlinePage.CharacterOptions.FirstOrDefault(item => string.Equals(item.Key, character, StringComparison.OrdinalIgnoreCase)); @@ -468,7 +468,7 @@ private string GetOnlineCharacterDisplayName(string characterName) return GetOnlineCharacterDisplayName(option); } - if (IsEndfieldRepository(_host.GetSelectedRepository())) + if (IsEndfieldRepository(_services.Repositories.Selected)) { EndfieldCharacterInfo? character = FindEndfieldCharacter(characterName); return character is null ? characterName : GetOnlineCharacterDisplayName(character); diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineDetails.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineDetails.cs index 5f9c19b..86dfed1 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineDetails.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineDetails.cs @@ -477,7 +477,7 @@ private async Task OpenOnlineDetailPaneAsync() var visual = ElementCompositionPreview.GetElementVisual(OnlineDetailPaneBorder); visual.StopAnimation("Opacity"); visual.StopAnimation("Offset"); - if (_host.ShouldReduceAnimations) + if (_services.Appearance.ReduceAnimations()) { visual.Opacity = 1f; visual.Offset = Vector3.Zero; @@ -500,7 +500,7 @@ private async Task OpenOnlineDetailPaneAsync() private void AnimateOnlineDetailContentRefresh() { - if (_host.ShouldReduceAnimations) + if (_services.Appearance.ReduceAnimations()) { return; } @@ -900,7 +900,7 @@ internal async Task CloseOnlineDetailPaneAsync() visual.StopAnimation("Opacity"); visual.StopAnimation("Offset"); - if (!_host.ShouldReduceAnimations && IsOnlineDetailOpen) + if (!_services.Appearance.ReduceAnimations() && IsOnlineDetailOpen) { var compositor = visual.Compositor; var easing = compositor.CreateCubicBezierEasingFunction(new Vector2(0.4f, 0f), new Vector2(1f, 1f)); @@ -948,7 +948,7 @@ private async void OnOpenOnlineDetailPageClicked(object sender, RoutedEventArgs return; } - await _host.OpenExternalUrlAsync(_activeOnlineDetailMod.ProfileUrl, T("Common.OpenPageFailed")); + await _services.Dialogs.OpenExternalUrlAsync(_activeOnlineDetailMod.ProfileUrl, T("Common.OpenPageFailed")); } catch (Exception ex) { @@ -983,7 +983,7 @@ private async void OnSelectOnlineDetailDownloadClicked(object sender, RoutedEven } OnlineModCard sourceMod = _activeOnlineDetailMod; - WorkspaceRepository? downloadRepository = _host.GetSelectedRepository(); + WorkspaceRepository? downloadRepository = _services.Repositories.Selected; OnlineDownloadContext downloadContext = CaptureOnlineDownloadContext(downloadRepository); bool useEndfieldMetadata = IsEndfieldRepository(downloadRepository); IReadOnlyCollection characterAliases = GetCharacterFolderAliases(sourceMod); diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineDownloadFlow.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineDownloadFlow.cs index 7900d2d..5933273 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineDownloadFlow.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineDownloadFlow.cs @@ -20,7 +20,7 @@ private async Task RefreshOnlineModDownloadInfoAsync(OnlineModCar { OnlineModCard? latestMod = await FetchGameBananaModCardAsyncV2( mod.ItemId, - useEndfieldMetadata: useEndfieldMetadata ?? IsEndfieldRepository(_host.GetSelectedRepository()), + useEndfieldMetadata: useEndfieldMetadata ?? IsEndfieldRepository(_services.Repositories.Selected), cancellationToken: cancellationToken); if (latestMod is null) { @@ -91,7 +91,7 @@ private async Task DownloadAndExtractOnlineModAsync( IReadOnlyCollection? capturedCharacterAliases = null, string? confirmedDestination = null, bool batchConfirmed = false) { - WorkspaceRepository? initialRepository = _host.GetSelectedRepository(); + WorkspaceRepository? initialRepository = _services.Repositories.Selected; OnlineDownloadContext context = capturedContext ?? CaptureOnlineDownloadContext(initialRepository); bool useEndfieldMetadata = capturedEndfieldMetadata ?? IsEndfieldRepository(initialRepository); IReadOnlyCollection characterAliases = capturedCharacterAliases ?? GetCharacterFolderAliases(mod); @@ -231,14 +231,14 @@ await Task.Run(() => // failed task or delete it. A changed repository must not receive the old selection. try { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository?.Id == context.RepositoryId && string.Equals(repository.SourcePath, context.RepositoryPath, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(context.RepositoryPath) && IsPathInsideDirectory(extractFolder, context.RepositoryPath)) { await _host.RefreshListsAsync(); - repository = _host.GetSelectedRepository(); + repository = _services.Repositories.Selected; if (repository?.Id == context.RepositoryId && string.Equals(repository.SourcePath, context.RepositoryPath, StringComparison.OrdinalIgnoreCase)) _host.SelectSecondLevelByPath(extractFolder); @@ -397,7 +397,7 @@ private static string GetTrackedModIdentity(int itemId, string? profileUrl) private TrackedModOrigin? FindInstalledOnlineMod(OnlineModCard mod) { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; string repositoryPath = repository?.SourcePath ?? string.Empty; foreach (TrackedModOrigin origin in _host.TrackedModOrigins.Values) @@ -753,6 +753,6 @@ await ShowMessageAsync(T("OnlineDownloadFlow.CouldNotCreateTheCharacterFolder") } } - return await _host.PickFolderAsync(); + return await _services.Dialogs.PickFolderAsync(); } } diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineFeed.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineFeed.cs index 49b24d1..4331644 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineFeed.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineFeed.cs @@ -34,7 +34,7 @@ internal void RefreshOnlinePaneV2() { EnsureOnlineBrowseSession(); RefreshOnlineNavigationButtonLabels(); - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; bool hasRepository = repository is not null; string effectiveSource = GetEffectiveOnlineSource(repository); string effectiveGameName = GetEffectiveOnlineGameName(repository); @@ -318,7 +318,7 @@ private async void OnRefreshOnlineModsClicked(object sender, RoutedEventArgs e) { try { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is not null) { _ = EnsureOnlineCharacterCatalogAsync(repository, forceReload: true); @@ -337,7 +337,7 @@ internal async Task LoadOnlineModsAsync(bool forceReload) EnsureOnlineBrowseSession(); _host.SaveShellConfig(); CancelOnlineModListRequest(); - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null) { return; @@ -891,7 +891,7 @@ bool Current() => enrichmentVersion == _onlineDownloadEnrichmentVersion private bool MatchesOnlineSearch(OnlineModCard mod, string searchText) { - EndfieldCharacterInfo? character = IsEndfieldRepository(_host.GetSelectedRepository()) + EndfieldCharacterInfo? character = IsEndfieldRepository(_services.Repositories.Selected) ? FindEndfieldCharacter(mod.CharacterName) : null; return OnlineBrowseStateAdapter.MatchesSearch(mod.Title, mod.CharacterName, mod.Author, searchText, character?.AllNames); diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineLibrary.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineLibrary.cs index 39763df..cfb2d82 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineLibrary.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineLibrary.cs @@ -17,7 +17,7 @@ private async Task BrowseOnlineAuthorAsync(int id, string name) { UiCrashDiagnostics.RecordAction("Author browse"); if (id <= 0) { OnlineSearchTextBox.Text = $"author:{name}"; return; } - string category = GetEffectiveOnlineCategoryId(_host.GetSelectedRepository()); + string category = GetEffectiveOnlineCategoryId(_services.Repositories.Selected); _onlinePreferences.RecentAuthors.RemoveAll(item => item.Id == id && item.CategoryId == category); _onlinePreferences.RecentAuthors.Insert(0, new OnlineAuthorVisit(id, name, category, DateTimeOffset.UtcNow)); _onlinePreferences.RecentAuthors = _onlinePreferences.RecentAuthors.Take(100).ToList(); @@ -83,13 +83,13 @@ private void RefreshOnlineFilterSummary(int matches, int loaded) } private bool IsSavedOnlineMod(OnlineModCard mod) => _onlinePreferences.SavedMods.Any(item => - item.RepositoryId == _host.GetSelectedRepository()?.Id - && string.Equals(item.RepositoryPath.TrimEnd('\\', '/'), _host.GetSelectedRepository()?.SourcePath.TrimEnd('\\', '/'), StringComparison.OrdinalIgnoreCase) + item.RepositoryId == _services.Repositories.Selected?.Id + && string.Equals(item.RepositoryPath.TrimEnd('\\', '/'), _services.Repositories.Selected?.SourcePath.TrimEnd('\\', '/'), StringComparison.OrdinalIgnoreCase) && GetOnlineDownloadIdentity(item.Mod) == GetOnlineDownloadIdentity(mod)); private void ToggleSavedOnlineMod(OnlineModCard mod) { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null) return; bool remove = IsSavedOnlineMod(mod); _onlinePreferences.SavedMods.RemoveAll(item => item.RepositoryId == repository.Id @@ -111,11 +111,11 @@ private MenuFlyout CreateOnlineCardMenu(OnlineModCard mod) author.Click += async (_, _) => await _host.RunUiActionAsync("BrowseOnlineAuthorAsync", () => BrowseOnlineAuthorAsync(mod.AuthorId, mod.Author)); menu.Items.Add(author); bool savedAuthor = _onlinePreferences.Authors.Any(item => item.Id == mod.AuthorId - && item.CategoryId == GetEffectiveOnlineCategoryId(_host.GetSelectedRepository())); + && item.CategoryId == GetEffectiveOnlineCategoryId(_services.Repositories.Selected)); var favorite = new MenuFlyoutItem { Text = savedAuthor ? T("OnlineLibrary.UnsaveAuthor") : T("OnlineLibrary.SaveAuthor"), IsEnabled = mod.AuthorId > 0 }; favorite.Click += (_, _) => { - string category = GetEffectiveOnlineCategoryId(_host.GetSelectedRepository()); + string category = GetEffectiveOnlineCategoryId(_services.Repositories.Selected); _onlinePreferences.Authors.RemoveAll(item => item.Id == mod.AuthorId && item.CategoryId == category); if (!savedAuthor) _onlinePreferences.Authors.Insert(0, new OnlineAuthorBookmark(mod.AuthorId, mod.Author, category)); _onlinePreferences.Authors = _onlinePreferences.Authors.Take(100).ToList(); @@ -126,7 +126,7 @@ private MenuFlyout CreateOnlineCardMenu(OnlineModCard mod) { save.Text = IsSavedOnlineMod(mod) ? T("OnlineLibrary.RemoveFromSaved") : T("OnlineLibrary.SaveForLater"); savedAuthor = _onlinePreferences.Authors.Any(item => item.Id == mod.AuthorId - && item.CategoryId == GetEffectiveOnlineCategoryId(_host.GetSelectedRepository())); + && item.CategoryId == GetEffectiveOnlineCategoryId(_services.Repositories.Selected)); favorite.Text = savedAuthor ? T("OnlineLibrary.UnsaveAuthor") : T("OnlineLibrary.SaveAuthor"); }; return menu; diff --git a/src/WinUI3/Pages/Online/OnlinePage.OnlineState.cs b/src/WinUI3/Pages/Online/OnlinePage.OnlineState.cs index ec1c997..c503275 100644 --- a/src/WinUI3/Pages/Online/OnlinePage.OnlineState.cs +++ b/src/WinUI3/Pages/Online/OnlinePage.OnlineState.cs @@ -13,7 +13,7 @@ public sealed partial class OnlinePage private string GetOnlineDetailContextKey() { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; return System.Text.Json.JsonSerializer.Serialize(new[] { repository?.Id, repository?.SourcePath, repository?.OnlineCategoryId, @@ -26,7 +26,7 @@ internal static string GetOnlineDownloadIdentity(OnlineModCard mod) private bool IsOnlineDownloadActionBlocked(OnlineModCard mod) => _onlineDownloadActions.IsPreparing || _onlineDownloadActions.IsActive( - CaptureOnlineDownloadContext(_host.GetSelectedRepository()), GetOnlineDownloadIdentity(mod)); + CaptureOnlineDownloadContext(_services.Repositories.Selected), GetOnlineDownloadIdentity(mod)); private void RefreshOnlineDetailActionState() { diff --git a/src/WinUI3/Pages/OnlinePage.xaml.cs b/src/WinUI3/Pages/OnlinePage.xaml.cs index fe57af8..0050e68 100644 --- a/src/WinUI3/Pages/OnlinePage.xaml.cs +++ b/src/WinUI3/Pages/OnlinePage.xaml.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; namespace ModFolderCopier.WinUI; @@ -26,6 +27,9 @@ internal OnlinePage(AppServices services, IOnlineHost host) private bool IsChineseUi => _services.Language.IsChinese; + // The App*Brush tokens of Themes/Tokens.xaml, in the theme the window shows (stage J finish, step 2). + private Brush GetAppThemeBrush(string resourceKey) => ThemeTokens.Get(resourceKey, _services.Appearance.ThemeKey); + private string T(string key) => _services.Language.T(key); private string T(string key, params object?[] values) => _services.Language.T(key, values); diff --git a/src/WinUI3/Pages/Workspace/IWorkspaceHost.cs b/src/WinUI3/Pages/Workspace/IWorkspaceHost.cs index d7ec641..49143e8 100644 --- a/src/WinUI3/Pages/Workspace/IWorkspaceHost.cs +++ b/src/WinUI3/Pages/Workspace/IWorkspaceHost.cs @@ -21,21 +21,15 @@ internal interface IWorkspaceHost Windows.Foundation.Rect Bounds { get; } object OnlineNavButton { get; } void AttachTrackedTextInput(TextBox box); - Brush GetAppThemeBrush(string resourceKey); void OnOnlineNavClicked(object sender, RoutedEventArgs e); // Errors, the error log and listing a repository through the local index. (List Items, int SecondCount) LoadFolderItemsUsingIndex(string repositoryId, string sourceDir, string targetDir, CancellationToken cancellationToken, out int warningCount); void LogApplicationIssue(string operation, Exception exception); - bool ShouldReduceAnimations { get; } void ReportUiActionFailure(string operation, Exception exception); Task RunUiActionAsync(string operation, Func action); // Pickers, folders and the image viewer. - void OpenDirectory(string? directory, string title); - void OpenFileLocation(string? filePath, string title); - Task PickFileAsync(string[] extensions); - Task PickFolderAsync(string? preferredPath = null); Task ShowImageViewerAsync(ImageSource? source, string title, IReadOnlyList? imageUrls = null, int selectedIndex = 0); // Saving the settings (beta-shell.json) and config.ini. @@ -47,7 +41,6 @@ internal interface IWorkspaceHost void RefreshLocalizedStates(); // Repositories: the selected one, switching, editing and the deployment mode switch. - WorkspaceRepository? GetSelectedRepository(); void OnEditRepositoryDetailsClicked(object sender, RoutedEventArgs e); void SwitchRepository(string repositoryId, bool openManager); diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.BatchModActions.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.BatchModActions.cs index 02ddeea..f11d79f 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.BatchModActions.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.BatchModActions.cs @@ -12,13 +12,13 @@ public sealed partial class WorkspacePage private async Task ShowBatchModActionsAsync() { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; SecondLevelFolderItem[] items = _modList.VisibleMods.ToArray(); if (repository is null) return; if (items.Length == 0) { await ShowMessageAsync(T("LocalTags.NoModsInTheCurrentFilter"), T("BatchMods.Title")); return; } if (await BatchModActionsDialog.ShowAsync(_services, items, _modList.SelectedMod, [.. _host.GetCurrentRepositoryProfiles()], _host.SelectedConfigurationProfileId, _host.Root.ActualHeight) - is not { } choice || !ReferenceEquals(_host.GetSelectedRepository(), repository)) return; + is not { } choice || !ReferenceEquals(_services.Repositories.Selected, repository)) return; SecondLevelFolderItem[] chosen = [.. choice.Mods]; switch (choice.Action) { @@ -62,7 +62,7 @@ private void AddModsToPreset(WorkspaceRepository repository, IReadOnlyList mods) { - if (await _host.PickFolderAsync() is not { } folder) return; + if (await _services.Dialogs.PickFolderAsync() is not { } folder) return; int copied = await Task.Run(() => { int count = 0; diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterAvatars.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterAvatars.cs index cf5ad93..6ca11e0 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterAvatars.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterAvatars.cs @@ -24,7 +24,7 @@ private async Task RefreshCharacterFolderAvatarsAsync() _characterAvatarCancellation = new CancellationTokenSource(); CancellationToken cancellationToken = _characterAvatarCancellation.Token; - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; FirstLevelFolderItem[] items = [.. _host.Characters.All]; if (repository is null || items.Length == 0) { @@ -46,7 +46,7 @@ private async Task RefreshCharacterFolderAvatarsAsync() if (!fresh && !cancellationToken.IsCancellationRequested && _avatarCatalogRefreshRequested.Add(repository.Id)) { await _host.OnlineView.EnsureOnlineCharacterCatalogAsync(repository, forceReload: false); - if (!cancellationToken.IsCancellationRequested && ReferenceEquals(_host.GetSelectedRepository(), repository)) + if (!cancellationToken.IsCancellationRequested && ReferenceEquals(_services.Repositories.Selected, repository)) { await RefreshCharacterFolderAvatarsAsync(); } @@ -63,7 +63,7 @@ private async Task RefreshCharacterFolderAvatarsAsync() private async Task RefreshCharacterFolderAvatarAsync(FirstLevelFolderItem item) { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null) { return; @@ -164,7 +164,7 @@ private List CreateCharacterAvatarMenuItems(FirstLevelFolder var choose = new MenuFlyoutItem { Text = T("CharacterAvatars.ChooseImage") }; choose.Click += async (_, _) => await RunCharacterAvatarActionAsync(item, async () => { - string? image = await _host.PickFileAsync([.. CharacterFolderAvatars.ImageExtensions]); + string? image = await _services.Dialogs.PickFileAsync([.. CharacterFolderAvatars.ImageExtensions]); if (image is null) return false; CharacterFolderAvatars.SetCustomAvatar(item.Path, image); return true; @@ -206,7 +206,7 @@ private async Task RunCharacterAvatarActionAsync(FirstLevelFolderItem item, Func // Binds the folder to a catalog character, or clears the binding (automatic matching). private async Task PromptCharacterAvatarBindingAsync(FirstLevelFolderItem item) { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null) { return false; diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterSelection.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterSelection.cs index 04d9818..52a12e1 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterSelection.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.CharacterSelection.cs @@ -18,7 +18,7 @@ private static bool AllowsMultipleMods(WorkspaceRepository repository, string mo private void AddCharacterSelectionMenuItem(MenuFlyout menu, FirstLevelFolderItem item) { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null) { return; @@ -56,7 +56,7 @@ private void SetCharacterAllowsMultiple(WorkspaceRepository repository, FirstLev private void UpdateCharacterHeaderActions() { if (CharacterMultipleModsToggle is null || CharacterAvatarButton is null) return; - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; var item = _host.Characters.Selected; bool allows = repository is not null && item is not null && CharacterSelectionPolicy.AllowsMultiple(item.Name, repository.AllowMultipleModsPerCharacter, repository.CharacterSelectionOverrides); @@ -82,7 +82,7 @@ private void UpdateCharacterHeaderActions() private void OnCharacterMultipleModsToggleClicked(object sender, RoutedEventArgs e) { - if (_host.GetSelectedRepository() is { } repository && _host.Characters.Selected is { } item) + if (_services.Repositories.Selected is { } repository && _host.Characters.Selected is { } item) { SetCharacterAllowsMultiple(repository, item, CharacterMultipleModsToggle.IsChecked == true); } diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.ExistingModImport.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.ExistingModImport.cs index 0e4a829..add07f1 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.ExistingModImport.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.ExistingModImport.cs @@ -41,7 +41,7 @@ private void InitializeExistingModImportCommands(StackPanel host) private async Task ShowExistingModImportAsync() { if (_existingModImportBusy) return; - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null || !Directory.Exists(repository.SourcePath)) return; OnlineDownloadContext context = OnlinePage.CaptureOnlineDownloadContext(repository); string[] characters = Directory.GetDirectories(context.RepositoryPath).OrderBy(Path.GetFileName).ToArray(); @@ -50,7 +50,7 @@ private async Task ShowExistingModImportAsync() await ShowMessageAsync(T("ExistingModImport.CreateACharacterFolderInThis"), T("ExistingModImport.ChooseATargetCharacter")); return; } - if (await ExistingModImportDialogs.ChooseAsync(_services, characters, _host.Characters.Selected?.Path, () => _host.PickFolderAsync()) is not { } chosen) return; + if (await ExistingModImportDialogs.ChooseAsync(_services, characters, _host.Characters.Selected?.Path, () => _services.Dialogs.PickFolderAsync()) is not { } chosen) return; (string source, string destination) = chosen; _existingModImportBusy = true; using var cancellation = new CancellationTokenSource(); @@ -76,7 +76,7 @@ await Task.Run(() => ExistingModImportService.ExecuteAsync(plan, manifest, (done, total) => DispatcherQueue.TryEnqueue(() => status.Text = T("ExistingModImport.CopiedMods", done, total)), cancellation.Token)); } finally { _services.Dialogs.Close(operation); await shown; } - if (_host.GetSelectedRepository()?.Id == context.RepositoryId) await RefreshListsAsync(); + if (_services.Repositories.Selected?.Id == context.RepositoryId) await RefreshListsAsync(); ShowAppNotification(T("ExistingModImport.Imported")); } catch (OperationCanceledException) @@ -95,7 +95,7 @@ await Task.Run(() => ExistingModImportService.ExecuteAsync(plan, manifest, private async Task UndoExistingModImportAsync() { string? manifest = _host.OnlineView._onlinePreferences.LastImportManifest; - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (_existingModImportBusy || manifest is null || repository is null) return; try { @@ -117,7 +117,7 @@ await ShowMessageAsync(T("ExistingModImport.RemovedUnchangedCopiesPreservedModif private async Task RecoverExistingModImportAsync() { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (_existingModImportBusy || repository is null) return; _existingModImportBusy = true; try diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.LocalLibrary.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.LocalLibrary.cs index 164a0ac..ab10a3a 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.LocalLibrary.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.LocalLibrary.cs @@ -16,7 +16,7 @@ public sealed partial class WorkspacePage { internal void AnimateImageReveal(UIElement image) { - if (_host.ShouldReduceAnimations) + if (_services.Appearance.ReduceAnimations()) { ElementCompositionPreview.GetElementVisual(image).Opacity = 1f; return; @@ -52,7 +52,7 @@ internal async void OnPickSourceClicked(object sender, RoutedEventArgs e) { try { - string? picked = await _host.PickFolderAsync(); + string? picked = await _services.Dialogs.PickFolderAsync(); if (!string.IsNullOrEmpty(picked)) { Paths.Source = picked; @@ -70,7 +70,7 @@ internal async void OnPickTargetClicked(object sender, RoutedEventArgs e) { try { - string? picked = await _host.PickFolderAsync(); + string? picked = await _services.Dialogs.PickFolderAsync(); if (!string.IsNullOrEmpty(picked)) { Paths.Target = picked; @@ -88,7 +88,7 @@ internal async void OnPickLauncherClicked(object sender, RoutedEventArgs e) { try { - string? picked = await _host.PickFileAsync([".exe", ".bat", ".cmd"]); + string? picked = await _services.Dialogs.PickFileAsync([".exe", ".bat", ".cmd"]); if (!string.IsNullOrEmpty(picked)) { Paths.Launcher = picked; @@ -101,11 +101,11 @@ internal async void OnPickLauncherClicked(object sender, RoutedEventArgs e) } } - internal void OnOpenSourceClicked(object sender, RoutedEventArgs e) => _host.OpenDirectory(Paths.Source, T("LocalLibrary.SourceFolder")); + internal void OnOpenSourceClicked(object sender, RoutedEventArgs e) => _services.Dialogs.OpenDirectory(Paths.Source, T("LocalLibrary.SourceFolder")); - internal void OnOpenTargetClicked(object sender, RoutedEventArgs e) => _host.OpenDirectory(Paths.Target, T("LocalLibrary.TargetFolder")); + internal void OnOpenTargetClicked(object sender, RoutedEventArgs e) => _services.Dialogs.OpenDirectory(Paths.Target, T("LocalLibrary.TargetFolder")); - internal void OnOpenLauncherClicked(object sender, RoutedEventArgs e) => _host.OpenFileLocation(Paths.Launcher, T("LocalLibrary.Launcher")); + internal void OnOpenLauncherClicked(object sender, RoutedEventArgs e) => _services.Dialogs.OpenFileLocation(Paths.Launcher, T("LocalLibrary.Launcher")); internal async void OnRunLauncherClicked(object sender, RoutedEventArgs e) => await _host.RunUiActionAsync("OnRunLauncherClicked", _host.RunRepositoryLauncherAsync); @@ -124,7 +124,7 @@ await ShowMessageAsync( return; } - string? archive = await _host.PickFileAsync(_host.SupportedArchiveExtensions + string? archive = await _services.Dialogs.PickFileAsync(_host.SupportedArchiveExtensions .Select(value => Path.GetExtension(value)!) .Where(extension => !string.IsNullOrWhiteSpace(extension)) .Distinct(StringComparer.OrdinalIgnoreCase).ToArray()); @@ -196,7 +196,7 @@ private MenuFlyout CreateFirstLevelMenu(FirstLevelFolderItem item) Text = T("LocalLibrary.OpenFolder"), Icon = new FontIcon { Glyph = "\uE838" } }; - openItem.Click += (_, _) => _host.OpenDirectory(item.Path, T("LocalLibrary.FirstLevelFolder")); + openItem.Click += (_, _) => _services.Dialogs.OpenDirectory(item.Path, T("LocalLibrary.FirstLevelFolder")); menu.Items.Add(openItem); var renameItem = new MenuFlyoutItem @@ -290,7 +290,7 @@ await ShowMessageAsync( return; } - WorkspaceRepository repository = _host.GetSelectedRepository()!; + WorkspaceRepository repository = _services.Repositories.Selected!; string? loader = string.IsNullOrWhiteSpace(repository.TargetPath) ? null : repository.TargetPath; CharacterRenamePlan plan = await Task.Run(() => CharacterRename.Prepare(item.Path, trimmed, loader)); if (!plan.CanRename) @@ -397,7 +397,7 @@ private void OnSecondLevelItemRightTapped(object sender, RightTappedRoutedEventA Text = T("LocalLibrary.OpenModFolder"), Icon = new FontIcon { Glyph = "\uE838" } }; - openItem.Click += (_, _) => _host.OpenDirectory(item.Path, T("Common.ModFolder")); + openItem.Click += (_, _) => _services.Dialogs.OpenDirectory(item.Path, T("Common.ModFolder")); menu.Items.Add(openItem); menu.Items.Add(new MenuFlyoutSeparator()); @@ -446,7 +446,7 @@ await ShowMessageAsync( return; } - string loaderRoot = (_host.GetSelectedRepository()?.TargetPath ?? Paths.Target).Trim(); + string loaderRoot = (_services.Repositories.Selected?.TargetPath ?? Paths.Target).Trim(); // Without the full list of links, deleting the folder could leave links to a missing folder (issue #109): // a loader folder that is set but cannot be reached stops the delete like a failed scan. LinkScanResult scan = await Task.Run(() => DirectoryLinkDeployment.ScanLinksTargeting(item.Path, loaderRoot)); @@ -507,7 +507,7 @@ private void OnDropTargetDragLeave(object sender, DragEventArgs e) private void SetDropTargetHighlight(Border border, bool isActive) { - border.BorderBrush = _host.GetAppThemeBrush(isActive ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"); + border.BorderBrush = GetAppThemeBrush(isActive ? "AppNavSelectedBorderBrush" : "AppCardBorderBrush"); border.BorderThickness = isActive ? new Thickness(2) : new Thickness(1); } @@ -684,7 +684,7 @@ internal async Task RefreshListsAsync() } _host.SaveConfig(); - string repositoryId = _host.GetSelectedRepository()?.Id ?? sourceDir; + string repositoryId = _services.Repositories.Selected?.Id ?? sourceDir; using var cancellation = new CancellationTokenSource(); _host.FolderRefreshCancellation = cancellation; _workspaceScanning = true; @@ -706,7 +706,7 @@ internal async Task RefreshListsAsync() timing?.SetCounts(loaded.SecondCount); ApplyFirstLevelFilter(); _ = RefreshCharacterFolderAvatarsAsync(); - if (_host.GetSelectedRepository() is { SourcePath: { Length: > 0 } scannedSource } scanned + if (_services.Repositories.Selected is { SourcePath: { Length: > 0 } scannedSource } scanned && string.Equals(Path.GetFullPath(scannedSource).TrimEnd('\\'), Path.GetFullPath(sourceDir).TrimEnd('\\'), StringComparison.OrdinalIgnoreCase)) _ = _host.SeedDefaultSlotsAsync(scanned, loaded.Items); FirstCountTextBlock.Text = _host.Characters.All.Count.ToString(); @@ -839,7 +839,7 @@ await ShowMessageAsync( } string targetPath = GetTargetDirectoryPath(targetDir, item.Path); - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null) { await ShowMessageAsync(T("Common.SelectARepositoryFirst"), T("LocalLibrary.CannotContinue")); diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.LocalTags.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.LocalTags.cs index 78784e7..6fbd290 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.LocalTags.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.LocalTags.cs @@ -15,7 +15,7 @@ public sealed partial class WorkspacePage private LocalLibraryState? SelectedLocalLibrary() { - var repo = _host.GetSelectedRepository(); + var repo = _services.Repositories.Selected; if (repo is null || string.IsNullOrWhiteSpace(repo.SourcePath)) return null; string key = WorkspaceIdentity.Library(repo.Id, repo.SourcePath); if (_localLibraryKey != key) { _localLibraryKey = key; _localTagFilter = ""; } @@ -27,7 +27,7 @@ public sealed partial class WorkspacePage // Those are not part of this repository's library, so they get null instead of a throw. private string? LocalModRelativePath(string path) { - string? source = _host.GetSelectedRepository()?.SourcePath; + string? source = _services.Repositories.Selected?.SourcePath; return string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(path) || !IntegratedModManager.Core.Infrastructure.PathSafety.IsStrictlyInside(path, source) ? null @@ -75,7 +75,7 @@ private string LocalModTagSummary(string path) private async Task EditLocalTagsAsync(SecondLevelFolderItem? single = null) { - var repo = _host.GetSelectedRepository(); + var repo = _services.Repositories.Selected; var library = SelectedLocalLibrary(); if (repo is null || library is null) return; string repoKey = WorkspaceIdentity.Library(repo.Id, repo.SourcePath); @@ -84,7 +84,7 @@ private async Task EditLocalTagsAsync(SecondLevelFolderItem? single = null) var entries = items.Select(mod => new LocalTagsDialog.Entry(mod, mod.Name + LocalModTagSummary(mod.Path), ReferenceEquals(mod, single ?? _modList.SelectedMod), library.TagsFor(WorkspaceIdentity.Relative(repo.SourcePath, mod.Path)))).ToArray(); if (await LocalTagsDialog.ShowAsync(_services, entries, _host.Root.ActualHeight) is not { } edit) return; - if (_host.GetSelectedRepository() is not { } current + if (_services.Repositories.Selected is not { } current || WorkspaceIdentity.Library(current.Id, current.SourcePath) != repoKey) return; var (chosenMods, tags, mode) = edit; library.EditTags(chosenMods.Cast() @@ -118,7 +118,7 @@ private void ShowLocalFilterMenu() { try { - var repo = _host.GetSelectedRepository(); + var repo = _services.Repositories.Selected; if (repo is null) return; string role = Path.Combine(repo.SourcePath, filter.Character.Replace('/', Path.DirectorySeparatorChar)); if (!Directory.Exists(role) || !OnlinePage.IsPathInsideDirectory(role, repo.SourcePath)) @@ -144,7 +144,7 @@ private void ShowLocalFilterMenu() private async Task SaveLocalFilterAsync() { - var repo = _host.GetSelectedRepository(); + var repo = _services.Repositories.Selected; var library = SelectedLocalLibrary(); if (repo is null || library is null || _host.Characters.Selected is not { } role) return; var snapshot = new LocalModFilter { Character = WorkspaceIdentity.Relative(repo.SourcePath, role.Path), Search = _repositoryModSearch!.Text, Tag = _localTagFilter }; diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.RepositoryWorkspace.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.RepositoryWorkspace.cs index 42ab148..65be0da 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.RepositoryWorkspace.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.RepositoryWorkspace.cs @@ -183,7 +183,7 @@ private void OnRepositoryWorkspacePickerSelectionChanged(object sender, Selectio private void OnRepositoryOpenModFolderClicked(object sender, RoutedEventArgs e) { - if (GetSelectedSecondLevelItem() is { } mod) _host.OpenDirectory(mod.Path, T("Common.ModFolder")); + if (GetSelectedSecondLevelItem() is { } mod) _services.Dialogs.OpenDirectory(mod.Path, T("Common.ModFolder")); } private void InitializeRepositoryToolbarV4() @@ -288,7 +288,7 @@ private void RefreshRepositoryStatusV4() string target = Paths.Target.Trim(); bool connected = target.Length > 0 && Directory.Exists(target); _repositoryTargetStatus.Text = connected ? T("RepositoryWorkspace.TargetConnected") : T("RepositoryWorkspace.TargetMissing"); - _repositoryTargetStatus.Foreground = _host.GetAppThemeBrush(connected ? "AppSuccessBrush" : "AppDangerBrush"); + _repositoryTargetStatus.Foreground = GetAppThemeBrush(connected ? "AppSuccessBrush" : "AppDangerBrush"); } } @@ -574,9 +574,9 @@ private void UpdateRepositoryWorkspaceLanguageV4() AutomationProperties.SetName(_repositoryModeButtons[index], labels[index]); foreach (string state in new[] { "Checked", "CheckedPointerOver", "CheckedPressed" }) { - _repositoryModeButtons[index].Resources["ToggleButtonBackground" + state] = _host.GetAppThemeBrush("AppSecondarySelectedBrush"); - _repositoryModeButtons[index].Resources["ToggleButtonForeground" + state] = _host.GetAppThemeBrush("AppSecondarySelectedForegroundBrush"); - _repositoryModeButtons[index].Resources["ToggleButtonBorderBrush" + state] = _host.GetAppThemeBrush("AppNavSelectedBorderBrush"); + _repositoryModeButtons[index].Resources["ToggleButtonBackground" + state] = GetAppThemeBrush("AppSecondarySelectedBrush"); + _repositoryModeButtons[index].Resources["ToggleButtonForeground" + state] = GetAppThemeBrush("AppSecondarySelectedForegroundBrush"); + _repositoryModeButtons[index].Resources["ToggleButtonBorderBrush" + state] = GetAppThemeBrush("AppNavSelectedBorderBrush"); } } _repositoryOpenDetails!.Content = RepositoryIconLabel("\uE8A5", T("RepositoryWorkspace.Details")); @@ -587,7 +587,7 @@ private void UpdateRepositoryWorkspaceLanguageV4() AutomationProperties.SetName(_repositoryCopyModPath, T("RepositoryWorkspace.CopyPath")); PreviewTab.Header = T("RepositoryWorkspace.Preview"); SelectionEyebrowTextBlock.Text = T("RepositoryWorkspace.CURRENTMOD"); - CurrentFolderTextBlock.Foreground = _host.GetAppThemeBrush("AppNavDefaultForegroundBrush"); + CurrentFolderTextBlock.Foreground = GetAppThemeBrush("AppNavDefaultForegroundBrush"); DeploymentStateLabelTextBlock.Visibility = Visibility.Collapsed; _combinationWorkspaceTitle!.Text = T("RepositoryWorkspace.MyPresets"); AutomationProperties.SetName(DeleteConfigurationProfileButton, T("RepositoryWorkspace.DeletePreset")); @@ -603,9 +603,9 @@ private void UpdateRepositoryWorkspaceLanguageV4() _repositoryStateChips[index].Content = chipLabels[index]; foreach (string state in new[] { "Checked", "CheckedPointerOver", "CheckedPressed" }) { - _repositoryStateChips[index].Resources["ToggleButtonBackground" + state] = _host.GetAppThemeBrush("AppSecondarySelectedBrush"); - _repositoryStateChips[index].Resources["ToggleButtonForeground" + state] = _host.GetAppThemeBrush("AppSecondarySelectedForegroundBrush"); - _repositoryStateChips[index].Resources["ToggleButtonBorderBrush" + state] = _host.GetAppThemeBrush("AppNavSelectedBorderBrush"); + _repositoryStateChips[index].Resources["ToggleButtonBackground" + state] = GetAppThemeBrush("AppSecondarySelectedBrush"); + _repositoryStateChips[index].Resources["ToggleButtonForeground" + state] = GetAppThemeBrush("AppSecondarySelectedForegroundBrush"); + _repositoryStateChips[index].Resources["ToggleButtonBorderBrush" + state] = GetAppThemeBrush("AppNavSelectedBorderBrush"); } } CharacterFolderHintTextBlock.Text = T("RepositoryWorkspace.CharacterFolderHint"); @@ -652,7 +652,7 @@ private void EnsureWorkspaceSplitters() private PaneSplitter CreateWorkspaceSplitter(string automationId, string name) { - var splitter = new PaneSplitter { Width = RepositoryWorkspacePolicy.SplitterWidth, LineBrush = _host.GetAppThemeBrush("AppAccentBrush") }; + var splitter = new PaneSplitter { Width = RepositoryWorkspacePolicy.SplitterWidth, LineBrush = GetAppThemeBrush("AppAccentBrush") }; AutomationProperties.SetAutomationId(splitter, automationId); AutomationProperties.SetName(splitter, name); ToolTipService.SetToolTip(splitter, name); @@ -968,11 +968,11 @@ private Border CreateRepositorySetupCard() { var row = RepositoryColumns(GridLength.Auto, new GridLength(1, GridUnitType.Star), GridLength.Auto); row.ColumnSpacing = 10; - var done = new FontIcon { Glyph = "\uE73E", FontSize = 14, Foreground = _host.GetAppThemeBrush("AppSuccessBrush"), VerticalAlignment = VerticalAlignment.Center }; + var done = new FontIcon { Glyph = "\uE73E", FontSize = 14, Foreground = GetAppThemeBrush("AppSuccessBrush"), VerticalAlignment = VerticalAlignment.Center }; var number = new Border { Width = 20, Height = 20, CornerRadius = new CornerRadius(10), BorderThickness = new Thickness(1), - BorderBrush = _host.GetAppThemeBrush("AppStrongBorderBrush"), VerticalAlignment = VerticalAlignment.Center, + BorderBrush = GetAppThemeBrush("AppStrongBorderBrush"), VerticalAlignment = VerticalAlignment.Center, Child = new TextBlock { Text = (index + 1).ToString(System.Globalization.CultureInfo.InvariantCulture), FontSize = 11, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center } }; var marks = new Grid { Width = 20, VerticalAlignment = VerticalAlignment.Center }; @@ -1004,7 +1004,7 @@ private Border CreateRepositorySetupCard() private RepositorySetup CurrentRepositorySetup() { - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; string source = Paths.Source.Trim(); string target = Paths.Target.Trim(); // The game counts as chosen when the repository has one, its own or the default preset. @@ -1018,7 +1018,7 @@ private void UpdateRepositorySetupCard(RepositorySetup setup) { _repositorySetupTitle!.Text = T("RepositoryWorkspace.SetupTitle"); _repositorySetupProgress!.Text = T("RepositoryWorkspace.SetupProgress", setup.Done, RepositorySetup.Total); - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; string source = Paths.Source.Trim(); (bool Done, string Title, string Detail, string Action)[] steps = [ @@ -1137,7 +1137,7 @@ private void UpdateRepositorySelectedMetadataV4() _repositoryInfoTags.Children.Add(new Border { Padding = new Thickness(8, 1, 8, 2), CornerRadius = new CornerRadius(4), - Background = _host.GetAppThemeBrush("AppSecondaryDefaultBrush"), + Background = GetAppThemeBrush("AppSecondaryDefaultBrush"), Child = new TextBlock { Text = tag, FontSize = 12 } }); } @@ -1147,7 +1147,7 @@ private void UpdateRepositorySelectedMetadataV4() int shortcuts = mod is null ? 0 : ModShortcutCount(mod); if (ShortcutTab is not null) ShortcutTab.Header = shortcuts > 0 ? T("Common.Shortcuts") + " · " + shortcuts : T("Common.Shortcuts"); // With links, deploying replaces the character's deployed Mod (unless it keeps several); say which one. - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; bool links = repository?.UseDirectoryLinks == true; bool several = links && mod is not null && AllowsMultipleMods(repository!, mod.Path); string? replaced = mod is null || !links || ModTone(mod) == ModStateTone.Deployed || several ? null @@ -1547,7 +1547,7 @@ internal void OnLinkDeploymentChanged() return; } - WorkspaceRepository? repository = _host.GetSelectedRepository(); + WorkspaceRepository? repository = _services.Repositories.Selected; if (repository is null) { return; diff --git a/src/WinUI3/Pages/Workspace/WorkspacePage.ShortcutBindings.cs b/src/WinUI3/Pages/Workspace/WorkspacePage.ShortcutBindings.cs index 9717c95..53872db 100644 --- a/src/WinUI3/Pages/Workspace/WorkspacePage.ShortcutBindings.cs +++ b/src/WinUI3/Pages/Workspace/WorkspacePage.ShortcutBindings.cs @@ -118,7 +118,7 @@ private void ConfigureShortcutTextBoxTheme(TextBox box) box.Resources["TextControlBorderBrushPointerOver"] = transparentBrush; box.Resources["TextControlBorderBrushDisabled"] = transparentBrush; box.Resources["TextControlBorderBrushReadOnly"] = transparentBrush; - box.Resources["TextControlForegroundReadOnly"] = _host.GetAppThemeBrush("AppNavDefaultForegroundBrush"); + box.Resources["TextControlForegroundReadOnly"] = GetAppThemeBrush("AppNavDefaultForegroundBrush"); } private void OnShortcutKeyBoxGotFocus(object sender, RoutedEventArgs e) @@ -139,10 +139,10 @@ private void OnShortcutKeyBoxLostFocus(object sender, RoutedEventArgs e) internal void UpdateShortcutKeyFocusVisuals() { - Brush normalBackground = _host.GetAppThemeBrush("AppInsetBackgroundBrush"); - Brush normalBorder = _host.GetAppThemeBrush("AppCardBorderBrush"); - Brush activeBackground = _host.GetAppThemeBrush("AppAccentSoftBrush"); - Brush activeBorder = _host.GetAppThemeBrush("AppNavSelectedBorderBrush"); + Brush normalBackground = GetAppThemeBrush("AppInsetBackgroundBrush"); + Brush normalBorder = GetAppThemeBrush("AppCardBorderBrush"); + Brush activeBackground = GetAppThemeBrush("AppAccentSoftBrush"); + Brush activeBorder = GetAppThemeBrush("AppNavSelectedBorderBrush"); object? focusedElement = Content is FrameworkElement root && root.XamlRoot is not null ? FocusManager.GetFocusedElement(root.XamlRoot) : null; diff --git a/src/WinUI3/Pages/WorkspacePage.xaml.cs b/src/WinUI3/Pages/WorkspacePage.xaml.cs index 5421ebd..53a3495 100644 --- a/src/WinUI3/Pages/WorkspacePage.xaml.cs +++ b/src/WinUI3/Pages/WorkspacePage.xaml.cs @@ -1,4 +1,5 @@ using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; namespace ModFolderCopier.WinUI; @@ -25,6 +26,9 @@ internal WorkspacePage(AppServices services, IWorkspaceHost host) private bool IsChineseUi => _services.Language.IsChinese; + // The App*Brush tokens of Themes/Tokens.xaml, in the theme the window shows (stage J finish, step 2). + private Brush GetAppThemeBrush(string resourceKey) => ThemeTokens.Get(resourceKey, _services.Appearance.ThemeKey); + private string T(string key) => _services.Language.T(key); private string T(string key, params object?[] values) => _services.Language.T(key, values); diff --git a/src/WinUI3/Services/DialogService.cs b/src/WinUI3/Services/DialogService.cs index 9a8faf0..fe2d3b4 100644 --- a/src/WinUI3/Services/DialogService.cs +++ b/src/WinUI3/Services/DialogService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using IntegratedModManager.Core.Layout; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -25,6 +26,11 @@ internal sealed class DialogService(LanguageService language) private string T(string key, params object?[] values) => language.T(key, values); + private Func _windowHandle = () => 0; + + /// The window's handle, for the file pickers (stage J finish, step 2). + public void AttachWindow(Func handle) => _windowHandle = handle; + /// Called once the window exists. public void Attach(Func root, Func? theme = null) { @@ -217,4 +223,78 @@ private void Prepare(ContentDialog dialog) if (dialog.Style is null && Application.Current.Resources.TryGetValue("DefaultContentDialogStyle", out object style)) dialog.Style = (Style)style; } + + // Pickers and opening folders, files and links (moved from the window in stage J finish, step 2). + public async Task OpenExternalUrlAsync(string? url, string failureTitle) + { + if (string.IsNullOrWhiteSpace(url)) + { + await MessageAsync(failureTitle, T("Dialogs.ThisEntryDoesNotHaveA")); + return; + } + + try + { + Process.Start(new ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }); + } + catch (Exception ex) + { + await MessageAsync(failureTitle, T("Common.FailedToOpenLink") + ex.Message); + } + } + + public Task PickFolderAsync(string? preferredPath = null) => + Task.FromResult(NativeFileDialog.PickFolder(_windowHandle())); + + public Task PickFileAsync(string[] extensions) => + Task.FromResult(NativeFileDialog.PickFile(_windowHandle(), extensions)); + + public void OpenDirectory(string? directory, string title) + { + try + { + if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory)) + { + _ = MessageAsync(T("Common.InvalidPath"), title + T("Dialogs.DoesNotExistPleaseChooseIt")); + return; + } + + Process.Start(new ProcessStartInfo + { + FileName = directory, + UseShellExecute = true + }); + } + catch (Exception ex) + { + _ = MessageAsync(T("Dialogs.OperationFailed"), T("Dialogs.FailedToOpenFolder") + ex.Message); + } + } + + public void OpenFileLocation(string? filePath, string title) + { + try + { + if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath)) + { + _ = MessageAsync(T("Common.InvalidPath"), title + T("Dialogs.DoesNotExistPleaseChooseIt")); + return; + } + + Process.Start(new ProcessStartInfo + { + FileName = "explorer.exe", + Arguments = "/select,\"" + filePath + "\"", + UseShellExecute = true + }); + } + catch (Exception ex) + { + _ = MessageAsync(T("Dialogs.OperationFailed"), T("Dialogs.FailedToOpenFileLocation") + ex.Message); + } + } }