2022-03-13 22:47:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reactive.Disposables;
|
|
|
|
|
using System.Reactive.Linq;
|
2023-05-07 20:32:18 +00:00
|
|
|
|
using System.Threading;
|
2022-03-13 22:47:30 +00:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Windows.Input;
|
|
|
|
|
using DynamicData;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using ReactiveUI;
|
|
|
|
|
using ReactiveUI.Fody.Helpers;
|
|
|
|
|
using Wabbajack.Common;
|
|
|
|
|
using Wabbajack.Downloaders.GameFile;
|
|
|
|
|
using Wabbajack.DTOs;
|
|
|
|
|
using Wabbajack.Messages;
|
|
|
|
|
using Wabbajack.Networking.WabbajackClientApi;
|
|
|
|
|
using Wabbajack.Services.OSIntegrated;
|
|
|
|
|
using Wabbajack.Services.OSIntegrated.Services;
|
|
|
|
|
|
|
|
|
|
namespace Wabbajack
|
|
|
|
|
{
|
|
|
|
|
public class ModListGalleryVM : BackNavigatingVM
|
|
|
|
|
{
|
|
|
|
|
public MainWindowVM MWVM { get; }
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-30 03:39:48 +00:00
|
|
|
|
private readonly SourceCache<ModListMetadataVM, string> _modLists = new(x => x.Metadata.NamespacedName);
|
2022-03-13 22:47:30 +00:00
|
|
|
|
public ReadOnlyObservableCollection<ModListMetadataVM> _filteredModLists;
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
public ReadOnlyObservableCollection<ModListMetadataVM> ModLists => _filteredModLists;
|
|
|
|
|
|
|
|
|
|
private const string ALL_GAME_TYPE = "All";
|
|
|
|
|
|
|
|
|
|
[Reactive] public IErrorResponse Error { get; set; }
|
|
|
|
|
|
|
|
|
|
[Reactive] public string Search { get; set; }
|
|
|
|
|
|
|
|
|
|
[Reactive] public bool OnlyInstalled { get; set; }
|
|
|
|
|
|
|
|
|
|
[Reactive] public bool ShowNSFW { get; set; }
|
|
|
|
|
|
2022-05-15 20:17:36 +00:00
|
|
|
|
[Reactive] public bool ShowUnofficialLists { get; set; }
|
2022-03-13 22:47:30 +00:00
|
|
|
|
|
|
|
|
|
[Reactive] public string GameType { get; set; }
|
|
|
|
|
|
2023-05-07 20:32:18 +00:00
|
|
|
|
public class GameTypeEntry
|
|
|
|
|
{
|
|
|
|
|
public GameTypeEntry(string humanFriendlyName, int amount)
|
|
|
|
|
{
|
|
|
|
|
HumanFriendlyName = humanFriendlyName;
|
|
|
|
|
Amount = amount;
|
|
|
|
|
FormattedName = $"{HumanFriendlyName} ({Amount})";
|
|
|
|
|
}
|
|
|
|
|
public string HumanFriendlyName { get; set; }
|
|
|
|
|
public int Amount { get; set; }
|
|
|
|
|
public string FormattedName { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Reactive] public List<GameTypeEntry> GameTypeEntries { get; set; }
|
|
|
|
|
private bool _filteringOnGame;
|
|
|
|
|
private GameTypeEntry _selectedGameTypeEntry = null;
|
|
|
|
|
|
|
|
|
|
public GameTypeEntry SelectedGameTypeEntry
|
|
|
|
|
{
|
|
|
|
|
get => _selectedGameTypeEntry;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
RaiseAndSetIfChanged(ref _selectedGameTypeEntry, value == null ? GameTypeEntries?.FirstOrDefault(gte => gte.HumanFriendlyName == ALL_GAME_TYPE) : value);
|
|
|
|
|
GameType = _selectedGameTypeEntry?.HumanFriendlyName;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-13 22:47:30 +00:00
|
|
|
|
|
|
|
|
|
private readonly Client _wjClient;
|
|
|
|
|
private readonly ILogger<ModListGalleryVM> _logger;
|
|
|
|
|
private readonly GameLocator _locator;
|
|
|
|
|
private readonly ModListDownloadMaintainer _maintainer;
|
|
|
|
|
private readonly SettingsManager _settingsManager;
|
2023-05-07 20:32:18 +00:00
|
|
|
|
private readonly CancellationToken _cancellationToken;
|
2022-03-13 22:47:30 +00:00
|
|
|
|
|
|
|
|
|
private FiltersSettings settings { get; set; } = new();
|
|
|
|
|
public ICommand ClearFiltersCommand { get; set; }
|
|
|
|
|
|
2023-05-07 20:32:18 +00:00
|
|
|
|
public ModListGalleryVM(ILogger<ModListGalleryVM> logger, Client wjClient, GameLocator locator,
|
|
|
|
|
SettingsManager settingsManager, ModListDownloadMaintainer maintainer, CancellationToken cancellationToken)
|
2022-03-13 22:47:30 +00:00
|
|
|
|
: base(logger)
|
|
|
|
|
{
|
|
|
|
|
_wjClient = wjClient;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_locator = locator;
|
|
|
|
|
_maintainer = maintainer;
|
|
|
|
|
_settingsManager = settingsManager;
|
2023-05-07 20:32:18 +00:00
|
|
|
|
_cancellationToken = cancellationToken;
|
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
ClearFiltersCommand = ReactiveCommand.Create(
|
|
|
|
|
() =>
|
|
|
|
|
{
|
|
|
|
|
OnlyInstalled = false;
|
|
|
|
|
ShowNSFW = false;
|
2022-05-15 20:17:36 +00:00
|
|
|
|
ShowUnofficialLists = false;
|
2022-03-13 22:47:30 +00:00
|
|
|
|
Search = string.Empty;
|
2023-05-07 20:32:18 +00:00
|
|
|
|
SelectedGameTypeEntry = GameTypeEntries.FirstOrDefault();
|
2022-03-13 22:47:30 +00:00
|
|
|
|
});
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
BackCommand = ReactiveCommand.Create(
|
|
|
|
|
() =>
|
|
|
|
|
{
|
|
|
|
|
NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.WhenActivated(disposables =>
|
2023-05-07 20:32:18 +00:00
|
|
|
|
{
|
2022-03-13 22:47:30 +00:00
|
|
|
|
LoadModLists().FireAndForget();
|
|
|
|
|
LoadSettings().FireAndForget();
|
|
|
|
|
|
|
|
|
|
Disposable.Create(() => SaveSettings().FireAndForget())
|
|
|
|
|
.DisposeWith(disposables);
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
var searchTextPredicates = this.ObservableForProperty(vm => vm.Search)
|
|
|
|
|
.Select(change => change.Value)
|
|
|
|
|
.StartWith("")
|
|
|
|
|
.Select<string, Func<ModListMetadataVM, bool>>(txt =>
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(txt)) return _ => true;
|
|
|
|
|
return item => item.Metadata.Title.ContainsCaseInsensitive(txt) ||
|
|
|
|
|
item.Metadata.Description.ContainsCaseInsensitive(txt);
|
|
|
|
|
});
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
var onlyInstalledGamesFilter = this.ObservableForProperty(vm => vm.OnlyInstalled)
|
|
|
|
|
.Select(v => v.Value)
|
|
|
|
|
.Select<bool, Func<ModListMetadataVM, bool>>(onlyInstalled =>
|
|
|
|
|
{
|
|
|
|
|
if (onlyInstalled == false) return _ => true;
|
|
|
|
|
return item => _locator.IsInstalled(item.Metadata.Game);
|
|
|
|
|
})
|
|
|
|
|
.StartWith(_ => true);
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-05-15 20:17:36 +00:00
|
|
|
|
var showUnofficial = this.ObservableForProperty(vm => vm.ShowUnofficialLists)
|
2022-03-13 22:47:30 +00:00
|
|
|
|
.Select(v => v.Value)
|
2022-05-15 20:17:36 +00:00
|
|
|
|
.StartWith(false)
|
|
|
|
|
.Select<bool, Func<ModListMetadataVM, bool>>(unoffical =>
|
2022-03-13 22:47:30 +00:00
|
|
|
|
{
|
2022-05-15 20:17:36 +00:00
|
|
|
|
if (unoffical) return x => true;
|
|
|
|
|
return x => x.Metadata.Official;
|
|
|
|
|
});
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
var showNSFWFilter = this.ObservableForProperty(vm => vm.ShowNSFW)
|
|
|
|
|
.Select(v => v.Value)
|
|
|
|
|
.Select<bool, Func<ModListMetadataVM, bool>>(showNsfw => { return item => item.Metadata.NSFW == showNsfw; })
|
|
|
|
|
.StartWith(item => item.Metadata.NSFW == false);
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
var gameFilter = this.ObservableForProperty(vm => vm.GameType)
|
|
|
|
|
.Select(v => v.Value)
|
|
|
|
|
.Select<string, Func<ModListMetadataVM, bool>>(selected =>
|
|
|
|
|
{
|
2023-05-07 20:32:18 +00:00
|
|
|
|
_filteringOnGame = true;
|
2022-03-13 22:47:30 +00:00
|
|
|
|
if (selected is null or ALL_GAME_TYPE) return _ => true;
|
|
|
|
|
return item => item.Metadata.Game.MetaData().HumanFriendlyGameName == selected;
|
|
|
|
|
})
|
|
|
|
|
.StartWith(_ => true);
|
2023-05-07 20:32:18 +00:00
|
|
|
|
|
2022-03-13 22:47:30 +00:00
|
|
|
|
_modLists.Connect()
|
|
|
|
|
.ObserveOn(RxApp.MainThreadScheduler)
|
|
|
|
|
.Filter(searchTextPredicates)
|
|
|
|
|
.Filter(onlyInstalledGamesFilter)
|
2022-05-15 20:17:36 +00:00
|
|
|
|
.Filter(showUnofficial)
|
2022-03-13 22:47:30 +00:00
|
|
|
|
.Filter(showNSFWFilter)
|
|
|
|
|
.Filter(gameFilter)
|
|
|
|
|
.Bind(out _filteredModLists)
|
2023-05-07 20:32:18 +00:00
|
|
|
|
.Subscribe((_) =>
|
|
|
|
|
{
|
|
|
|
|
if (!_filteringOnGame)
|
|
|
|
|
{
|
|
|
|
|
var previousGameType = GameType;
|
|
|
|
|
SelectedGameTypeEntry = null;
|
|
|
|
|
GameTypeEntries = new(GetGameTypeEntries());
|
|
|
|
|
var nextEntry = GameTypeEntries.FirstOrDefault(gte => previousGameType == gte.HumanFriendlyName);
|
|
|
|
|
SelectedGameTypeEntry = nextEntry != default ? nextEntry : GameTypeEntries.FirstOrDefault(gte => GameType == ALL_GAME_TYPE);
|
|
|
|
|
}
|
|
|
|
|
_filteringOnGame = false;
|
|
|
|
|
})
|
2022-03-13 22:47:30 +00:00
|
|
|
|
.DisposeWith(disposables);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class FilterSettings
|
|
|
|
|
{
|
|
|
|
|
public string GameType { get; set; }
|
|
|
|
|
public bool ShowNSFW { get; set; }
|
2022-05-15 20:17:36 +00:00
|
|
|
|
public bool ShowUnofficialLists { get; set; }
|
2022-03-13 22:47:30 +00:00
|
|
|
|
public bool OnlyInstalled { get; set; }
|
|
|
|
|
public string Search { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Unload()
|
|
|
|
|
{
|
|
|
|
|
Error = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SaveSettings()
|
|
|
|
|
{
|
|
|
|
|
await _settingsManager.Save("modlist_gallery", new FilterSettings
|
|
|
|
|
{
|
|
|
|
|
GameType = GameType,
|
|
|
|
|
ShowNSFW = ShowNSFW,
|
2022-05-15 20:17:36 +00:00
|
|
|
|
ShowUnofficialLists = ShowUnofficialLists,
|
2022-03-13 22:47:30 +00:00
|
|
|
|
Search = Search,
|
|
|
|
|
OnlyInstalled = OnlyInstalled,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task LoadSettings()
|
|
|
|
|
{
|
|
|
|
|
using var ll = LoadingLock.WithLoading();
|
2023-05-07 20:32:18 +00:00
|
|
|
|
RxApp.MainThreadScheduler.Schedule(await _settingsManager.Load<FilterSettings>("modlist_gallery"),
|
2022-03-13 22:47:30 +00:00
|
|
|
|
(_, s) =>
|
|
|
|
|
{
|
2023-05-07 20:32:18 +00:00
|
|
|
|
SelectedGameTypeEntry = GameTypeEntries?.FirstOrDefault(gte => gte.HumanFriendlyName.Equals(s.GameType));
|
2022-03-13 22:47:30 +00:00
|
|
|
|
ShowNSFW = s.ShowNSFW;
|
2022-05-15 20:17:36 +00:00
|
|
|
|
ShowUnofficialLists = s.ShowUnofficialLists;
|
2022-03-13 22:47:30 +00:00
|
|
|
|
Search = s.Search;
|
|
|
|
|
OnlyInstalled = s.OnlyInstalled;
|
|
|
|
|
return Disposable.Empty;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task LoadModLists()
|
|
|
|
|
{
|
|
|
|
|
using var ll = LoadingLock.WithLoading();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var modLists = await _wjClient.LoadLists();
|
|
|
|
|
_modLists.Edit(e =>
|
|
|
|
|
{
|
|
|
|
|
e.Clear();
|
|
|
|
|
e.AddOrUpdate(modLists.Select(m =>
|
2023-05-07 20:32:18 +00:00
|
|
|
|
new ModListMetadataVM(_logger, this, m, _maintainer, _wjClient, _cancellationToken)));
|
2022-03-13 22:47:30 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "While loading lists");
|
|
|
|
|
ll.Fail();
|
|
|
|
|
}
|
|
|
|
|
ll.Succeed();
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-07 20:32:18 +00:00
|
|
|
|
private List<GameTypeEntry> GetGameTypeEntries()
|
2022-03-13 22:47:30 +00:00
|
|
|
|
{
|
2023-05-07 20:32:18 +00:00
|
|
|
|
return ModLists.Select(fm => fm.Metadata)
|
|
|
|
|
.GroupBy(m => m.Game)
|
|
|
|
|
.Select(g => new GameTypeEntry(g.Key.MetaData().HumanFriendlyGameName, g.Count()))
|
|
|
|
|
.OrderBy(gte => gte.HumanFriendlyName)
|
|
|
|
|
.Prepend(new GameTypeEntry(ALL_GAME_TYPE, ModLists.Count))
|
|
|
|
|
.ToList();
|
2022-03-13 22:47:30 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|