Skip to content

Commit

Permalink
feat: Now possible to disable all other mods while activating the new…
Browse files Browse the repository at this point in the history
… mod when installing a new mod (#116)

refactor: Redid notifications and updated namespaces

fix: Potential fix for deleting mods freezing the app

tweaks: Bring main window to front after mod install
  • Loading branch information
Jorixon authored Jan 7, 2024
1 parent d7044dd commit 9130f0c
Show file tree
Hide file tree
Showing 30 changed files with 610 additions and 85 deletions.
12 changes: 7 additions & 5 deletions src/GIMI-ModManager.Core/Entities/CharacterModList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -402,19 +402,21 @@ public bool FolderAlreadyExists(string folderName)

public void DeleteModBySkinEntryId(Guid skinEntryId, bool moveToRecycleBin = true)
{
ISkinMod? mod = null;
lock (_modsLock)
{
var skinEntry = _mods.FirstOrDefault(modEntry => modEntry.Id == skinEntryId);
if (skinEntry is null)
throw new InvalidOperationException("Skin entry not found");
using var disableWatcher = DisableWatcher();
var mod = skinEntry.Mod;

mod = skinEntry.Mod;
UnTrackMod(skinEntry.Mod);
mod.Delete(moveToRecycleBin);
_logger?.Information("{Operation} mod {ModName} from {CharacterName} modList",
moveToRecycleBin ? "Recycled" : "Deleted", mod.Name, Character.InternalName);
}

using var disable = DisableWatcher();
mod.Delete(moveToRecycleBin);
_logger?.Information("{Operation} mod {ModName} from {CharacterName} modList",
moveToRecycleBin ? "Recycled" : "Deleted", mod.Name, Character.InternalName);
}

public bool IsMultipleModsActive(bool perSkin = false)
Expand Down
2 changes: 2 additions & 0 deletions src/GIMI-ModManager.WinUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Serilog;
using Serilog.Events;
using Serilog.Templates;
using NotificationManager = GIMI_ModManager.WinUI.Services.Notifications.NotificationManager;

namespace GIMI_ModManager.WinUI;

Expand Down Expand Up @@ -106,6 +107,7 @@ public App()
services.AddSingleton<NotificationManager>();
services.AddSingleton<ModNotificationManager>();
services.AddTransient<ModDragAndDropService>();
services.AddSingleton<CharacterSkinService>();

services.AddSingleton<ElevatorService>();
services.AddSingleton<GenshinProcessManager>();
Expand Down
2 changes: 1 addition & 1 deletion src/GIMI-ModManager.WinUI/Models/ModModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
using GIMI_ModManager.Core.Entities.Mods.Contract;
using GIMI_ModManager.Core.GamesService.Interfaces;
using GIMI_ModManager.Core.Helpers;
using GIMI_ModManager.WinUI.Services;
using GIMI_ModManager.WinUI.Services.Notifications;
using NotificationManager = GIMI_ModManager.WinUI.Services.Notifications.NotificationManager;

namespace GIMI_ModManager.WinUI.Models;

Expand Down
47 changes: 40 additions & 7 deletions src/GIMI-ModManager.WinUI/Models/Notification.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
namespace GIMI_ModManager.WinUI.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using GIMI_ModManager.WinUI.Services.Notifications;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

public record Notification
namespace GIMI_ModManager.WinUI.Models;

public class Notification : ObservableObject
{
public Notification(string title, string message)
public Notification(string title, TimeSpan duration, string? subtitle = null, UIElement? content = null,
FontIcon? icon = null, bool showNow = false,
string? logMessage = null, NotificationType type = NotificationType.Information)
{
Title = title;
Message = message;
Timestamp = DateTime.Now;
Subtitle = subtitle;
Content = content;
Duration = duration;
Icon = icon;
ShowNow = showNow;
LogMessage = logMessage;
Type = type;
}

public Guid Id { get; } = Guid.NewGuid();

public DateTime QueueTime { get; } = DateTime.Now;

public DateTime? ShowTime { get; set; }

public string Title { get; }
public string Message { get; }
public DateTime Timestamp { get; }
public string? Subtitle { get; }
public UIElement? Content { get; }
public TimeSpan Duration { get; }
public FontIcon? Icon { get; }
public bool ShowNow { get; }
public string? LogMessage { get; set; }

public NotificationType Type { get; }

public bool IsLogged { get; set; }


public override string ToString()
{
return LogMessage ?? $"Title: {Title} | Subtitle: {Subtitle} | Duration: {Duration} | " +
$"Icon: {Icon} | ShowNow: {ShowNow} | ShowTime: {ShowTime}";
}
}
2 changes: 2 additions & 0 deletions src/GIMI-ModManager.WinUI/Services/ActivationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ private async Task InitializeAsync()
await SetLanguage();
await SetWindowSettings();
await _themeSelectorService.InitializeAsync().ConfigureAwait(false);
_notificationManager.Initialize();
}


