diff --git a/src/GIMI-ModManager.Core/Helpers/Constants.cs b/src/GIMI-ModManager.Core/Helpers/Constants.cs index 63560e59..18435653 100644 --- a/src/GIMI-ModManager.Core/Helpers/Constants.cs +++ b/src/GIMI-ModManager.Core/Helpers/Constants.cs @@ -9,6 +9,6 @@ public static class Constants public static readonly string ModConfigFileName = ".JASM_ModConfig.json"; public static readonly string ShaderFixesFolderName = "ShaderFixes"; - public static readonly string[] ScriptIniNames = ["Script.ini", "merged.ini"]; + public static readonly string[] ScriptIniNames = ["Script.ini", "merged.ini", "mod.ini"]; public static readonly string UserIniFileName = "d3dx_user.ini"; } \ No newline at end of file diff --git a/src/GIMI-ModManager.Core/Services/GameBanana/ModArchiveRepository.cs b/src/GIMI-ModManager.Core/Services/GameBanana/ModArchiveRepository.cs index 1592de0d..8598693e 100644 --- a/src/GIMI-ModManager.Core/Services/GameBanana/ModArchiveRepository.cs +++ b/src/GIMI-ModManager.Core/Services/GameBanana/ModArchiveRepository.cs @@ -28,6 +28,7 @@ public ModArchiveRepository(ILogger logger, ArchiveService archiveService) _logger = logger.ForContext(); } + public Uri ArchiveDirectory => new(_modArchiveDirectory.FullName); public async Task InitializeAsync(string appDataFolder, Action? setup = null) { diff --git a/src/GIMI-ModManager.WinUI/App.xaml.cs b/src/GIMI-ModManager.WinUI/App.xaml.cs index 78b6e2a3..67c9c600 100644 --- a/src/GIMI-ModManager.WinUI/App.xaml.cs +++ b/src/GIMI-ModManager.WinUI/App.xaml.cs @@ -38,7 +38,6 @@ using Serilog; using Serilog.Events; using Serilog.Templates; -using CharacterDetailsPage = GIMI_ModManager.WinUI.Views.CharacterDetailsPage; using CreateCommandViewModel = GIMI_ModManager.WinUI.ViewModels.SettingsViewModels.CreateCommandViewModel; using GameBananaService = GIMI_ModManager.WinUI.Services.ModHandling.GameBananaService; using NotificationManager = GIMI_ModManager.WinUI.Services.Notifications.NotificationManager; @@ -241,8 +240,6 @@ public App() services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Chevreuse.png b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Chevreuse.png index 1683b3b8..a08d03b5 100644 Binary files a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Chevreuse.png and b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Chevreuse.png differ diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Citlali.png b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Citlali.png index 73c0520f..36847589 100644 Binary files a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Citlali.png and b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Citlali.png differ diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Clorinde.png b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Clorinde.png index c31d6e4a..ed59f5ee 100644 Binary files a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Clorinde.png and b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Clorinde.png differ diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Mavuika.png b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Mavuika.png index 527b9ff8..3382d738 100644 Binary files a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Mavuika.png and b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Mavuika.png differ diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sethos.png b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sethos.png index 3e2c3845..cc6929e4 100644 Binary files a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sethos.png and b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sethos.png differ diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sigewinne.png b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sigewinne.png new file mode 100644 index 00000000..5d211678 Binary files /dev/null and b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sigewinne.png differ diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sigewinne.webp b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sigewinne.webp deleted file mode 100644 index 32770b0a..00000000 Binary files a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/Images/Characters/Sigewinne.webp and /dev/null differ diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/characters.json b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/characters.json index 8b42672d..75b4b30b 100644 --- a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/characters.json +++ b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/characters.json @@ -1584,7 +1584,7 @@ "Sigewinne" ], "ReleaseDate": "2024-06-05T00:00:00", - "Image": "Sigewinne.webp", + "Image": "Sigewinne.png", "Rarity": 5, "Element": "Hydro", "Class": "Bow", diff --git a/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/fixImages.py b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/fixImages.py new file mode 100644 index 00000000..30dcfc33 --- /dev/null +++ b/src/GIMI-ModManager.WinUI/Assets/Games/Genshin/fixImages.py @@ -0,0 +1,43 @@ +import json +import os + + +charactersJson = json.load(open('characters.json')) + +for character in charactersJson: + character['Image'] = character['Image'].replace("Character_", "").replace("_Thumb", "") + if (character['InGameSkins']): + for skin in character['InGameSkins']: + skin['Image'] = skin['Image'].replace("Character_", "").replace("_Thumb", "").replace("Skin_", "") + +json.dump(charactersJson, open('characters.json', 'w'), indent=2) + + +# Update image files in Images folder +images_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Images', 'Characters')) + +for filename in os.listdir(images_folder): + new_filename = filename.replace("Character_", "").replace("_Thumb", "") + os.rename(os.path.join(images_folder, filename), os.path.join(images_folder, new_filename)) + + +images_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Images', 'AltCharacterSkins')) + +for filename in os.listdir(images_folder): + new_filename = filename.replace("Character_Skin_", "").replace("_Thumb", "").replace("Skin_", "") + os.rename(os.path.join(images_folder, filename), os.path.join(images_folder, new_filename)) + + +weaponsJson = json.load(open('weapons.json')) + +for weapon in weaponsJson: + if (weapon['Image']): + weapon['Image'] = weapon['Image'].replace("Weapon_", "") + +json.dump(weaponsJson, open('weapons.json', 'w'), indent=2) + +images_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Images', 'Weapons')) + +for filename in os.listdir(images_folder): + new_filename = filename.replace("Weapon_", "") + os.rename(os.path.join(images_folder, filename), os.path.join(images_folder, new_filename)) \ No newline at end of file diff --git a/src/GIMI-ModManager.WinUI/GIMI-ModManager.WinUI.csproj b/src/GIMI-ModManager.WinUI/GIMI-ModManager.WinUI.csproj index 6d9aba59..fadae3ca 100644 --- a/src/GIMI-ModManager.WinUI/GIMI-ModManager.WinUI.csproj +++ b/src/GIMI-ModManager.WinUI/GIMI-ModManager.WinUI.csproj @@ -138,7 +138,6 @@ - @@ -232,9 +231,6 @@ MSBuild:Compile - - MSBuild:Compile - MSBuild:Compile diff --git a/src/GIMI-ModManager.WinUI/Models/Settings/CharacterDetailsSettings.cs b/src/GIMI-ModManager.WinUI/Models/Settings/CharacterDetailsSettings.cs index 2088872c..62dfd9d1 100644 --- a/src/GIMI-ModManager.WinUI/Models/Settings/CharacterDetailsSettings.cs +++ b/src/GIMI-ModManager.WinUI/Models/Settings/CharacterDetailsSettings.cs @@ -7,9 +7,6 @@ public class CharacterDetailsSettings [JsonIgnore] public const string Key = "CharacterDetailsSettings"; public bool GalleryView { get; set; } = false; - - public bool LegacyCharacterDetails { get; set; } = false; - public bool SingleSelect { get; set; } public string? SortingMethod { get; set; } public bool? SortByDescending { get; set; } diff --git a/src/GIMI-ModManager.WinUI/Services/ModHandling/ModSettingsService.cs b/src/GIMI-ModManager.WinUI/Services/ModHandling/ModSettingsService.cs index 40e192a4..27eef332 100644 --- a/src/GIMI-ModManager.WinUI/Services/ModHandling/ModSettingsService.cs +++ b/src/GIMI-ModManager.WinUI/Services/ModHandling/ModSettingsService.cs @@ -5,7 +5,6 @@ using GIMI_ModManager.Core.Entities.Mods.Exceptions; using GIMI_ModManager.Core.Entities.Mods.Helpers; using GIMI_ModManager.Core.Helpers; -using GIMI_ModManager.WinUI.Models; using GIMI_ModManager.WinUI.Services.Notifications; using OneOf; using OneOf.Types; @@ -31,50 +30,6 @@ public ModSettingsService(ISkinManagerService skinManagerService, } - public async Task>> LegacySaveSettingsAsync(ModModel modModel) - { - var mod = _skinManagerService.GetModById(modModel.Id); - - if (mod is null) - return new ModNotFound(modModel.Id); - - if (!mod.Settings.TryGetSettings(out var modSettings)) - return new Error(new ModSettingsNotFoundException(mod.FullPath)); - - - var modUrl = Uri.TryCreate(modModel.ModUrl, UriKind.Absolute, out var uriResult) && - (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps) - ? uriResult - : null; - - modSettings = modSettings.DeepCopyWithProperties( - customName: NewValue.Set(EmptyStringToNull(modModel.Name)), - author: NewValue.Set(EmptyStringToNull(modModel.Author)), - modUrl: NewValue.Set(modUrl), - imagePath: NewValue.Set(modModel.ImagePath == ModModel.PlaceholderImagePath - ? null - : modModel.ImagePath), - characterSkinOverride: NewValue.Set(EmptyStringToNull(modModel.CharacterSkinOverride)) - ); - - - try - { - await mod.Settings.SaveSettingsAsync(modSettings).ConfigureAwait(false); - return new Success(); - } - catch (Exception e) - { - _logger.Error(e, "Failed to save settings for mod {modName}", mod.Name); - - _notificationManager.ShowNotification($"Failed to save settings for mod {mod.Name}", - $"An Error Occurred. Reason: {e.Message}", - TimeSpan.FromSeconds(5)); - - return new Error(e); - } - } - public async Task>> SetCharacterSkinOverrideLegacy(Guid modId, string skinName) { diff --git a/src/GIMI-ModManager.WinUI/Services/NavigationService.cs b/src/GIMI-ModManager.WinUI/Services/NavigationService.cs index 94dcac32..ae8c5da7 100644 --- a/src/GIMI-ModManager.WinUI/Services/NavigationService.cs +++ b/src/GIMI-ModManager.WinUI/Services/NavigationService.cs @@ -4,7 +4,7 @@ using GIMI_ModManager.WinUI.Contracts.ViewModels; using GIMI_ModManager.WinUI.Helpers; using GIMI_ModManager.WinUI.Models.Settings; -using GIMI_ModManager.WinUI.ViewModels; +using GIMI_ModManager.WinUI.ViewModels.CharacterDetailsViewModels; using GIMI_ModManager.WinUI.ViewModels.CharacterGalleryViewModels; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Animation; @@ -153,9 +153,7 @@ public bool NavigateToCharacterDetails(string internalName, bool clearNavigation return NavigateTo(typeof(CharacterGalleryViewModel).FullName!, internalName, clearNavigation: clearNavigation); } - var pageKey = settings is null || !settings.LegacyCharacterDetails - ? typeof(ViewModels.CharacterDetailsViewModels.CharacterDetailsViewModel).FullName - : typeof(CharacterDetailsViewModel).FullName; + var pageKey = typeof(CharacterDetailsViewModel).FullName; return NavigateTo(pageKey!, internalName, clearNavigation); } diff --git a/src/GIMI-ModManager.WinUI/Services/PageService.cs b/src/GIMI-ModManager.WinUI/Services/PageService.cs index 9a4a913e..da799b80 100644 --- a/src/GIMI-ModManager.WinUI/Services/PageService.cs +++ b/src/GIMI-ModManager.WinUI/Services/PageService.cs @@ -21,7 +21,6 @@ public PageService() Configure(); Configure(); Configure(); - Configure(); Configure(); Configure(); diff --git a/src/GIMI-ModManager.WinUI/ViewModels/CharacterDetailsViewModel.cs b/src/GIMI-ModManager.WinUI/ViewModels/CharacterDetailsViewModel.cs deleted file mode 100644 index 96ccab2b..00000000 --- a/src/GIMI-ModManager.WinUI/ViewModels/CharacterDetailsViewModel.cs +++ /dev/null @@ -1,767 +0,0 @@ -using System.Collections.ObjectModel; -using Windows.Storage; -using Windows.Storage.Pickers; -using Windows.System; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using CommunityToolkitWrapper; -using GIMI_ModManager.Core.Contracts.Entities; -using GIMI_ModManager.Core.Contracts.Services; -using GIMI_ModManager.Core.Entities; -using GIMI_ModManager.Core.Entities.Mods.Contract; -using GIMI_ModManager.Core.GamesService; -using GIMI_ModManager.Core.GamesService.Interfaces; -using GIMI_ModManager.Core.GamesService.Models; -using GIMI_ModManager.Core.Helpers; -using GIMI_ModManager.Core.Services; -using GIMI_ModManager.WinUI.Contracts.Services; -using GIMI_ModManager.WinUI.Contracts.ViewModels; -using GIMI_ModManager.WinUI.Models; -using GIMI_ModManager.WinUI.Models.CustomControlTemplates; -using GIMI_ModManager.WinUI.Models.Options; -using GIMI_ModManager.WinUI.Models.Settings; -using GIMI_ModManager.WinUI.Services; -using GIMI_ModManager.WinUI.Services.AppManagement; -using GIMI_ModManager.WinUI.Services.ModHandling; -using GIMI_ModManager.WinUI.Services.Notifications; -using GIMI_ModManager.WinUI.ViewModels.CharacterGalleryViewModels; -using GIMI_ModManager.WinUI.ViewModels.SubVms; -using GIMI_ModManager.WinUI.Views; -using Serilog; - -namespace GIMI_ModManager.WinUI.ViewModels; - -public partial class CharacterDetailsViewModel : ObservableRecipient, INavigationAware -{ - private readonly IGameService _gameService; - private readonly ILogger _logger; - private readonly INavigationService _navigationService; - private readonly ISkinManagerService _skinManagerService; - private readonly ILocalSettingsService _localSettingsService; - private readonly IWindowManagerService _windowManagerService; - private readonly NotificationManager _notificationService; - private readonly ModDragAndDropService _modDragAndDropService; - private readonly ModCrawlerService _modCrawlerService; - private readonly ModNotificationManager _modNotificationManager; - private readonly ModSettingsService _modSettingsService; - private readonly ImageHandlerService _imageHandlerService; - private readonly ElevatorService _elevatorService; - private readonly CharacterSkinService _characterSkinService; - - private ICharacterModList _modList = null!; - private ICategory? _category; - public ModListVM ModListVM { get; } = null!; - public ModPaneVM ModPaneVM { get; } = null!; - - public MoveModsFlyoutVM MoveModsFlyoutVM { get; private set; } - - [ObservableProperty] private IModdableObject _shownCharacter = null!; - - public readonly ObservableCollection SelectableInGameSkins = new(); - - [ObservableProperty] public bool _multipleInGameSkins = false; - - [ObservableProperty] private ICharacterSkin? _selectedInGameSkin; - - [ObservableProperty] private Uri _moddableObjectImage; - - private static readonly Dictionary _lastSelectedSkin = new(); - - public ModListVM.SortMethod? SortMethod { get; set; } - - [ObservableProperty] private bool _isICharacter = false; - - - public CharacterDetailsViewModel(IGameService gameService, ILogger logger, - INavigationService navigationService, ISkinManagerService skinManagerService, - NotificationManager notificationService, ILocalSettingsService localSettingsService, - ModDragAndDropService modDragAndDropService, ModCrawlerService modCrawlerService, - ModNotificationManager modNotificationManager, ModSettingsService modSettingsService, - IWindowManagerService windowManagerService, ImageHandlerService imageHandlerService, - ElevatorService elevatorService, CharacterSkinService characterSkinService) - { - _gameService = gameService; - _logger = logger.ForContext(); - _navigationService = navigationService; - _skinManagerService = skinManagerService; - _notificationService = notificationService; - _localSettingsService = localSettingsService; - _modDragAndDropService = modDragAndDropService; - _modCrawlerService = modCrawlerService; - _modNotificationManager = modNotificationManager; - _modSettingsService = modSettingsService; - _windowManagerService = windowManagerService; - _imageHandlerService = imageHandlerService; - _elevatorService = elevatorService; - _characterSkinService = characterSkinService; - - _modDragAndDropService.DragAndDropFinished += OnDragAndDropFinished; - - ModdableObjectImage = _imageHandlerService.PlaceholderImageUri; - - MoveModsFlyoutVM = new MoveModsFlyoutVM(_gameService, _skinManagerService); - MoveModsFlyoutVM.ModsMoved += async (sender, args) => await RefreshMods().ConfigureAwait(false); - MoveModsFlyoutVM.ModsDeleted += async (sender, args) => await RefreshMods().ConfigureAwait(false); - MoveModsFlyoutVM.ModCharactersSkinOverriden += - async (sender, args) => await RefreshMods().ConfigureAwait(false); - - ModListVM = new ModListVM(skinManagerService, modNotificationManager); - ModListVM.OnModsSelected += OnModsSelected; - - ModPaneVM = new ModPaneVM(); - ModPaneVM.UnloadMod(); - - _modNotificationManager.OnModNotification += OnOnModNotificationHandler; - } - - private async void OnDragAndDropFinished(object? sender, ModDragAndDropService.DragAndDropFinishedArgs args) - { - foreach (var extractResult in args.ExtractResults) - { - var extractedFolderName = new DirectoryInfo(extractResult.ExtractedFolderPath).Name; - await AddNewModAddedNotificationAsync(AttentionType.Added, extractedFolderName, null); - } - - await App.MainWindow.DispatcherQueue.EnqueueAsync(async () => { await RefreshMods(); }).ConfigureAwait(false); - } - - private void OnOnModNotificationHandler(object? sender, ModNotificationManager.ModNotificationEvent e) - { - App.MainWindow.DispatcherQueue.EnqueueAsync(() => RefreshModsCommand.ExecuteAsync(null)); - } - - private async void OnModsSelected(object? sender, ModListVM.ModSelectedEventArgs args) - { - var selectedMod = args.Mods.FirstOrDefault(); - var mod = _modList.Mods.FirstOrDefault(x => x.Id == selectedMod?.Id); - if (mod is null || selectedMod is null) - { - if (ModPaneVM.SelectedModModel is not null && ModPaneVM.SelectedModModel.Id != Guid.Empty) - ModPaneVM.UnloadMod(); - return; - } - - var recentlyAddedModNotifications = args.Mods.SelectMany(x => - x.ModNotifications.Where(notification => notification.AttentionType == AttentionType.Added)).ToArray(); - - if (recentlyAddedModNotifications.Any()) - foreach (var modNotification in recentlyAddedModNotifications) - { - await _modNotificationManager.RemoveModNotificationAsync(modNotification.Id); - - foreach (var newModModel in args.Mods) - { - var notification = newModModel.ModNotifications.FirstOrDefault(x => x.Id == modNotification.Id); - if (notification is not null) newModModel.ModNotifications.Remove(notification); - } - } - - - await ModPaneVM.LoadMod(selectedMod).ConfigureAwait(false); - } - - private void ModListOnModsChanged(object? sender, ModFolderChangedArgs e) - { - App.MainWindow.DispatcherQueue.EnqueueAsync(async () => - { - if (!IsAddingModFolder) - _notificationService.ShowNotification( - $"Folder Activity Detected in {ShownCharacter.DisplayName}'s Mod Folder", - "Files/Folders were changed in the characters mod folder and mods have been refreshed.", - TimeSpan.FromSeconds(5)); - - - if (e.ChangeType == ModFolderChangeType.Renamed) - { - var inMemoryModNotification = new ModNotification() - { - CharacterInternalName = ShownCharacter.InternalName, - ShowOnOverview = false, - ModFolderName = new DirectoryInfo(e.NewName).Name, - AttentionType = e.ChangeType switch - { - ModFolderChangeType.Created => AttentionType.Added, - ModFolderChangeType.Renamed => AttentionType.Added, - _ => AttentionType.None - }, - Message = e.ChangeType switch - { - ModFolderChangeType.Created => - $"Mod '{e.NewName}' was added to {ShownCharacter.DisplayName}'s mod folder.", - ModFolderChangeType.Renamed => - $"Mod '{e.OldName}' was renamed to '{e.NewName}' in {ShownCharacter.DisplayName}'s mod folder.", - _ => string.Empty - } - }; - - await _modNotificationManager.AddModNotification(inMemoryModNotification); - } - - await RefreshMods().ConfigureAwait(false); - }); - } - - public async void OnNavigatedTo(object parameter) - { - var internalName = new InternalName("_"); - - - if (parameter is CharacterGridItemModel characterGridItemModel) - { - internalName = characterGridItemModel.Character.InternalName; - } - else if (parameter is INameable iInternalName) - { - internalName = iInternalName.InternalName; - } - else if (parameter is string internalNameString) - { - internalName = new InternalName(internalNameString); - } - else if (parameter is InternalName internalName1) - { - internalName = internalName1; - } - - - var moddableObject = _gameService.GetModdableObjectByIdentifier(internalName); - - if (moddableObject is null) - { - ErrorNavigateBack(); - return; - } - - _category = moddableObject.ModCategory; - - if (moddableObject.ImageUri is not null) - ModdableObjectImage = moddableObject.ImageUri; - - - ShownCharacter = moddableObject; - MoveModsFlyoutVM.SetShownCharacter(moddableObject); - - _modList = _skinManagerService.GetCharacterModList(moddableObject.InternalName); - - if (_gameService.IsMultiMod(moddableObject)) - ModListVM.DisableInfoBar = true; - - if (moddableObject is ICharacter character) - { - IsICharacter = true; - var skins = character.Skins.ToArray(); - - foreach (var characterInGameSkin in skins) - { - var skinImage = characterInGameSkin.ImageUri ?? _imageHandlerService.PlaceholderImageUri; - SelectableInGameSkins.Add( - new SelectCharacterTemplate(characterInGameSkin.DisplayName, characterInGameSkin.InternalName, - skinImage.ToString()) - ); - } - - SelectedInGameSkin = skins.First(skinVm => skinVm.IsDefault); - - if (SelectedInGameSkin.ImageUri is not null) - ModdableObjectImage = SelectedInGameSkin.ImageUri; - - MoveModsFlyoutVM.SetActiveSkin(SelectedInGameSkin); - - MultipleInGameSkins = character.Skins.Count > 1; - } - - - try - { - await RefreshMods(); - _modList.ModsChanged += ModListOnModsChanged; - } - catch (Exception e) - { - _logger.Error(e, "Error while refreshing mods."); - _notificationService.ShowNotification("Error while loading modes.", - $"An error occurred while loading the mods for this character.\n{e.Message}", - TimeSpan.FromSeconds(10)); - ErrorNavigateBack(); - return; - } - - if (IsICharacter) - { - var lastSelectedSkin = SelectableInGameSkins.FirstOrDefault(selectCharacterTemplate => - selectCharacterTemplate.InternalName.Equals( - _lastSelectedSkin.FirstOrDefault(kv => kv.Key == ShownCharacter).Value, - StringComparison.CurrentCultureIgnoreCase)); - - if (lastSelectedSkin is not null) await SwitchCharacterSkin(lastSelectedSkin); - } - - if (ModListVM.Mods.Count(mod => mod.IsEnabled) == 1) - { - var mod = ModListVM.Mods.FirstOrDefault(mod => mod.IsEnabled); - if (mod is not null) - ModListVM.SelectedMods.Add(mod); - } - } - - // This function is called from the ModModel _toggleMod delegate. - // This is a hacky way to get the toggle button to work. - private void ToggleMod(ModModel thisMod) - { - var modList = _skinManagerService.GetCharacterModList(thisMod.Character); - if (thisMod.IsEnabled) - modList.DisableMod(thisMod.Id); - else - modList.EnableMod(thisMod.Id); - - thisMod.IsEnabled = !thisMod.IsEnabled; - thisMod.FolderName = _modList.Mods.First(mod => mod.Id == thisMod.Id).Mod.Name; - _elevatorService.RefreshGenshinMods(); - } - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(GoToGalleryScreenCommand))] - private bool _isAddingModFolder = false; - - [RelayCommand] - private async Task AddModFolder() - { - var folderPicker = new FolderPicker(); - folderPicker.FileTypeFilter.Add("*"); - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow); - WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd); - - var folder = await folderPicker.PickSingleFolderAsync(); - if (folder is null) - { - _logger.Debug("User cancelled folder picker."); - return; - } - - try - { - IsAddingModFolder = true; - await Task.Run(async () => - { - await _modDragAndDropService.AddStorageItemFoldersAsync(_modList, - new ReadOnlyCollection(new List { folder })); - }); - } - finally - { - IsAddingModFolder = false; - } - } - - [RelayCommand] - private async Task AddModArchiveAsync() - { - var filePicker = new FileOpenPicker(); - filePicker.FileTypeFilter.Add(".zip"); - filePicker.FileTypeFilter.Add(".rar"); - filePicker.FileTypeFilter.Add(".7z"); - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow); - WinRT.Interop.InitializeWithWindow.Initialize(filePicker, hwnd); - var file = await filePicker.PickSingleFileAsync(); - if (file is null) - { - _logger.Debug("User cancelled file picker."); - return; - } - - try - { - IsAddingModFolder = true; - await Task.Run( - async () => await _modDragAndDropService.AddStorageItemFoldersAsync(_modList, new[] { file })); - } - catch (Exception e) - { - _logger.Error(e, "Error while adding archive."); - _notificationService.ShowNotification("Error while adding storage items.", - $"An error occurred while adding the storage items.\n{e.Message}", - TimeSpan.FromSeconds(5)); - } - finally - { - IsAddingModFolder = false; - } - } - - [RelayCommand] - private async Task RefreshMods() - { - var selectedModPaths = ModListVM.SelectedMods.Select(mod => mod.FolderName).ToArray(); - await _refreshMods(); - var selectedMods = ModListVM.Mods.Where(mod => selectedModPaths.Any(oldModPath => - ModFolderHelpers.FolderNameEquals(mod.FolderName, oldModPath))).ToArray(); - ModListVM.SelectionChanged(selectedMods, new List()); - } - - private async Task _refreshMods() - { - var refreshResult = await Task.Run(() => _skinManagerService.RefreshModsAsync(ShownCharacter.InternalName)); - var modList = new List(); - - - var mods = _gameService.IsMultiMod(_modList.Character) || !MultipleInGameSkins - ? _modList.Mods - : await FilterModsToSkin(_modList.Mods, SelectedInGameSkin!); - - foreach (var skinEntry in mods) - { - var newModModel = ModModel.FromMod(skinEntry); - newModModel.WithToggleModDelegate(ToggleMod); - - var modSettings = await LoadModSettings(skinEntry); - - if (modSettings != null) - newModModel.WithModSettings(modSettings); - - var notifications = await _modNotificationManager.GetNotificationsAsync(); - - var modNotifications = notifications.Where(x => - x.ModId == skinEntry.Id) - .ToArray(); - - modNotifications.ForEach(newModModel.ModNotifications.Add); - - - modList.Add(newModModel); - } - - - if (refreshResult.ModsDuplicate.Any()) - { - var message = $"Duplicate mods were detected in {ShownCharacter.DisplayName}'s mod folder.\n"; - - message = refreshResult.ModsDuplicate.Aggregate(message, - (current, duplicateMod) => - current + - $"Mod: '{duplicateMod.ExistingFolderName}' was renamed to '{duplicateMod.RenamedFolderName}' to avoid conflicts.\n"); - - _notificationService.ShowNotification("Duplicate Mods Detected", - message, - TimeSpan.FromSeconds(10)); - } - - - ModListVM.SetBackendMods(modList); - ModListVM.ResetContent(SortMethod); - } - - [RelayCommand] - private async Task SwitchCharacterSkin(SelectCharacterTemplate? characterTemplate) - { - if (characterTemplate is null) - return; - - if (ShownCharacter is not ICharacter character || SelectedInGameSkin is null) - { - return; - } - - if (characterTemplate.InternalName.Equals( - SelectedInGameSkin.InternalName, - StringComparison.OrdinalIgnoreCase)) - return; - - var characterSkin = character.Skins.FirstOrDefault(skin => - skin.InternalName.Equals(characterTemplate.InternalName)); - - - if (characterSkin is null) - { - _logger.Error("Could not find character skin {SkinName} for character {CharacterName}", - characterTemplate.DisplayName, ShownCharacter.DisplayName); - _notificationService.ShowNotification("Error while switching character skin.", "", TimeSpan.FromSeconds(5)); - return; - } - - SelectedInGameSkin = characterSkin; - ModdableObjectImage = SelectedInGameSkin.ImageUri ?? _imageHandlerService.PlaceholderImageUri; - - MoveModsFlyoutVM.SetActiveSkin(characterSkin); - - - foreach (var selectableInGameSkin in SelectableInGameSkins) - selectableInGameSkin.IsSelected = selectableInGameSkin.InternalName.Equals(characterTemplate.InternalName, - StringComparison.CurrentCultureIgnoreCase); - - _lastSelectedSkin[character] = SelectedInGameSkin.InternalName; - await RefreshMods().ConfigureAwait(false); - } - - private async Task> FilterModsToSkin( - IReadOnlyCollection mods, - ICharacterSkin skin) - { - if (ShownCharacter is not ICharacter character) - return mods.ToList(); - - var filteredMods = new List(); - - await foreach (var skinMod in _characterSkinService.FilterModsToSkinAsync(skin, mods.Select(ske => ske.Mod), - ignoreUndetectableMods: false).ConfigureAwait(false)) - { - filteredMods.Add(mods.First(mod => mod.Mod.Id == skinMod.Id)); - } - - - return filteredMods; - } - - private async Task LoadModSettings(CharacterSkinEntry characterSkinEntry) - { - var result = await _modSettingsService.GetSettingsAsync(characterSkinEntry.Mod.Id); - - - ModSettings? modSettings = null; - - modSettings = result.Match( - modSettings => modSettings, - notFound => null, - modNotFound => null, - error => null); - - - return modSettings; - } - - [RelayCommand] - private async Task DragAndDropAsync(IReadOnlyList? storageItems) - { - if (storageItems is null) - { - _logger.Warning("Drag and drop files called with null storage items."); - return; - } - - try - { - IsAddingModFolder = true; - await Task.Run(async () => await _modDragAndDropService.AddStorageItemFoldersAsync(_modList, storageItems)); - } - catch (Exception e) - { - _logger.Error(e, "Error while adding storage items."); - _notificationService.ShowNotification("Drag And Drop operation failed", - $"An error occurred while adding the storage items. Reason:\n{e.Message}", - TimeSpan.FromSeconds(5)); - } - finally - { - IsAddingModFolder = false; - } - } - - [RelayCommand] - private async Task OpenModsFolderAsync() - { - var directoryToOpen = new DirectoryInfo(_modList.AbsModsFolderPath); - if (!directoryToOpen.Exists) - { - _modList.InstantiateCharacterFolder(); - directoryToOpen.Refresh(); - - if (!directoryToOpen.Exists) - { - var parentDir = directoryToOpen.Parent; - - if (parentDir is null) - { - _logger.Error("Could not find parent directory of {Directory}", directoryToOpen.FullName); - return; - } - - directoryToOpen = parentDir; - } - } - - await Launcher.LaunchFolderAsync( - await StorageFolder.GetFolderFromPathAsync(directoryToOpen.FullName)); - } - - [RelayCommand] - private async Task OpenGIMIRootFolderAsync() - { - var options = await _localSettingsService.ReadSettingAsync(ModManagerOptions.Section) ?? - new ModManagerOptions(); - if (string.IsNullOrWhiteSpace(options.GimiRootFolderPath)) return; - await Launcher.LaunchFolderAsync( - await StorageFolder.GetFolderFromPathAsync(options.GimiRootFolderPath)); - } - - [RelayCommand] - private async Task DisableAllMods() - { - if (ModListVM.Mods.Count == 0) return; - var shownMods = ModListVM.Mods.Where(mod => mod.IsEnabled).ToArray(); - await Task.Run(() => - { - foreach (var modEntry in shownMods) - _modList.DisableMod(modEntry.Id); - }); - await RefreshMods().ConfigureAwait(false); - } - - public void ChangeModDetails(ModModel modModel) - { - var oldMod = _modList.Mods.FirstOrDefault(mod => mod.Id == modModel.Id); - if (oldMod == null) - { - _logger.Warning("Could not find mod with id {ModId} to change details.", modModel.Id); - return; - } - - var oldModModel = ModModel.FromMod(oldMod); - - - if (oldModModel.Name != modModel.Name) - NotImplemented.Show("Setting custom mod names are not persisted between sessions", - TimeSpan.FromSeconds(10)); - } - - public async Task ModList_KeyHandler(IEnumerable modEntryId, VirtualKey key) - { - var selectedMods = ModListVM.Mods.Where(mod => modEntryId.Contains(mod.Id)).ToArray(); - - if (key == VirtualKey.Space) - foreach (var newModModel in selectedMods) - await newModModel.ToggleModCommand.ExecuteAsync(newModModel); - } - - - private IEnumerable GetNewModModels() - { - return _modList.Mods.Select(mod => ModModel.FromMod(mod).WithToggleModDelegate(ToggleMod)); - } - - - [RelayCommand] - private async Task OpenNewModsWindowAsync(object? modNotification) - { - if (modNotification is not ModNotification notification) - { - _logger.Warning("OpenNewModsWindowAsync called with null modModel."); - return; - } - - var skinEntry = _modList.Mods.FirstOrDefault(mod => mod.Id == notification.ModId); - - if (skinEntry is null) - { - return; - } - - var existingWindow = _windowManagerService.GetWindow(notification.Id); - if (existingWindow is not null) - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(async () => -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - { - await Task.Delay(100); - existingWindow.BringToFront(); - }); - return; - } - - - var modWindow = new ModUpdateAvailableWindow(notification.Id) - { - Title = - $"New Mod Files Available: {ModFolderHelpers.GetFolderNameWithoutDisabledPrefix(skinEntry.Mod.Name)}" - }; - _windowManagerService.CreateWindow(modWindow, identifier: notification.Id); - await Task.Delay(100); - modWindow.BringToFront(); - } - - - [RelayCommand] - private void GoBackToGrid() - { - var gridLastStack = _navigationService.GetBackStackItems().LastOrDefault(); - - if (gridLastStack is not null && gridLastStack.SourcePageType == typeof(CharactersPage)) - { - _navigationService.GoBack(); - return; - } - - _navigationService.NavigateTo(typeof(CharactersViewModel).FullName!, _category); - } - - [RelayCommand] - private void GoToModsOverview() - { - _navigationService.NavigateTo(typeof(ModsOverviewVM).FullName!, ShownCharacter.InternalName); - } - - [RelayCommand] - private void GoToCharacterEditScreen() - { - _navigationService.NavigateTo(typeof(CharacterManagerViewModel).FullName!, ShownCharacter.InternalName); - } - - private bool CanGoToGalleryScreen() - { - return !IsAddingModFolder; - } - - [RelayCommand(CanExecute = nameof(CanGoToGalleryScreen))] - private async Task GoToGalleryScreen() - { - var settings = await _localSettingsService.ReadOrCreateSettingAsync( - CharacterDetailsSettings.Key, SettingScope.App); - - settings.GalleryView = true; - - await _localSettingsService.SaveSettingAsync(CharacterDetailsSettings.Key, settings, SettingScope.App); - - _navigationService.NavigateTo(typeof(CharacterGalleryViewModel).FullName!, ShownCharacter.InternalName); - _navigationService.ClearBackStack(1); - } - - public void OnNavigatedFrom() - { - if (_modList is not null) - _modList.ModsChanged -= ModListOnModsChanged; - if (_modNotificationManager is not null) - _modNotificationManager.OnModNotification -= OnOnModNotificationHandler; - if (_modDragAndDropService is not null) - _modDragAndDropService.DragAndDropFinished -= OnDragAndDropFinished; - } - - - private void ErrorNavigateBack() - { - Task.Run(async () => - { - await Task.Delay(500); - App.MainWindow.DispatcherQueue.TryEnqueue(() => - { - if (_navigationService.CanGoBack) - _navigationService.GoBack(); - else - _navigationService.NavigateTo(typeof(CharactersViewModel).FullName!); - }); - }); - } - - - public Task AddNewModAddedNotificationAsync(AttentionType attentionType, string newModFolderName, string? message) - { - var inMemoryModNotification = new ModNotification() - { - CharacterInternalName = ShownCharacter.InternalName, - ShowOnOverview = false, - ModFolderName = newModFolderName, - AttentionType = attentionType, - Message = message ?? string.Empty - }; - - return _modNotificationManager.AddModNotification(inMemoryModNotification); - } -} \ No newline at end of file diff --git a/src/GIMI-ModManager.WinUI/ViewModels/CharactersViewModel.cs b/src/GIMI-ModManager.WinUI/ViewModels/CharactersViewModel.cs index ac87b032..f9e7642e 100644 --- a/src/GIMI-ModManager.WinUI/ViewModels/CharactersViewModel.cs +++ b/src/GIMI-ModManager.WinUI/ViewModels/CharactersViewModel.cs @@ -1,6 +1,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using Windows.Storage; +using Windows.System; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkitWrapper; @@ -13,14 +14,16 @@ using GIMI_ModManager.Core.Services.GameBanana; using GIMI_ModManager.WinUI.Contracts.Services; using GIMI_ModManager.WinUI.Contracts.ViewModels; +using GIMI_ModManager.WinUI.Helpers; using GIMI_ModManager.WinUI.Models; using GIMI_ModManager.WinUI.Models.Settings; using GIMI_ModManager.WinUI.Models.ViewModels; using GIMI_ModManager.WinUI.Services; using GIMI_ModManager.WinUI.Services.ModHandling; using GIMI_ModManager.WinUI.Services.Notifications; +using GIMI_ModManager.WinUI.ViewModels.CharacterDetailsViewModels; using GIMI_ModManager.WinUI.ViewModels.SubVms; -using GIMI_ModManager.WinUI.Views; +using GIMI_ModManager.WinUI.Views.CharacterDetailsPages; using Serilog; namespace GIMI_ModManager.WinUI.ViewModels; @@ -66,10 +69,9 @@ public partial class CharactersViewModel : ObservableRecipient, INavigationAware private readonly Dictionary _filters = new(); - public ObservableCollection SortingMethods { get; } = - new() { }; + public ObservableCollection SortingMethods { get; } = new(); - [ObservableProperty] private SortingMethod _selectedSortingMethod; + [ObservableProperty] private GridItemSortingMethod _selectedSortingMethod; [ObservableProperty] private bool _sortByDescending; [ObservableProperty] private bool _canCheckForUpdates = false; @@ -388,37 +390,19 @@ public async void OnNavigatedTo(object parameter) _backendCharacters = backendCharacters; - var characterTasks = new Dictionary>>(); foreach (var characterGridItemModel in _backendCharacters) { var modList = _skinManagerService.GetCharacterModList(characterGridItemModel.Character); - var task = Task.Run(async () => + var characterModItems = new List(); + foreach (var skinModEntry in modList.Mods) { - var characterModItems = new List(); + var modSettings = await skinModEntry.Mod.Settings.TryReadSettingsAsync(true); - var modEntries = modList.Mods.ToArray(); - - foreach (var skinModEntry in modEntries) - { - var modSettings = await skinModEntry.Mod.Settings.TryReadSettingsAsync(true).ConfigureAwait(false); - - characterModItems.Add(new CharacterModItem(skinModEntry.Mod.GetDisplayName(), skinModEntry.IsEnabled, modSettings?.DateAdded ?? default)); - } - - return characterModItems; - }); - characterTasks.Add(characterGridItemModel, task); - } - - while (characterTasks.Any()) - { - var completedTask = await Task.WhenAny(characterTasks.Values); - var (characterGridItemModel, _) = characterTasks.First(x => x.Value == completedTask); - - characterTasks.Remove(characterGridItemModel); + characterModItems.Add(new CharacterModItem(skinModEntry.Mod.GetDisplayName(), skinModEntry.IsEnabled, modSettings?.DateAdded ?? default)); + } - characterGridItemModel.SetMods(await completedTask); + characterGridItemModel.SetMods(characterModItems); } @@ -433,7 +417,7 @@ public async void OnNavigatedTo(object parameter) .Count(); if (distinctReleaseDates == 1 && - SortingMethods.FirstOrDefault(x => x.SortingMethodType == Sorter.ReleaseDateSortName) is + SortingMethods.FirstOrDefault(x => x.SortingMethodType == GridItemSorter.ReleaseDateSortName) is { } releaseDateSortingMethod) { SortingMethods.Remove(releaseDateSortingMethod); @@ -492,21 +476,26 @@ await _localSettingsService if (lastPageType?.PageType == typeof(CharacterDetailsPage) || lastPageType?.PageType == typeof(CharacterDetailsViewModel)) { - IModdableObject? moddableObject = null; + InternalName? internalName = null; if (lastPageType.Parameter is CharacterGridItemModel characterGridModel) { - moddableObject = characterGridModel.Character; + internalName = characterGridModel.Character.InternalName; } - else if (lastPageType.Parameter is IModdableObject modObject) + else if (lastPageType.Parameter is INameable modObject) { - moddableObject = modObject; + internalName = modObject.InternalName; } + else if (lastPageType.Parameter is string modObjectString) - if (moddableObject is null) + { + internalName = new InternalName(modObjectString); + } + + if (internalName is null) return; - var characterGridItemModel = FindCharacterByInternalName(moddableObject.InternalName); + var characterGridItemModel = FindCharacterByInternalName(internalName); if (characterGridItemModel is not null) { OnScrollToCharacter?.Invoke(this, new ScrollToCharacterArgs(characterGridItemModel)); @@ -519,7 +508,7 @@ private async Task RefreshMultipleModsWarningAsync() var charactersWithMultipleMods = _skinManagerService.CharacterModLists .Where(x => x.Mods.Count(mod => mod.IsEnabled) > 1); - var charactersWithMultipleActiveSkins = new List(); + var charactersWithMultipleActiveSkins = new HashSet(); await Task.Run(async () => { foreach (var modList in charactersWithMultipleMods) @@ -576,13 +565,19 @@ await Task.Run(async () => }); - foreach (var characterGridItemModel in _backendCharacters.Where(x => - charactersWithMultipleActiveSkins.Contains(x.Character.InternalName))) + foreach (var characterGridItemModel in _backendCharacters) { - if (_gameService.IsMultiMod(characterGridItemModel.Character)) - continue; + if (charactersWithMultipleActiveSkins.Contains(characterGridItemModel.Character.InternalName)) + { + if (_gameService.IsMultiMod(characterGridItemModel.Character)) + continue; - characterGridItemModel.Warning = true; + characterGridItemModel.Warning = true; + } + else + { + characterGridItemModel.Warning = false; + } } } @@ -754,7 +749,8 @@ private async Task ShowOnlyCharactersWithEnabledMods() public void OnRightClickContext(CharacterGridItemModel clickedCharacter) { - ClearNotificationsCommand.CanExecute(clickedCharacter); + ClearNotificationsCommand.NotifyCanExecuteChanged(); + DisableCharacterModsCommand.NotifyCanExecuteChanged(); if (clickedCharacter.IsPinned) { PinText = DefaultUnpinText; @@ -804,18 +800,87 @@ private async Task PinCharacterAsync(CharacterGridItemModel character) } - private bool canClearNotifications(CharacterGridItemModel? character) + private bool CanClearNotifications(CharacterGridItemModel? character) { return character?.Notification ?? false; } - [RelayCommand(CanExecute = nameof(canClearNotifications))] + [RelayCommand(CanExecute = nameof(CanClearNotifications))] private async Task ClearNotificationsAsync(CharacterGridItemModel character) { await _modNotificationManager.ClearModNotificationsAsync(character.Character.InternalName); await RefreshNotificationsAsync().ConfigureAwait(false); } + private bool CanDisableCharacterMods(CharacterGridItemModel? character) => + character is not null && character.Mods.Any(m => m.IsEnabled); + + [RelayCommand(CanExecute = nameof(CanDisableCharacterMods))] + private async Task DisableCharacterMods(CharacterGridItemModel character) + { + var updatedMods = new List(); + try + { + await Task.Run(async () => + { + var modList = _skinManagerService.GetCharacterModList(character.Character); + var mods = modList.Mods.ToArray(); + foreach (var modEntry in mods) + { + if (modList.IsModEnabled(modEntry.Mod)) + { + modList.DisableMod(modEntry.Id); + } + + var modSettings = await modEntry.Mod.Settings.TryReadSettingsAsync(true).ConfigureAwait(false); + + updatedMods.Add(new CharacterModItem(modEntry.Mod.GetDisplayName(), modEntry.IsEnabled, modSettings?.DateAdded ?? default)); + } + }); + } + catch (Exception e) + { + _logger.Error(e, "Error disabling mods for character {Character}", character.Character.InternalName); + NotificationManager.ShowNotification("Error disabling mods", e.Message, TimeSpan.FromSeconds(6)); + return; + } + + character.SetMods(updatedMods); + await RefreshMultipleModsWarningAsync(); + NotificationManager.ShowNotification("Mods Disabled", $"Alls mods for {character.Character.DisplayName} have been disabled", null); + } + + [RelayCommand] + private async Task OpenCharacterFolderAsync(CharacterGridItemModel? character) + { + if (character is null) + return; + var modList = _skinManagerService.GetCharacterModList(character.Character); + + var directoryToOpen = new DirectoryInfo(modList.AbsModsFolderPath); + if (!directoryToOpen.Exists) + { + modList.InstantiateCharacterFolder(); + directoryToOpen.Refresh(); + + if (!directoryToOpen.Exists) + { + var parentDir = directoryToOpen.Parent; + + if (parentDir is null) + { + _logger.Error("Could not find parent directory of {Directory}", directoryToOpen.FullName); + return; + } + + directoryToOpen = parentDir; + } + } + + await Launcher.LaunchFolderAsync( + await StorageFolder.GetFolderFromPathAsync(directoryToOpen.FullName)); + } + [RelayCommand] private void HideCharacter(CharacterGridItemModel character) { @@ -937,7 +1002,7 @@ public async Task ModUrlDroppedOnCharacterAsync(CharacterGridItemModel character [RelayCommand] - private async Task SortBy(IEnumerable methodTypes) + private async Task SortBy(IEnumerable methodTypes) { if (_isNavigating) return; var sortingMethodType = methodTypes.First(); @@ -990,25 +1055,25 @@ private void InitializeSorters() var othersCharacter = _backendCharacters.FirstOrDefault(ch => ch.Character.InternalName.Id.Contains("Others", StringComparison.OrdinalIgnoreCase)); - var alphabetical = new SortingMethod(Sorter.Alphabetical(), othersCharacter, lastCharacters); + var alphabetical = new GridItemSortingMethod(GridItemSorter.Alphabetical, othersCharacter, lastCharacters); SortingMethods.Add(alphabetical); - var byModCount = new SortingMethod(Sorter.ModCount(), othersCharacter, lastCharacters); + var byModCount = new GridItemSortingMethod(GridItemSorter.ModCount, othersCharacter, lastCharacters); SortingMethods.Add(byModCount); - var byModRecentlyAdded = new SortingMethod(Sorter.ModRecentlyAdded(), othersCharacter, lastCharacters); + var byModRecentlyAdded = new GridItemSortingMethod(GridItemSorter.ModRecentlyAdded, othersCharacter, lastCharacters); SortingMethods.Add(byModRecentlyAdded); if (_category.ModCategory == ModCategory.Character) { - SortingMethods.Add(new SortingMethod(Sorter.ReleaseDate(), othersCharacter, lastCharacters)); - SortingMethods.Add(new SortingMethod(Sorter.Rarity(), othersCharacter, lastCharacters)); + SortingMethods.Add(new GridItemSortingMethod(GridItemSorter.ReleaseDate, othersCharacter, lastCharacters)); + SortingMethods.Add(new GridItemSortingMethod(GridItemSorter.Rarity, othersCharacter, lastCharacters)); } if (_category.ModCategory == ModCategory.Weapons) { - SortingMethods.Add(new SortingMethod(Sorter.Rarity(), othersCharacter, lastCharacters)); + SortingMethods.Add(new GridItemSortingMethod(GridItemSorter.Rarity, othersCharacter, lastCharacters)); } } @@ -1040,6 +1105,103 @@ private void OnBusyChangedHandler(object? sender, BusyChangedEventArgs args) IsBusy = args.IsBusy; }); } + + public sealed class GridItemSortingMethod( + Sorter sortingMethodType, + CharacterGridItemModel? firstItem = null, + ICollection? lastItems = null) + : SortingMethod(sortingMethodType, firstItem, lastItems); + + public sealed class GridItemSorter : Sorter + { + private GridItemSorter(string sortingMethodType, SortFunc firstSortFunc, AdditionalSortFunc? secondSortFunc = null, + AdditionalSortFunc? thirdSortFunc = null) : base(sortingMethodType, firstSortFunc, secondSortFunc, thirdSortFunc) + { + } + + public const string AlphabeticalSortName = "Alphabetical"; + + public static GridItemSorter Alphabetical { get; } = + new( + AlphabeticalSortName, + (characters, isDescending) => + isDescending + ? characters.OrderByDescending(x => x.Character.DisplayName) + : characters.OrderBy(x => x.Character.DisplayName + )); + + + public const string ReleaseDateSortName = "Release Date"; + + public static GridItemSorter ReleaseDate { get; } = + new( + ReleaseDateSortName, + (characters, isDescending) => + !isDescending + ? characters.OrderByDescending(x => ((ICharacter)x.Character).ReleaseDate) + : characters.OrderBy(x => ((ICharacter)x.Character).ReleaseDate), + (characters, _) => + characters.ThenBy(x => x.Character.DisplayName + )); + + + public const string RaritySortName = "Rarity"; + + public static GridItemSorter Rarity { get; } = + new( + RaritySortName, + (characters, isDescending) => + !isDescending + ? characters.OrderByDescending(x => ((IRarity)x.Character).Rarity) + : characters.OrderBy(x => ((IRarity)x.Character).Rarity), + (characters, _) => + characters.ThenBy(x => x.Character.DisplayName + )); + + + public const string ModCountSortName = "Mod Count"; + + public static GridItemSorter ModCount { get; } = + new( + ModCountSortName, + (characters, isDescending) => + !isDescending + ? characters.OrderByDescending(x => x.ModCount) + : characters.OrderBy(x => x.ModCount), + (characters, _) => + characters.ThenBy(x => x.Character.DisplayName + )); + + + public const string ModRecentlyAddedName = "Recently Added Mods"; + + public static GridItemSorter ModRecentlyAdded { get; } = + new( + ModRecentlyAddedName, + (characters, isDescending) => + !isDescending + ? characters.OrderByDescending(x => + { + var validDates = x.Mods.Where(mod => mod.DateAdded != default).Select(mod => mod.DateAdded) + .ToArray(); + if (validDates.Any()) + return validDates.Max(); + else + return DateTime.MinValue; + }) + : characters.OrderBy(x => + { + var validDates = x.Mods.Where(mod => mod.DateAdded != default).Select(mod => mod.DateAdded) + .ToArray(); + if (validDates.Any()) + return validDates.Min(); + else + return DateTime.MaxValue; + }), + (characters, _) => + characters.ThenBy(x => x.Character.DisplayName + )); + } } public sealed class GridFilter @@ -1071,209 +1233,6 @@ public enum FilterType HasEnabledMods } -public sealed class SortingMethod -{ - public string SortingMethodType => _sorter.SortingMethodType; - - private readonly Sorter _sorter; - - private readonly CharacterGridItemModel[] _lastCharacters; - private readonly CharacterGridItemModel? _firstCharacter; - - public SortingMethod(Sorter sortingMethodType, CharacterGridItemModel? firstCharacter = null, - ICollection? lastCharacters = null) - { - _sorter = sortingMethodType; - _lastCharacters = lastCharacters?.ToArray() ?? Array.Empty(); - _firstCharacter = firstCharacter; - } - - public IEnumerable Sort(IEnumerable characters, bool isDescending) - { - IEnumerable sortedCharacters = null!; - - sortedCharacters = _sorter.Sort(characters, isDescending).Cast(); - - var returnCharactersList = sortedCharacters.ToList(); - - var modifiableCharacters = new List(returnCharactersList); - - var index = 0; - foreach (var pinnedCharacter in modifiableCharacters.Where(x => x.IsPinned)) - { - returnCharactersList.Remove(pinnedCharacter); - returnCharactersList.Insert(index, pinnedCharacter); - index++; - } - - foreach (var characterGridItemModel in modifiableCharacters.Intersect(_lastCharacters)) - { - if (characterGridItemModel.IsPinned) continue; - returnCharactersList.Remove(characterGridItemModel); - returnCharactersList.Add(characterGridItemModel); - } - - if (_firstCharacter is not null) - { - returnCharactersList.Remove(_firstCharacter); - returnCharactersList.Insert(0, _firstCharacter); - } - - return returnCharactersList; - } - - public override string ToString() - { - return SortingMethodType; - } -} - -// I originally tried to do this with type support, but it turned into quite a 'generic' mess -// So I decided to go with a more 'hardcoded' approach and casting values when doing the comparison -public sealed class Sorter -{ - public string SortingMethodType { get; } - private readonly SortFunc _firstSortFunc; - - private readonly AdditionalSortFunc? _secondSortFunc; - - private readonly AdditionalSortFunc? _thirdSortFunc; - - - private delegate IOrderedEnumerable SortFunc(IEnumerable characters, - bool isDescending); - - private delegate IOrderedEnumerable AdditionalSortFunc( - IOrderedEnumerable characters, - bool isDescending); - - private Sorter(string sortingMethodType, SortFunc firstSortFunc, AdditionalSortFunc? secondSortFunc = null, - AdditionalSortFunc? thirdSortFunc = null) - { - SortingMethodType = sortingMethodType; - _firstSortFunc = firstSortFunc; - _secondSortFunc = secondSortFunc; - _thirdSortFunc = thirdSortFunc; - } - - public IEnumerable Sort(IEnumerable characters, bool isDescending) - { - var sorted = _firstSortFunc(characters, isDescending); - - if (_secondSortFunc is not null) - sorted = _secondSortFunc(sorted, isDescending); - - if (_thirdSortFunc is not null) - sorted = _thirdSortFunc(sorted, isDescending); - - return sorted; - } - - - public const string AlphabeticalSortName = "Alphabetical"; - - // TODO: These can be a static property - public static Sorter Alphabetical() - { - return new Sorter - ( - AlphabeticalSortName, - (characters, isDescending) => - isDescending - ? characters.OrderByDescending(x => (x.Character.DisplayName)) - : characters.OrderBy(x => (x.Character.DisplayName) - )); - } - - - public const string ReleaseDateSortName = "Release Date"; - - // TODO: IDateSupport interface - public static Sorter ReleaseDate() - { - return new Sorter - ( - ReleaseDateSortName, - (characters, isDescending) => - !isDescending - ? characters.OrderByDescending(x => ((ICharacter)x.Character).ReleaseDate) - : characters.OrderBy(x => ((ICharacter)x.Character).ReleaseDate), - (characters, _) => - characters.ThenBy(x => (x.Character.DisplayName) - )); - } - - public const string RaritySortName = "Rarity"; - - public static Sorter Rarity() - { - return new Sorter - ( - RaritySortName, - (characters, isDescending) => - !isDescending - ? characters.OrderByDescending(x => ((IRarity)x.Character).Rarity) - : characters.OrderBy(x => ((IRarity)x.Character).Rarity), - (characters, _) => - characters.ThenBy(x => (x.Character.DisplayName) - )); - } - - public const string ModCountSortName = "Mod Count"; - - public static Sorter ModCount() - { - return new Sorter - ( - ModCountSortName, - (characters, isDescending) => - !isDescending - ? characters.OrderByDescending(x => (x.ModCount)) - : characters.OrderBy(x => (x.ModCount)), - (characters, _) => - characters.ThenBy(x => (x.Character.DisplayName) - )); - } - - - public const string ModRecentlyAddedName = "Recently Added Mods"; - - public static Sorter ModRecentlyAdded() - { - return new Sorter - ( - ModRecentlyAddedName, - (characters, isDescending) => - !isDescending - ? characters.OrderByDescending(x => - { - var validDates = x.Mods.Where(mod => mod.DateAdded != default).Select(mod => mod.DateAdded) - .ToArray(); - if (validDates.Any()) - return validDates.Max(); - else - return DateTime.MinValue; - }) - : characters.OrderBy(x => - { - var validDates = x.Mods.Where(mod => mod.DateAdded != default).Select(mod => mod.DateAdded) - .ToArray(); - if (validDates.Any()) - return validDates.Min(); - else - return DateTime.MaxValue; - }), - (characters, _) => - characters.ThenBy(x => (x.Character.DisplayName) - )); - } - - - //sortedCharacters = Sort(characters, x => x.Character.Rarity, !IsDescending, - //sortSecondBy: x => x.Character.ReleaseDate, !IsDescending, - //sortThirdBy: x => x.Character.DisplayName); -} - public class ScrollToCharacterArgs : EventArgs { public CharacterGridItemModel Character { get; } diff --git a/src/GIMI-ModManager.WinUI/ViewModels/PresetDetailsViewModel.cs b/src/GIMI-ModManager.WinUI/ViewModels/PresetDetailsViewModel.cs index 33fd7734..2a4a1ab3 100644 --- a/src/GIMI-ModManager.WinUI/ViewModels/PresetDetailsViewModel.cs +++ b/src/GIMI-ModManager.WinUI/ViewModels/PresetDetailsViewModel.cs @@ -12,6 +12,7 @@ using GIMI_ModManager.WinUI.Services; using GIMI_ModManager.WinUI.Services.AppManagement; using GIMI_ModManager.WinUI.Services.Notifications; +using GIMI_ModManager.WinUI.ViewModels.CharacterDetailsViewModels; using GIMI_ModManager.WinUI.Views; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; diff --git a/src/GIMI-ModManager.WinUI/ViewModels/SettingsViewModel.cs b/src/GIMI-ModManager.WinUI/ViewModels/SettingsViewModel.cs index 61858edb..63f482d1 100644 --- a/src/GIMI-ModManager.WinUI/ViewModels/SettingsViewModel.cs +++ b/src/GIMI-ModManager.WinUI/ViewModels/SettingsViewModel.cs @@ -99,6 +99,8 @@ public partial class SettingsViewModel : ObservableRecipient, INavigationAware [ObservableProperty] private int _maxCacheLimit; + [ObservableProperty] private Uri _archiveCacheFolderPath; + [ObservableProperty] private bool _persistWindowSize = false; [ObservableProperty] private bool _persistWindowPosition = false; @@ -161,6 +163,7 @@ public SettingsViewModel( CanIgnoreUpdate = true; } + ArchiveCacheFolderPath = _modArchiveRepository.ArchiveDirectory; _modManagerOptions = localSettingsService.ReadSetting(ModManagerOptions.Section); PathToGIMIFolderPicker = new PathPicker(); @@ -790,7 +793,6 @@ await _localSettingsService.ReadOrCreateSettingAsync BackendMods = new(); - - public ObservableCollection SelectedMods { get; } = new(); - [ObservableProperty] private InfoBarSeverity _severity = InfoBarSeverity.Warning; - - [ObservableProperty] private bool _isInfoBarOpen; - - [ObservableProperty] private string _infoBarMessage = string.Empty; - - [ObservableProperty] private int _selectedModsCount; - - public ObservableCollection Mods { get; } = new(); - - public bool DisableInfoBar { get; set; } = false; - - public ModListVM(ISkinManagerService skinManagerService, ModNotificationManager modNotificationManager) - { - _skinManagerService = skinManagerService; - _modNotificationManager = modNotificationManager; - Mods.CollectionChanged += Mods_CollectionChanged; - } - - - private void Mods_CollectionChanged(object? sender, - System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - if (e.NewItems is not null) - { - foreach (ModModel item in e.NewItems) - { - item.PropertyChanged += (o, args) => - { - if (args.PropertyName != nameof(ModModel.IsEnabled)) return; - - if (Mods.Count(x => x.IsEnabled) > 1) - { - SetInfoBarMessage("More than one skin mod enabled", InfoBarSeverity.Warning); - } - else - { - ResetInfoBar(); - } - }; - } - } - } - - public void SetBackendMods(IEnumerable mods) - { - BackendMods.Clear(); - foreach (var mod in mods) - { - BackendMods.Add(mod); - } - } - - public void ReplaceMods(IEnumerable mods) - { - Mods.Clear(); - foreach (var mod in mods) - { - Mods.Add(mod); - } - } - - - public class SortMethod - { - public SortMethod(string propertyName, bool isDescending = false) - { - PropertyName = propertyName; - IsDescending = isDescending; - } - - public bool IsDescending { get; } - public string PropertyName { get; } - } - - public void ResetContent(SortMethod? sortMethod = null) - { - Mods.Clear(); - var isEnabledComparer = new ModEnabledComparer(); - - if (sortMethod is not null) - { - isEnabledComparer.IsDescending = sortMethod.IsDescending; - - void AddMods(IEnumerable mods) - { - foreach (var mod in mods) - { - Mods.Add(mod); - } - } - - switch (sortMethod.PropertyName) - { - case nameof(ModModel.IsEnabled): - AddMods(sortMethod.IsDescending - ? BackendMods.OrderByDescending(modModel => modModel, isEnabledComparer) - : BackendMods.OrderBy(modModel => modModel, isEnabledComparer)); - - break; - - case nameof(ModModel.Name): - AddMods(sortMethod.IsDescending - ? BackendMods.OrderByDescending(modModel => modModel.Name) - : BackendMods.OrderBy(modModel => modModel.Name)); - - break; - - case nameof(ModModel.FolderName): - AddMods(sortMethod.IsDescending - ? BackendMods.OrderByDescending(modModel => modModel.FolderName) - : BackendMods.OrderBy(modModel => modModel.FolderName)); - break; - - case nameof(ModModel.Author): - AddMods(sortMethod.IsDescending - ? BackendMods.OrderBy(m => string.IsNullOrWhiteSpace(m.Author)) - .ThenByDescending(modModel => modModel.Author) - : BackendMods.OrderBy(m => string.IsNullOrWhiteSpace(m.Author)) - .ThenBy(modModel => modModel.Author)); - break; - - case nameof(ModModel.DateAdded): - AddMods(sortMethod.IsDescending - ? BackendMods.OrderByDescending(m => m.DateAdded) - : BackendMods.OrderBy(m => m.DateAdded)); - break; - - default: - Debug.Assert(false, "Unknown sort method"); - AddMods(BackendMods.OrderBy(modModel => modModel.Name)); - break; - } - } - else - { - foreach (var mod in BackendMods.OrderBy(newModModel => newModModel.Name)) - { - Mods.Add(mod); - } - } - - - if (Mods.Count(x => x.IsEnabled) > 1) - SetInfoBarMessage("More than one skin enabled", InfoBarSeverity.Warning); - else - ResetInfoBar(); - } - - public void SelectionChanged(ICollection selectedMods, ICollection removedMods) - { - if (selectedMods.Any()) - { - foreach (var mod in selectedMods) - { - if (!SelectedMods.Contains(mod)) - SelectedMods.Add(mod); - } - } - - if (removedMods.Any()) - { - foreach (var mod in removedMods) - { - if (SelectedMods.Contains(mod)) - SelectedMods.Remove(mod); - } - } - - SelectedModsCount = SelectedMods.Count; - OnModsSelected?.Invoke(this, new ModSelectedEventArgs(SelectedMods)); - } - - public event EventHandler? OnModsSelected; - - public class ModSelectedEventArgs : EventArgs - { - public ModSelectedEventArgs(IEnumerable mods) - { - Mods = mods.ToArray(); - } - - public ICollection Mods { get; } - } - - - public void SetInfoBarMessage(string message, InfoBarSeverity severity, bool openInfoBar = true) - { - if (DisableInfoBar) return; - InfoBarMessage = message; - Severity = severity; - IsInfoBarOpen = openInfoBar; - } - - public void ResetInfoBar() - { - InfoBarMessage = string.Empty; - Severity = InfoBarSeverity.Informational; - IsInfoBarOpen = false; - } -} - -public sealed class ModEnabledComparer : IComparer -{ - public bool IsDescending; - - - public int Compare(ModModel? x, ModModel? y) - { - if (x is null || y is null) return 0; - if (x.IsEnabled == y.IsEnabled) - { - var nameComparison = x.Name.CompareTo(y.Name); - if (IsDescending && nameComparison != 0) - return -nameComparison; - - return nameComparison; - } - - return x.IsEnabled.CompareTo(y.IsEnabled); - } -} \ No newline at end of file diff --git a/src/GIMI-ModManager.WinUI/ViewModels/SubVms/ModPaneVM.cs b/src/GIMI-ModManager.WinUI/ViewModels/SubVms/ModPaneVM.cs deleted file mode 100644 index 3ba28283..00000000 --- a/src/GIMI-ModManager.WinUI/ViewModels/SubVms/ModPaneVM.cs +++ /dev/null @@ -1,397 +0,0 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using Windows.Storage; -using Windows.Storage.Pickers; -using Windows.Storage.Streams; -using Windows.System; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using GIMI_ModManager.Core.Contracts.Entities; -using GIMI_ModManager.Core.Contracts.Services; -using GIMI_ModManager.Core.Entities.Mods.Contract; -using GIMI_ModManager.WinUI.Models; -using GIMI_ModManager.WinUI.Services.ModHandling; -using GIMI_ModManager.WinUI.Services.Notifications; -using Serilog; - -namespace GIMI_ModManager.WinUI.ViewModels.SubVms; - -public partial class ModPaneVM : ObservableRecipient -{ - private readonly ISkinManagerService _skinManagerService = App.GetService(); - private readonly NotificationManager _notificationManager = App.GetService(); - private readonly ILogger _logger = Log.ForContext(); - - private readonly KeySwapService _keySwapService = App.GetService(); - private readonly ModSettingsService _modSettingsService = App.GetService(); - - private ISkinMod? _selectedSkinMod; - - private ModModel? _backendModModel; - - [ObservableProperty] private ModModel? _selectedModModel; - [ObservableProperty] private bool _isReadOnlyMode = true; - - [ObservableProperty] private bool _isEditingModName = false; - - [ObservableProperty] private string _showKeySwapSection = "true"; - - - public async Task LoadMod(ModModel modModel, CancellationToken cancellationToken = default) - { - if (modModel.Id == SelectedModModel?.Id) return; - - var mod = await Task.Run(() => _skinManagerService.GetModById(modModel.Id), cancellationToken); - if (mod == null) - { - return; - } - - UnloadMod(); - _selectedSkinMod = mod; - _backendModModel = ModModel.FromMod(mod, modModel.Character, modModel.IsEnabled); - SelectedModModel = modModel; - SelectedModModel.PropertyChanged += (_, _) => SettingsPropertiesChanged(); - await ReloadModSettings(cancellationToken).ConfigureAwait(false); - } - - private async Task ReloadModSettings(CancellationToken cancellationToken = default) - { - if (!IsLoaded()) return; - - var readSettingsResult = - await Task.Run(() => _modSettingsService.GetSettingsAsync(_backendModModel?.Id ?? Guid.Empty), - cancellationToken); - - - if (!readSettingsResult.TryPickT0(out var modSettings, out _)) - { - UnloadMod(); - return; - } - - if (_selectedSkinMod is null || _backendModModel is null || SelectedModModel is null) return; - SelectedModModel?.WithModSettings(modSettings); - _backendModModel?.WithModSettings(modSettings); - - - Debug.Assert(_backendModModel is not null && _backendModModel.Equals(SelectedModModel)); - - ShowKeySwapSection = modSettings.IgnoreMergedIni ? "false" : "true"; - - - if (!_selectedSkinMod.Settings.HasMergedIni) - { - IsReadOnlyMode = false; - SelectedModModel?.SetKeySwaps(Array.Empty()); - _backendModModel?.SetKeySwaps(Array.Empty()); - return; - } - - var readKeySwapResult = - await Task.Run(() => _keySwapService.GetKeySwapsAsync(_backendModModel.Id), cancellationToken); - - if (!readKeySwapResult.TryPickT0(out var keySwaps, out _)) - { - IsReadOnlyMode = false; - SelectedModModel?.SetKeySwaps(Array.Empty()); - _backendModModel?.SetKeySwaps(Array.Empty()); - return; - } - - SelectedModModel?.SetKeySwaps(keySwaps); - _backendModModel?.SetKeySwaps(keySwaps); - - - foreach (var skinModKeySwapModel in SelectedModModel?.SkinModKeySwaps ?? []) - skinModKeySwapModel.PropertyChanged += (_, _) => SettingsPropertiesChanged(); - - IsReadOnlyMode = false; - - Debug.Assert(_backendModModel is not null && _backendModModel.Equals(SelectedModModel)); - } - - public void UnloadMod() - { - if (!IsLoaded()) return; - if (SelectedModModel is not null) - // ReSharper disable once EventUnsubscriptionViaAnonymousDelegate - SelectedModModel.PropertyChanged -= (_, _) => SettingsPropertiesChanged(); - SelectedModModel?.SkinModKeySwaps.Clear(); - IsReadOnlyMode = true; - _selectedSkinMod = null!; - _backendModModel = null!; - IsEditingModName = false; - SelectedModModel = new ModModel(); - SettingsPropertiesChanged(); - } - - private string[] _supportedImageExtensions = - { ".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".ico", ".svg", ".webp" }; - - [RelayCommand] - private async Task SetImageUriAsync() - { - var filePicker = new FileOpenPicker(); - foreach (var supportedImageExtension in _supportedImageExtensions) - filePicker.FileTypeFilter.Add(supportedImageExtension); - - filePicker.CommitButtonText = "Set Image"; - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow); - WinRT.Interop.InitializeWithWindow.Initialize(filePicker, hwnd); - - var file = await filePicker.PickSingleFileAsync(); - - if (file == null) return; - var imageUri = new Uri(file.Path); - SelectedModModel!.ImagePath = imageUri; - } - - public Task SetImageFromDragDropFile(IReadOnlyList items) - { - foreach (var storageItem in items) - { - if (storageItem is not StorageFile file) continue; - - if (_supportedImageExtensions.Contains(Path.GetExtension(file.Name))) - { - Uri.TryCreate(file.Path, UriKind.Absolute, out var imageUri); - - if (imageUri is null) - { - _notificationManager.ShowNotification("Error setting image", - "Could not set image, invalid Uri. Drag and drop can be unreliable in certain situations", - TimeSpan.FromSeconds(5)); - return Task.CompletedTask; - } - - SelectedModModel!.ImagePath = imageUri; - } - } - - return Task.CompletedTask; - } - - public async Task SetImageFromDragDropWeb(Uri? url) - { - if (!IsLoaded()) return; - - if (url is null || !url.IsAbsoluteUri || (url.Scheme != Uri.UriSchemeHttps && url.Scheme != Uri.UriSchemeHttp)) - { - _notificationManager.ShowNotification("Error setting image", - "Could not set image, invalid Uri. Drag and drop can be unreliable in certain situations", - TimeSpan.FromSeconds(5)); - return; - } - - if (!_supportedImageExtensions.Contains(Path.GetExtension(url.AbsolutePath))) - { - var invalidExtension = Path.GetExtension(url.AbsolutePath); - - invalidExtension = string.IsNullOrWhiteSpace(invalidExtension) - ? "Could not get extension" - : invalidExtension; - - _notificationManager.ShowNotification("Error setting image", - $"Could not set image, invalid extenstion: {invalidExtension}", - TimeSpan.FromSeconds(5)); - return; - } - - var tmpDir = App.TMP_DIR; - - var tmpFile = Path.Combine(tmpDir, $"WEB_DROP_{Guid.NewGuid():N}{Path.GetExtension(url.ToString())}"); - - await Task.Run(async () => - { - if (!Directory.Exists(tmpDir)) - Directory.CreateDirectory(tmpDir); - - var client = new HttpClient(); - var responseStream = await client.GetStreamAsync(url); - await using var fileStream = File.Create(tmpFile); - await responseStream.CopyToAsync(fileStream); - }); - - - var imageUri = new Uri(tmpFile); - SelectedModModel.ImagePath = imageUri; - } - - public async Task SetImageFromBitmapStreamAsync(RandomAccessStreamReference accessStreamReference, - IReadOnlyCollection formats) - { - if (!IsLoaded()) return; - var tmpDir = App.TMP_DIR; - - if (!Directory.Exists(tmpDir)) - Directory.CreateDirectory(tmpDir); - - var tmpFile = Path.Combine(tmpDir, $"CLIPBOARD_PASTE_{Guid.NewGuid():N}"); - - var fileExtension = _supportedImageExtensions.Append("bitmap").FirstOrDefault(supportedFormats => - formats.Any(format => - format.Trim('.').Equals(supportedFormats.Trim('.'), StringComparison.OrdinalIgnoreCase))); - - if (fileExtension is null) - { - _notificationManager.ShowNotification("Error setting image", - "Could not set image, invalid extenstion", - TimeSpan.FromSeconds(5)); - return; - } - - tmpFile += fileExtension; - - - await Task.Run(async () => - { - using var stream = await accessStreamReference.OpenReadAsync(); - await using var fileStream = File.Create(tmpFile); - await stream.AsStreamForRead().CopyToAsync(fileStream); - }); - - SelectedModModel.ImagePath = new Uri(tmpFile); - } - - [RelayCommand] - private void ToggleEditingModName() - { - IsEditingModName = !IsEditingModName; - } - - - [RelayCommand] - private async Task OpenModFolder() - { - if (!IsLoaded()) return; - await Launcher.LaunchFolderAsync( - await StorageFolder.GetFolderFromPathAsync(_selectedSkinMod.FullPath)); - } - - [RelayCommand] - private async Task SetModIniFileAsync() - { - if (!IsLoaded()) return; - var filePicker = new FileOpenPicker(); - filePicker.FileTypeFilter.Add(".ini"); - filePicker.CommitButtonText = "Set"; - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow); - WinRT.Interop.InitializeWithWindow.Initialize(filePicker, hwnd); - var file = await filePicker.PickSingleFileAsync(); - - if (file is null) - { - _logger.Debug("User cancelled file picker."); - return; - } - - var result = await Task.Run(() => _modSettingsService.SetModIniAsync(_selectedSkinMod.Id, file.Path)); - - - if (result.Notification is not null) - _notificationManager.ShowNotification(result.Notification); - - _selectedSkinMod.ClearCache(); - await ReloadModSettings(CancellationToken.None).ConfigureAwait(false); - } - - [RelayCommand] - private async Task ClearSetModIniFileAsync() - { - if (!IsLoaded()) return; - var autoDetect = SelectedModModel.IgnoreMergedIni; - - var result = await Task.Run(() => - _modSettingsService.SetModIniAsync(_selectedSkinMod.Id, string.Empty, autoDetect)); - - - if (result.Notification is not null) - _notificationManager.ShowNotification(result.Notification); - - _selectedSkinMod.ClearCache(); - await ReloadModSettings(CancellationToken.None).ConfigureAwait(false); - } - - private bool ModSettingsChanged() - { - return _backendModModel is not null && !_backendModModel.SettingsEquals(SelectedModModel); - } - - [RelayCommand(CanExecute = nameof(ModSettingsChanged))] - private async Task SaveModSettingsAsync(CancellationToken cancellationToken = default) - { - if (!IsLoaded()) return; - IsReadOnlyMode = true; - var errored = false; - - - var saveResult = - await Task.Run(() => _modSettingsService.LegacySaveSettingsAsync(SelectedModModel), cancellationToken); - - if (saveResult.TryPickT2(out var error, out var notFoundOrSuccess)) - { - errored = true; - } - else if (notFoundOrSuccess.TryPickT1(out var modNotFound, out _)) - { - _notificationManager.ShowNotification("Error saving mod settings", - $"Could not find mod with id {modNotFound.ModId}", - TimeSpan.FromSeconds(5)); - } - - if (_selectedSkinMod.Settings.HasMergedIni || SelectedModModel.SkinModKeySwaps.Any()) - { - var saveKeySwapResult = await Task.Run(() => _keySwapService.SaveKeySwapsAsync(SelectedModModel), - cancellationToken); - - saveKeySwapResult.Switch( - success => { }, - missingIni => - { - errored = true; - _notificationManager.ShowNotification("Error saving keyswap", - $"Could not find ini file for mod {SelectedModModel.Name}", - TimeSpan.FromSeconds(5)); - }, - modNotFound => - { - errored = true; - _notificationManager.ShowNotification("Error saving mod settings", - $"Could not find mod with id {modNotFound.ModId}", - TimeSpan.FromSeconds(5)); - }, - _ => { } - ); - } - - - IsReadOnlyMode = false; - - await ReloadModSettings(CancellationToken.None); - IsEditingModName = false; - - if (!errored) - _notificationManager.ShowNotification("Mod settings saved", - $"Settings saved for {SelectedModModel.Name}", TimeSpan.FromSeconds(2)); - } - - private void SettingsPropertiesChanged() - { - SaveModSettingsCommand.NotifyCanExecuteChanged(); - } - - [RelayCommand] - private void ClearImage() - { - if (!IsLoaded()) return; - SelectedModModel.ImagePath = ModModel.PlaceholderImagePath; - } - - [MemberNotNullWhen(true, nameof(_selectedSkinMod), nameof(_backendModModel), nameof(SelectedModModel))] - private bool IsLoaded() - { - return _selectedSkinMod is not null && _backendModModel is not null && - (SelectedModModel is not null || SelectedModModel?.Id != Guid.Empty); - } -} \ No newline at end of file diff --git a/src/GIMI-ModManager.WinUI/ViewModels/SubVms/MoveModsFlyoutVM.cs b/src/GIMI-ModManager.WinUI/ViewModels/SubVms/MoveModsFlyoutVM.cs deleted file mode 100644 index 79de015a..00000000 --- a/src/GIMI-ModManager.WinUI/ViewModels/SubVms/MoveModsFlyoutVM.cs +++ /dev/null @@ -1,381 +0,0 @@ -using System.Collections.ObjectModel; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using GIMI_ModManager.Core.Contracts.Services; -using GIMI_ModManager.Core.GamesService; -using GIMI_ModManager.Core.GamesService.Interfaces; -using GIMI_ModManager.Core.GamesService.Models; -using GIMI_ModManager.WinUI.Models; -using GIMI_ModManager.WinUI.Models.CustomControlTemplates; -using GIMI_ModManager.WinUI.Models.ViewModels; -using GIMI_ModManager.WinUI.Services.AppManagement; -using GIMI_ModManager.WinUI.Services.ModHandling; -using GIMI_ModManager.WinUI.Services.Notifications; -using Microsoft.UI.Xaml.Controls; -using Serilog; - -namespace GIMI_ModManager.WinUI.ViewModels.SubVms; - -public partial class MoveModsFlyoutVM : ObservableRecipient -{ - private readonly ISkinManagerService _skinManagerService; - private readonly IGameService _gameService; - private readonly ILogger _logger = App.GetService().ForContext(); - private readonly ModSettingsService _modSettingsService = App.GetService(); - - private IModdableObject _shownCharacter = null!; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(DeleteModsCommand), nameof(MoveModsCommand))] - private IModdableObject? _selectedCharacter = null; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(DeleteModsCommand), nameof(MoveModsCommand), - nameof(OverrideModCharacterSkinCommand))] - private ICharacterSkin? _selectedCharacterSkin = null; - - private ICharacterSkin? _backendSelectedCharacterSkin = null; - - [ObservableProperty] private bool _isMoveModsFlyoutOpen; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(DeleteModsCommand), nameof(OverrideModCharacterSkinCommand))] - private int _selectedModsCount; - - [ObservableProperty] private string _searchText = string.Empty; - - [ObservableProperty] private bool _selectedModHasCharacterSkinOverride; - - [ObservableProperty] private string _selectedModCharacterSkinOverrideDisplayName = string.Empty; - - public event EventHandler? CloseFlyoutEvent; - public ObservableCollection SuggestedCharacters { get; init; } = new(); - private List SelectedMods { get; init; } = new(); - - public ObservableCollection SelectableCharacterSkins { get; init; } = new(); - - public MoveModsFlyoutVM(IGameService gameService, ISkinManagerService skinManagerService) - { - _gameService = gameService; - _skinManagerService = skinManagerService; - } - - public void SetShownCharacter(IModdableObject selectedCharacter) - { - if (_shownCharacter is not null) - throw new InvalidOperationException("Selected character is already set"); - - _shownCharacter = selectedCharacter; - - if (_shownCharacter is ICharacter character) - { - var characterSkins = character.Skins.Select(SkinVM.FromSkin); - foreach (var characterSkin in characterSkins) - SelectableCharacterSkins.Add(new SelectCharacterTemplate(characterSkin)); - } - } - - public void SetActiveSkin(ICharacterSkin characterSkinVmSkin) - { - if (_shownCharacter is not ICharacter character) - throw new InvalidOperationException("Cannot set active skin for non-character object"); - - foreach (var selectableCharacterSkin in SelectableCharacterSkins) - if (selectableCharacterSkin.InternalName == characterSkinVmSkin.InternalName) - { - var characterSkin = character.Skins.FirstOrDefault(skin => - skin.InternalNameEquals(characterSkinVmSkin.InternalName)); - - - selectableCharacterSkin.IsSelected = true; - - _backendSelectedCharacterSkin = characterSkin ?? throw new InvalidOperationException( - $"Cannot find character skin with internal name {characterSkinVmSkin.InternalName}, when switching skin"); - - SelectedCharacterSkin = - character.Skins.FirstOrDefault(skin => - skin.InternalNameEquals(characterSkin)); - } - else - { - selectableCharacterSkin.IsSelected = false; - } - } - - - [RelayCommand] - private void SetSelectedMods(IEnumerable modModel) - { - SelectedMods.Clear(); - SelectedMods.AddRange(modModel); - SelectedModsCount = SelectedMods.Count; - if (SelectedModsCount == 1) - { - var selectedMod = SelectedMods.First(); - SelectedModHasCharacterSkinOverride = !string.IsNullOrWhiteSpace(selectedMod.CharacterSkinOverride); - SelectedModCharacterSkinOverrideDisplayName = selectedMod.CharacterSkinOverride; - } - else - { - SelectedModHasCharacterSkinOverride = false; - SelectedModCharacterSkinOverrideDisplayName = string.Empty; - } - } - - private bool CanMoveModsCommandExecute() - { - return SelectedCharacter is not null && SelectedModsCount > 0 - && (SelectedCharacterSkin is null || - SelectedCharacterSkin.InternalName.Equals( - _backendSelectedCharacterSkin?.InternalName)); - } - - [RelayCommand(CanExecute = nameof(CanMoveModsCommandExecute))] - private async Task MoveModsAsync() - { - var sourceModList = _skinManagerService.GetCharacterModList(_shownCharacter.InternalName); - var destinationModList = _skinManagerService.GetCharacterModList(SelectedCharacter!.InternalName); - var notificationManager = App.GetService(); - - var selectedCharacterName = - SelectedCharacter!.DisplayName; // Just in case it is nulled before the operation is done - var selectedModsCount = SelectedModsCount; // Just in case it is reset before the operation is done - - try - { - await Task.Run(() => - _skinManagerService.TransferMods(sourceModList, destinationModList, - SelectedMods.Select(modEntry => modEntry.Id))); - } - catch (InvalidOperationException e) - { - _logger.Error(e, "Error moving mods"); - notificationManager - .ShowNotification("Invalid Operation Exception", - $"Cannot move mods\n{e.Message}, see logs for details.", TimeSpan.FromSeconds(10)); - return; - } - - notificationManager.ShowNotification($"{selectedModsCount} Mods Moved", - $"Successfully moved {string.Join(",", SelectedMods.Select(m => m.Name))} mods to {selectedCharacterName}", - TimeSpan.FromSeconds(5)); - - ModsMoved?.Invoke(this, EventArgs.Empty); - } - - public event EventHandler? ModsMoved; - - private bool CanDeleteModsCommandExecute() - { - return SelectedCharacter is null && SelectedModsCount > 0 && SelectedCharacterSkin is null; - } - - [RelayCommand(CanExecute = nameof(CanDeleteModsCommandExecute))] - private async Task DeleteModsAsync() - { - var modList = _skinManagerService.GetCharacterModList(_shownCharacter.InternalName); - var modEntryIds = new List(SelectedMods.Select(modEntry => modEntry.Id)); - var modEntryNames = new List(SelectedMods.Select(modEntry => modEntry.Name)); - - - var shownCharacterName = - _shownCharacter.DisplayName; // Just in case it is nulled before the operation is done - var selectedModsCount = SelectedModsCount; // Just in case it is reset before the operation is done - var modsDeleted = 0; - - var notificationManager = App.GetService(); - var windowManager = App.GetService(); - - var MoveToRecycleBinCheckBox = new CheckBox() - { - Content = "Move to Recycle Bin?", - IsChecked = true - }; - var mods = new ListView() - { - ItemsSource = modEntryNames, - SelectionMode = ListViewSelectionMode.None - }; - - var scrollViewer = new ScrollViewer() - { - Content = mods, - HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, - VerticalScrollBarVisibility = ScrollBarVisibility.Auto, - Height = 400 - }; - var stackPanel = new StackPanel() - { - Children = - { - MoveToRecycleBinCheckBox, - scrollViewer - } - }; - var dialog = new ContentDialog() - { - Title = $"Delete These {selectedModsCount} Mods?", - Content = stackPanel, - PrimaryButtonText = "Delete", - SecondaryButtonText = "Cancel", - DefaultButton = ContentDialogButton.Primary - }; - - CloseFlyout(); - var result = await windowManager.ShowDialogAsync(dialog); - - if (result != ContentDialogResult.Primary) - return; - var recycleMods = MoveToRecycleBinCheckBox.IsChecked == true; - try - { - await Task.Run(() => - { - foreach (var modEntryId in modEntryIds) - { - modList.DeleteModBySkinEntryId(modEntryId, recycleMods); - modsDeleted++; - } - }); - } - catch (InvalidOperationException e) - { - _logger.Error(e, "Error deleting mods"); - notificationManager - .ShowNotification("Invalid Operation Exception", - $"Mods Deleted: {modsDeleted}. Some mods may not have been deleted, See Logs.\n{e.Message}", - TimeSpan.FromSeconds(10)); - if (modsDeleted > 0) - ModsDeleted?.Invoke(this, EventArgs.Empty); - return; - } - - notificationManager.ShowNotification($"{modsDeleted} Mods Deleted", - $"Successfully deleted {string.Join(", ", modEntryNames)} in {shownCharacterName} Mods Folder", - TimeSpan.FromSeconds(5)); - - ModsDeleted?.Invoke(this, EventArgs.Empty); - } - - public event EventHandler? ModsDeleted; - - private bool CanOverrideModCharacterSkin() - { - return SelectedCharacter is null && SelectedCharacterSkin is not null && SelectedModsCount > 0 - && !SelectedCharacterSkin.InternalName.Equals(_backendSelectedCharacterSkin?.InternalName); - } - - [RelayCommand(CanExecute = nameof(CanOverrideModCharacterSkin))] - private async Task OverrideModCharacterSkin() - { - if (SelectedCharacterSkin == null || _shownCharacter is not ICharacter character) return; - - var characterSkinToSet = - character.Skins.FirstOrDefault(charSkin => - charSkin.InternalNameEquals(SelectedCharacterSkin)); - - if (characterSkinToSet == null) - return; - - - foreach (var modModel in SelectedMods) - { - var result = - await _modSettingsService.SetCharacterSkinOverrideLegacy(modModel.Id, characterSkinToSet.InternalName); - - if (result.IsT0) continue; - - - var error = result.IsT1 ? result.AsT1.ToString() : result.AsT2.ToString(); - _logger.Error("Failed to override character skin for mod {modName}", modModel.Name); - App.GetService().ShowNotification( - $"Failed to override character skin for mod {modModel.Name}", - $"An Error Occurred. Reason: {error}", - TimeSpan.FromSeconds(5)); - continue; - } - - ModCharactersSkinOverriden?.Invoke(this, EventArgs.Empty); - CloseFlyoutCommand.Execute(null); - } - - public event EventHandler? ModCharactersSkinOverriden; - - - [RelayCommand] - private void CloseFlyout() - { - IsMoveModsFlyoutOpen = false; - CloseFlyoutEvent?.Invoke(this, EventArgs.Empty); - } - - private readonly IModdableObject - _noCharacterFound = new Character("None", "No Characters Found..."); - - [RelayCommand] - private async Task TextChanged(string searchString) - { - if (string.IsNullOrWhiteSpace(searchString)) - { - SuggestedCharacters.Clear(); - return; - } - - if (SelectedCharacter is not null) - return; - - SuggestedCharacters.Clear(); - var searchResultKeyValue = - await Task.Run(() => - _gameService.QueryModdableObjects(searchString, minScore: 100).OrderByDescending(kv => kv.Value)); - - var exclude = new List { _shownCharacter }; - - var eligibleCharacters = - searchResultKeyValue.Select(kv => kv.Key).Except(exclude).Take(5); - - - foreach (var eligibleCharacter in eligibleCharacters) - SuggestedCharacters.Add(eligibleCharacter); - - - if (SuggestedCharacters.Count == 0) - SuggestedCharacters.Add(_noCharacterFound); - } - - [RelayCommand] - private void ResetState() - { - SelectedCharacter = null; - SelectedCharacterSkin = null; - SuggestedCharacters.Clear(); - SearchText = string.Empty; - } - - public bool SelectCharacter(IModdableObject? characterVM) - { - if (_noCharacterFound.Equals(characterVM) || characterVM is null) return false; - - SuggestedCharacters.Clear(); - SelectedCharacter = characterVM; - SearchText = characterVM.DisplayName; - return true; - } - - - [RelayCommand] - private void SelectNewCharacterSkin(SelectCharacterTemplate? characterTemplate) - { - if (characterTemplate == null || _shownCharacter is not ICharacter character) return; - - var characterSkinToSet = character.Skins.FirstOrDefault(charSkin => - charSkin.InternalName.Equals(characterTemplate.InternalName)); - - if (characterSkinToSet == null) - return; - - SelectedCharacterSkin = characterSkinToSet; - foreach (var selectableCharacterSkin in SelectableCharacterSkins) selectableCharacterSkin.IsSelected = false; - characterTemplate.IsSelected = true; - } -} \ No newline at end of file diff --git a/src/GIMI-ModManager.WinUI/Views/CharacterDetailsPage.xaml b/src/GIMI-ModManager.WinUI/Views/CharacterDetailsPage.xaml deleted file mode 100644 index f9cb9f26..00000000 --- a/src/GIMI-ModManager.WinUI/Views/CharacterDetailsPage.xaml +++ /dev/null @@ -1,793 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -