wabbajack/Wabbajack/View Models/Gallery/ModListGalleryVM.cs

207 lines
7.6 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.ModListRegistry;
namespace Wabbajack
{
public class ModListGalleryVM : BackNavigatingVM
{
public MainWindowVM MWVM { get; }
public ObservableCollectionExtended<ModListMetadataVM> ModLists { get; } = new ObservableCollectionExtended<ModListMetadataVM>();
private FiltersSettings settings;
private int missingHashFallbackCounter;
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; }
2020-05-07 05:32:36 +00:00
2020-04-27 10:18:08 +00:00
[Reactive]
public bool ShowNSFW { get; set; }
2020-05-07 05:32:36 +00:00
[Reactive]
public string GameType { get; set; }
public List<string> GameTypeEntries { get { return GetGameTypeEntries(); } }
private readonly ObservableAsPropertyHelper<bool> _Loaded;
public bool Loaded => _Loaded.Value;
public ICommand ClearFiltersCommand { get; }
public ModListGalleryVM(MainWindowVM mainWindowVM)
: base(mainWindowVM)
{
MWVM = mainWindowVM;
// load persistent filter settings
settings = MWVM.Settings.Filters;
2020-05-10 04:59:34 +00:00
if (settings.IsPersistent)
{
GameType = !string.IsNullOrEmpty(settings.Game) ? settings.Game : ALL_GAME_TYPE;
2020-05-10 04:59:34 +00:00
ShowNSFW = settings.ShowNSFW;
OnlyInstalled = settings.OnlyInstalled;
Search = settings.Search;
2020-05-10 17:12:43 +00:00
// subscribe to save signal
MWVM.Settings.SaveSignal
.Subscribe(_ => UpdateFiltersSettings())
.DisposeWith(this.CompositeDisposable);
}
else
GameType = ALL_GAME_TYPE;
ClearFiltersCommand = ReactiveCommand.Create(
() =>
{
OnlyInstalled = false;
2020-04-27 10:18:08 +00:00
ShowNSFW = false;
Search = string.Empty;
GameType = ALL_GAME_TYPE;
});
var random = new Random();
var sourceList = Observable.Return(Unit.Default)
.ObserveOn(RxApp.TaskpoolScheduler)
.SelectTask(async _ =>
{
try
{
Error = null;
var list = await ModlistMetadata.LoadFromGithub();
Error = ErrorResponse.Success;
return list
// Sort randomly initially, just to give each list a fair shake
.Shuffle(random)
2020-03-22 15:50:53 +00:00
.AsObservableChangeSet(x => x.DownloadMetadata?.Hash ?? Hash.Empty);
}
catch (Exception ex)
{
Utils.Error(ex);
Error = ErrorResponse.Fail(ex);
2020-03-22 15:50:53 +00:00
return Observable.Empty<IChangeSet<ModlistMetadata, Hash>>();
}
})
// Unsubscribe and release when not active
.FlowSwitch(
this.WhenAny(x => x.IsActive),
2020-03-22 15:50:53 +00:00
valueWhenOff: Observable.Return(ChangeSet<ModlistMetadata, Hash>.Empty))
.Switch()
.RefCount();
_Loaded = sourceList.CollectionCount()
.Select(c => c > 0)
.ToProperty(this, nameof(Loaded));
// Convert to VM and bind to resulting list
sourceList
.ObserveOnGuiThread()
.Transform(m => new ModListMetadataVM(this, m))
.DisposeMany()
// Filter only installed
2020-04-27 10:18:08 +00:00
.Filter(this.WhenAny(x => x.OnlyInstalled)
.Select<bool, Func<ModListMetadataVM, bool>>(onlyInstalled => (vm) =>
{
if (!onlyInstalled) return true;
if (!GameRegistry.Games.TryGetValue(vm.Metadata.Game, out var gameMeta)) return false;
return gameMeta.IsInstalled;
}))
// Filter on search box
2020-04-27 10:18:08 +00:00
.Filter(this.WhenAny(x => x.Search)
.Debounce(TimeSpan.FromMilliseconds(150), RxApp.MainThreadScheduler)
.Select<string, Func<ModListMetadataVM, bool>>(search => (vm) =>
{
if (string.IsNullOrWhiteSpace(search)) return true;
return vm.Metadata.Title.ContainsCaseInsensitive(search);
}))
2020-04-27 10:18:08 +00:00
.Filter(this.WhenAny(x => x.ShowNSFW)
.Select<bool, Func<ModListMetadataVM, bool>>(showNSFW => vm =>
{
if (!vm.Metadata.NSFW) return true;
return vm.Metadata.NSFW && showNSFW;
}))
2020-05-07 05:32:36 +00:00
// Filter by Game
.Filter(this.WhenAny(x => x.GameType)
.Debounce(TimeSpan.FromMilliseconds(150), RxApp.MainThreadScheduler)
.Select<string, Func<ModListMetadataVM, bool>>(GameType => (vm) =>
{
if (GameType == ALL_GAME_TYPE)
2020-05-07 05:32:36 +00:00
return true;
if (string.IsNullOrEmpty(GameType))
return false;
return GameType == vm.Metadata.Game.GetDescription<Game>().ToString();
2020-05-07 05:32:36 +00:00
}))
.Filter(this.WhenAny(x => x.ShowNSFW)
.Select<bool, Func<ModListMetadataVM, bool>>(showNSFW => vm =>
{
if (!vm.Metadata.NSFW) return true;
return vm.Metadata.NSFW && showNSFW;
}))
// Put broken lists at bottom
.Sort(Comparer<ModListMetadataVM>.Create((a, b) => a.IsBroken.CompareTo(b.IsBroken)))
.Bind(ModLists)
.Subscribe()
.DisposeWith(CompositeDisposable);
// Extra GC when navigating away, just to immediately clean up modlist metadata
this.WhenAny(x => x.IsActive)
.Where(x => !x)
.Skip(1)
.Delay(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler)
.Subscribe(_ =>
{
GC.Collect();
})
.DisposeWith(CompositeDisposable);
}
public override void Unload()
{
Error = null;
}
2020-05-07 05:32:36 +00:00
private List<string> GetGameTypeEntries()
{
List<string> gameEntries = new List<string> { ALL_GAME_TYPE };
gameEntries.AddRange(EnumExtensions.GetAllItems<Game>().Select(gameType => gameType.GetDescription<Game>()));
2020-05-07 05:32:36 +00:00
return gameEntries;
}
private void UpdateFiltersSettings()
{
2020-05-10 04:59:34 +00:00
if (!settings.IsPersistent)
return;
if (!string.IsNullOrEmpty(GameType))
settings.Game = GameType;
if (Search != null)
settings.Search = Search;
2020-05-10 04:59:34 +00:00
settings.ShowNSFW = ShowNSFW;
settings.OnlyInstalled = OnlyInstalled;
}
}
}