Expand Down Expand Up @@ -210,6 +211,7 @@ private async void OnApplicationExit(object sender, WindowEventArgs args)
_logger.Debug("JASM shutting down...");
_modUpdateAvailableChecker.CancelAndStop();
_updateChecker.CancelAndStop();
_notificationManager.CancelAndStop();

await saveWindowSettingsTask;
await _windowManagerService.CloseWindowsAsync().ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace GIMI_ModManager.WinUI.Services.AppManagement;
public class LifeCycleService
{
private readonly ILogger _logger;
private readonly NotificationManager _notificationManager;
private readonly Notifications.NotificationManager _notificationManager;

public LifeCycleService(ILogger logger, NotificationManager notificationManager)
public LifeCycleService(ILogger logger, Notifications.NotificationManager notificationManager)
{
_notificationManager = notificationManager;
_logger = logger.ForContext<LifeCycleService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@

namespace GIMI_ModManager.WinUI.Services.AppManagement.Updating;

public sealed class UpdateChecker : IDisposable
public sealed class UpdateChecker
{
private readonly ILogger _logger;
private readonly ILocalSettingsService _localSettingsService;
private readonly NotificationManager _notificationManager;
private readonly Notifications.NotificationManager _notificationManager;

public Version CurrentVersion { get; private set; }
public Version? LatestRetrievedVersion { get; private set; }
public event EventHandler<NewVersionEventArgs>? NewVersionAvailable;
private Version? _ignoredVersion;
public Version? IgnoredVersion => _ignoredVersion;
private bool DisableChecker;
private readonly CancellationTokenSource _cancellationTokenSource;
private CancellationTokenSource _cancellationTokenSource;

private const string ReleasesApiUrl = "https://api.github.com/repos/Jorixon/JASM/releases?per_page=2";

public UpdateChecker(ILogger logger, ILocalSettingsService localSettingsService,
NotificationManager notificationManager, CancellationToken cancellationToken = default)
Notifications.NotificationManager notificationManager, CancellationToken cancellationToken = default)
{
_logger = logger.ForContext<UpdateChecker>();
_localSettingsService = localSettingsService;
Expand Down Expand Up @@ -153,15 +153,15 @@ private HttpClient CreateHttpClient()
return httpClient;
}

public void Dispose()
{
_cancellationTokenSource.Dispose();
}

public void CancelAndStop()
{
_cancellationTokenSource.Cancel();
Dispose();
if (_cancellationTokenSource is null || _cancellationTokenSource.IsCancellationRequested)
return;
var cts = _cancellationTokenSource;
_cancellationTokenSource = null!;
cts.Cancel();
cts.Dispose();
_logger.Debug("JASM update checker stopped");
}

Expand Down
161 changes: 161 additions & 0 deletions src/GIMI-ModManager.WinUI/Services/ModHandling/CharacterSkinService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System.Diagnostics;
using GIMI_ModManager.Core.Contracts.Entities;
using GIMI_ModManager.Core.Contracts.Services;
using GIMI_ModManager.Core.GamesService;
using GIMI_ModManager.Core.GamesService.Interfaces;
using GIMI_ModManager.Core.Helpers;
using GIMI_ModManager.Core.Services;
using Serilog;

namespace GIMI_ModManager.WinUI.Services.ModHandling;

public class CharacterSkinService
{
private readonly ILogger _logger;
private readonly ModCrawlerService _modCrawlerService;
private readonly ISkinManagerService _skinManagerService;
private readonly IGameService _gameService;

public CharacterSkinService(ModCrawlerService modCrawlerService, ISkinManagerService skinManagerService,
ILogger logger, IGameService gameService)
{
_modCrawlerService = modCrawlerService;
_skinManagerService = skinManagerService;
_gameService = gameService;
_logger = logger.ForContext<CharacterSkinService>();
}

private async IAsyncEnumerable<ISkinMod> FilterModsToSkin(ICharacterSkin skin,
IEnumerable<ISkinMod> mods, bool ignoreUndetectableMods = false)
{
foreach (var mod in mods)
{
var modSettings = await mod.Settings.TryReadSettingsAsync();

if (modSettings is null)
{
_logger.Warning("Could not read settings for mod {mod}", mod.Name);
Debugger.Break();

if (ignoreUndetectableMods)
continue;
}


var modSkin = modSettings?.CharacterSkinOverride;

// Has skin override and is a match for the shown skin
if (modSkin is not null && skin.InternalNameEquals(modSkin))
{
yield return mod;
continue;
}

// Has override skin, but does not match any of the characters skins
if (modSkin is not null && !skin.Character.Skins.Any(skinVm =>
skinVm.InternalNameEquals(modSkin)))
{
// In this case, the override skin is not a valid skin for this character, so we just add it.
yield return mod;
continue;
}


var detectedSkin =
_modCrawlerService.GetFirstSubSkinRecursive(mod.FullPath, skin.Character.InternalName);

// If we can detect the skin, and the mod has no override skin, check if the detected skin matches the shown skin.
if (modSkin is null && detectedSkin is not null && detectedSkin.InternalNameEquals(skin.InternalName))
{
yield return mod;
continue;
}

// If we can't detect the skin, and the mod has no override skin.
// We add it if the caller wants to see undetectable mods.
if (detectedSkin is null && modSkin is null && !ignoreUndetectableMods)
yield return mod;
}
}


public async IAsyncEnumerable<ISkinMod> GetModsForSkinAsync(ICharacterSkin skin,
bool ignoreUndetectableMods = false)
{
var modList = _skinManagerService.GetCharacterModList(skin.Character);

var mods = modList.Mods.Select(entry => entry.Mod).ToArray();
await foreach (var skinMod in FilterModsToSkin(skin, mods, ignoreUndetectableMods))
yield return skinMod;
}


public async Task<GetAllModsBySkinResult?> GetAllModsBySkin(ICharacter character)
{
var modList = _skinManagerService.GetCharacterModListOrDefault(character.InternalName);
if (modList is null) return null;

var mods = modList.Mods.Select(entry => entry.Mod).ToArray();

var result = new Dictionary<ICharacterSkin, ISkinMod[]>();
foreach (var skin in character.Skins)
{
var modsForSkin = new List<ISkinMod>();
await foreach (var skinMod in FilterModsToSkin(skin, mods))
{
modsForSkin.Add(skinMod);
}

result.Add(skin, modsForSkin.ToArray());
}

var unknownMods = mods
.Where(mod => !result.Values.SelectMany(detectedMods => detectedMods)
.Contains(mod))
.ToArray();

return new GetAllModsBySkinResult(result, unknownMods);
}


public async Task<ICharacterSkin?> GetFirstSkinForMod(ISkinMod mod, ICharacter? character = null)
{
var modSettings = await mod.Settings.TryReadSettingsAsync();

if (modSettings is null)
{
_logger.Warning("Could not read settings for mod {mod}", mod.Name);
Debugger.Break();
}

var modSkin = modSettings?.CharacterSkinOverride;


if (!modSkin.IsNullOrEmpty())
{
var foundSkin = character?.Skins.FirstOrDefault(skinVm =>
skinVm.InternalNameEquals(modSkin));

if (foundSkin != null)
return foundSkin;


var allSkins = _gameService.GetCharacters().SelectMany(characterVm => characterVm.Skins).ToArray();

var skin = allSkins.FirstOrDefault(skinVm =>
skinVm.InternalNameEquals(modSkin));

if (skin is not null)
return skin;
}

var detectedSkin =
_modCrawlerService.GetFirstSubSkinRecursive(mod.FullPath, character?.InternalName.Id);

return detectedSkin;
}

public record GetAllModsBySkinResult(
Dictionary<ICharacterSkin, ISkinMod[]> ModsBySkin,
ISkinMod[] UndetectableMods);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ public class KeySwapService
{
private readonly ISkinManagerService _skinManagerService;
private readonly ILogger _logger;
private readonly NotificationManager _notificationManager;
private readonly Notifications.NotificationManager _notificationManager;

public KeySwapService(ISkinManagerService skinManagerService, ILogger logger,
NotificationManager notificationManager)
Notifications.NotificationManager notificationManager)
{
_skinManagerService = skinManagerService;
_notificationManager = notificationManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public class ModDragAndDropService
private readonly IWindowManagerService _windowManagerService;


private readonly NotificationManager _notificationManager;
private readonly Notifications.NotificationManager _notificationManager;

public event EventHandler<DragAndDropFinishedArgs>? DragAndDropFinished;

public ModDragAndDropService(ILogger logger, NotificationManager notificationManager,
public ModDragAndDropService(ILogger logger, Notifications.NotificationManager notificationManager,
ModInstallerService modInstallerService, IWindowManagerService windowManagerService)
{
_notificationManager = notificationManager;
Expand Down
Loading

0 comments on commit 9130f0c

Please sign in to comment.