diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs
index 61a2754e2..408ce0d50 100644
--- a/src/Artemis.Core/Constants.cs
+++ b/src/Artemis.Core/Constants.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.IO;
using Artemis.Core.JsonConverters;
-using Artemis.Storage.Entities.Plugins;
+using Artemis.Core.Services.Core;
using Newtonsoft.Json;
namespace Artemis.Core
@@ -40,6 +40,14 @@ namespace Artemis.Core
Guid = Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff"), Name = "Artemis Core", Version = new Version(2, 0)
};
+ ///
+ /// The build information related to the currently running Artemis build
+ /// Information is retrieved from buildinfo.json
+ ///
+ public static readonly BuildInfo BuildInfo = File.Exists(Path.Combine(ApplicationFolder, "buildinfo.json"))
+ ? JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(ApplicationFolder, "buildinfo.json")))
+ : new BuildInfo();
+
///
/// The plugin used by core components of Artemis
///
@@ -52,10 +60,11 @@ namespace Artemis.Core
{
Converters = new List {new SKColorConverter(), new ForgivingIntConverter()}
};
+
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
{
TypeNameHandling = TypeNameHandling.All,
- Converters = new List { new SKColorConverter(), new ForgivingIntConverter() }
+ Converters = new List {new SKColorConverter(), new ForgivingIntConverter()}
};
///
diff --git a/src/Artemis.Core/Services/Core/BuildInfo.cs b/src/Artemis.Core/Services/Core/BuildInfo.cs
new file mode 100644
index 000000000..2be8b4a96
--- /dev/null
+++ b/src/Artemis.Core/Services/Core/BuildInfo.cs
@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+
+namespace Artemis.Core.Services.Core
+{
+ ///
+ /// Represents build information related to the currently running Artemis build
+ ///
+ public class BuildInfo
+ {
+ ///
+ /// Gets the unique ID of this build
+ ///
+ public int BuildId { get; internal set; }
+
+ ///
+ /// Gets the build number. This contains the date and the build count for that day.
+ /// Per example 20210108.4
+ ///
+ public double BuildNumber { get; internal set; }
+
+ ///
+ /// Gets the branch of the triggering repo the build was created for.
+ ///
+ public string SourceBranch { get; internal set; } = null!;
+
+ ///
+ /// Gets the commit ID used to create this build
+ ///
+ public string SourceVersion { get; internal set; } = null!;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index 3bbe992b6..55674dd4c 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions;
using Artemis.Core.Ninject;
+using Artemis.Core.Services.Core;
using Artemis.Storage;
using HidSharp;
+using Newtonsoft.Json;
using Ninject;
using RGB.NET.Core;
using Serilog;
@@ -37,9 +40,17 @@ namespace Artemis.Core.Services
private DateTime _lastExceptionLog;
private List _modules = new();
- // ReSharper disable UnusedParameter.Local - Storage migration and module service are injected early to ensure it runs before anything else
- public CoreService(IKernel kernel, ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginManagementService pluginManagementService,
- IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService)
+ // ReSharper disable UnusedParameter.Local
+ public CoreService(IKernel kernel,
+ ILogger logger,
+ StorageMigrationService _, // injected to ensure migration runs early
+ ISettingsService settingsService,
+ IPluginManagementService pluginManagementService,
+ IRgbService rgbService,
+ ISurfaceService surfaceService,
+ IProfileService profileService,
+ IModuleService moduleService // injected to ensure module priorities get applied
+ )
{
Kernel = kernel;
Constants.CorePlugin.Kernel = kernel;
@@ -82,7 +93,8 @@ namespace Artemis.Core.Services
throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
AssemblyInformationalVersionAttribute? versionAttribute = typeof(CoreService).Assembly.GetCustomAttribute();
- _logger.Information("Initializing Artemis Core version {version}", versionAttribute?.InformationalVersion);
+ _logger.Information("Initializing Artemis Core version {version}, build {buildNumber} branch {branch}.", versionAttribute?.InformationalVersion, Constants.BuildInfo.BuildNumber,
+ Constants.BuildInfo.SourceBranch);
// This should prevent a certain someone from removing HidSharp as an unused dependency as well
_logger.Information("Forcing plugins to use HidSharp {hidSharpVersion}", Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version);
diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
index cd544f926..03aa1dd75 100644
--- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
+++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj.DotSettings
@@ -15,4 +15,5 @@
True
True
True
+ True
True
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IMessageService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IMessageService.cs
new file mode 100644
index 000000000..3e86ada7a
--- /dev/null
+++ b/src/Artemis.UI.Shared/Services/Interfaces/IMessageService.cs
@@ -0,0 +1,115 @@
+using System;
+using MaterialDesignThemes.Wpf;
+
+namespace Artemis.UI.Shared.Services
+{
+ ///
+ /// Providers messaging functionality
+ ///
+ public interface IMessageService : IArtemisSharedUIService
+ {
+ ///
+ /// Gets the main snackbar message queue used by and its overloads
+ ///
+ ISnackbarMessageQueue MainMessageQueue { get; }
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ void ShowMessage(object content);
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ /// Content for the action button.
+ /// Call back to be executed if user clicks the action button.
+ void ShowMessage(object content, object actionContent, Action actionHandler);
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ /// Content for the action button.
+ /// Call back to be executed if user clicks the action button.
+ /// Argument to pass to .
+ void ShowMessage(
+ object content,
+ object actionContent,
+ Action actionHandler,
+ TArgument actionArgument);
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ ///
+ /// Subsequent, duplicate messages queued within a short time span will
+ /// be discarded. To override this behaviour and ensure the message always gets displayed set to true .
+ ///
+ void ShowMessage(object content, bool neverConsiderToBeDuplicate);
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ /// Content for the action button.
+ /// Call back to be executed if user clicks the action button.
+ /// The message will promoted to the front of the queue.
+ void ShowMessage(object content, object actionContent, Action actionHandler, bool promote);
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ /// Content for the action button.
+ /// Call back to be executed if user clicks the action button.
+ /// Argument to pass to .
+ /// The message will be promoted to the front of the queue and never considered to be a duplicate.
+ void ShowMessage(
+ object content,
+ object actionContent,
+ Action actionHandler,
+ TArgument actionArgument,
+ bool promote);
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ /// Content for the action button.
+ /// Call back to be executed if user clicks the action button.
+ /// Argument to pass to .
+ /// The message will be promoted to the front of the queue.
+ /// The message will never be considered a duplicate.
+ /// Message show duration override.
+ void ShowMessage(
+ object content,
+ object actionContent,
+ Action actionHandler,
+ TArgument actionArgument,
+ bool promote,
+ bool neverConsiderToBeDuplicate,
+ TimeSpan? durationOverride = null);
+
+ ///
+ /// Queues a notification message for display in a snackbar.
+ ///
+ /// Message.
+ /// Content for the action button.
+ /// Call back to be executed if user clicks the action button.
+ /// Argument to pass to .
+ /// The message will promoted to the front of the queue.
+ /// The message will never be considered a duplicate.
+ /// Message show duration override.
+ void ShowMessage(
+ object content,
+ object actionContent,
+ Action actionHandler,
+ object actionArgument,
+ bool promote,
+ bool neverConsiderToBeDuplicate,
+ TimeSpan? durationOverride = null);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/MessageService.cs b/src/Artemis.UI.Shared/Services/MessageService.cs
new file mode 100644
index 000000000..3a3344a8c
--- /dev/null
+++ b/src/Artemis.UI.Shared/Services/MessageService.cs
@@ -0,0 +1,67 @@
+using System;
+using MaterialDesignThemes.Wpf;
+
+namespace Artemis.UI.Shared.Services
+{
+ internal class MessageService : IMessageService
+ {
+ public ISnackbarMessageQueue MainMessageQueue { get; }
+
+ public MessageService(ISnackbarMessageQueue mainMessageQueue)
+ {
+ MainMessageQueue = mainMessageQueue;
+ }
+
+ public void ShowMessage(object content)
+ {
+ MainMessageQueue.Enqueue(content);
+ }
+
+ public void ShowMessage(object content, object actionContent, Action actionHandler)
+ {
+ MainMessageQueue.Enqueue(content, actionContent, actionHandler);
+ }
+
+ public void ShowMessage(object content, object actionContent, Action actionHandler, TArgument actionArgument)
+ {
+ MainMessageQueue.Enqueue(content, actionContent, actionHandler, actionArgument);
+ }
+
+ public void ShowMessage(object content, bool neverConsiderToBeDuplicate)
+ {
+ MainMessageQueue.Enqueue(content, neverConsiderToBeDuplicate);
+ }
+
+ public void ShowMessage(object content, object actionContent, Action actionHandler, bool promote)
+ {
+ MainMessageQueue.Enqueue(content, actionContent, actionHandler, promote);
+ }
+
+ public void ShowMessage(object content, object actionContent, Action actionHandler, TArgument actionArgument, bool promote)
+ {
+ MainMessageQueue.Enqueue(content, actionContent, actionHandler, actionArgument, promote);
+ }
+
+ public void ShowMessage(object content,
+ object actionContent,
+ Action actionHandler,
+ TArgument actionArgument,
+ bool promote,
+ bool neverConsiderToBeDuplicate,
+ TimeSpan? durationOverride = null)
+ {
+ MainMessageQueue.Enqueue(content, actionContent, actionHandler, actionArgument, promote, neverConsiderToBeDuplicate, durationOverride);
+ }
+
+ public void ShowMessage(object content,
+ object actionContent,
+ Action actionHandler,
+ object actionArgument,
+ bool promote,
+ bool neverConsiderToBeDuplicate,
+ TimeSpan? durationOverride = null)
+ {
+ MainMessageQueue.Enqueue(content, actionContent, actionHandler, actionArgument, promote, neverConsiderToBeDuplicate, durationOverride);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/Window/IMainWindowManager.cs b/src/Artemis.UI.Shared/Services/Window/IMainWindowManager.cs
new file mode 100644
index 000000000..eea24b7fb
--- /dev/null
+++ b/src/Artemis.UI.Shared/Services/Window/IMainWindowManager.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Artemis.UI.Shared.Services
+{
+ ///
+ /// Represents a class that manages a main window, used by the to control the state of
+ /// the main window.
+ ///
+ public interface IMainWindowManager
+ {
+ ///
+ /// Gets a boolean indicating whether the main window is currently open
+ ///
+ bool IsMainWindowOpen { get; }
+
+ ///
+ /// Opens the main window
+ ///
+ ///
+ bool OpenMainWindow();
+
+ ///
+ /// Closes the main window
+ ///
+ ///
+ bool CloseMainWindow();
+
+ ///
+ /// Occurs when the main window has been opened
+ ///
+ public event EventHandler? MainWindowOpened;
+
+ ///
+ /// Occurs when the main window has been closed
+ ///
+ public event EventHandler? MainWindowClosed;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/Window/IWindowService.cs b/src/Artemis.UI.Shared/Services/Window/IWindowService.cs
new file mode 100644
index 000000000..ed87006a8
--- /dev/null
+++ b/src/Artemis.UI.Shared/Services/Window/IWindowService.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace Artemis.UI.Shared.Services
+{
+ ///
+ /// A service that allows you to view, monitor and manage the open/close state of the main window
+ ///
+ public interface IWindowService : IArtemisSharedUIService
+ {
+ ///
+ /// Gets a boolean indicating whether the main window is currently open
+ ///
+ bool IsMainWindowOpen { get; }
+
+ ///
+ /// Sets up the main window manager that controls the state of the main window
+ ///
+ /// The main window manager to use to control the state of the main window
+ void ConfigureMainWindowManager(IMainWindowManager mainWindowManager);
+
+ ///
+ /// Opens the main window if it is not already open
+ ///
+ void OpenMainWindow();
+
+ ///
+ /// Closes the main window if it is not already closed
+ ///
+ void CloseMainWindow();
+
+ ///
+ /// Occurs when the main window has been opened
+ ///
+ public event EventHandler? MainWindowOpened;
+
+ ///
+ /// Occurs when the main window has been closed
+ ///
+ public event EventHandler? MainWindowClosed;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/Window/WindowService.cs b/src/Artemis.UI.Shared/Services/Window/WindowService.cs
new file mode 100644
index 000000000..60b425089
--- /dev/null
+++ b/src/Artemis.UI.Shared/Services/Window/WindowService.cs
@@ -0,0 +1,82 @@
+using System;
+
+namespace Artemis.UI.Shared.Services
+{
+ internal class WindowService : IWindowService
+ {
+ private IMainWindowManager? _mainWindowManager;
+
+ protected virtual void OnMainWindowOpened()
+ {
+ MainWindowOpened?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnMainWindowClosed()
+ {
+ MainWindowClosed?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void SyncWithManager()
+ {
+ if (_mainWindowManager == null)
+ return;
+
+ if (IsMainWindowOpen && !_mainWindowManager.IsMainWindowOpen)
+ {
+ IsMainWindowOpen = false;
+ OnMainWindowClosed();
+ }
+
+ if (!IsMainWindowOpen && _mainWindowManager.IsMainWindowOpen)
+ {
+ IsMainWindowOpen = true;
+ OnMainWindowOpened();
+ }
+ }
+
+ private void HandleMainWindowOpened(object? sender, EventArgs e)
+ {
+ SyncWithManager();
+ }
+
+ private void HandleMainWindowClosed(object? sender, EventArgs e)
+ {
+ SyncWithManager();
+ }
+
+ public bool IsMainWindowOpen { get; private set; }
+
+ public void ConfigureMainWindowManager(IMainWindowManager mainWindowManager)
+ {
+ if (mainWindowManager == null) throw new ArgumentNullException(nameof(mainWindowManager));
+
+ if (_mainWindowManager != null)
+ {
+ _mainWindowManager.MainWindowOpened -= HandleMainWindowOpened;
+ _mainWindowManager.MainWindowClosed -= HandleMainWindowClosed;
+ }
+
+ _mainWindowManager = mainWindowManager;
+ _mainWindowManager.MainWindowOpened += HandleMainWindowOpened;
+ _mainWindowManager.MainWindowClosed += HandleMainWindowClosed;
+
+ // Sync up with the new manager's state
+ SyncWithManager();
+ }
+
+ public void OpenMainWindow()
+ {
+ IsMainWindowOpen = true;
+ OnMainWindowOpened();
+ }
+
+ public void CloseMainWindow()
+ {
+ IsMainWindowOpen = false;
+ OnMainWindowClosed();
+ }
+
+ public event EventHandler? MainWindowOpened;
+ public event EventHandler? MainWindowClosed;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileExportViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileExportViewModel.cs
index a85b85046..7ab085f39 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileExportViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileExportViewModel.cs
@@ -8,16 +8,15 @@ namespace Artemis.UI.Screens.ProfileEditor.Dialogs
{
public class ProfileExportViewModel : DialogViewModelBase
{
- private readonly ISnackbarMessageQueue _mainMessageQueue;
-
private readonly IProfileService _profileService;
+ private readonly IMessageService _messageService;
- public ProfileExportViewModel(ProfileDescriptor profileDescriptor, IProfileService profileService, ISnackbarMessageQueue mainMessageQueue)
+ public ProfileExportViewModel(ProfileDescriptor profileDescriptor, IProfileService profileService, IMessageService messageService)
{
ProfileDescriptor = profileDescriptor;
_profileService = profileService;
- _mainMessageQueue = mainMessageQueue;
+ _messageService = messageService;
}
public ProfileDescriptor ProfileDescriptor { get; }
@@ -26,7 +25,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Dialogs
{
string encoded = _profileService.ExportProfile(ProfileDescriptor);
Clipboard.SetText(encoded);
- _mainMessageQueue.Enqueue("Profile contents exported to clipboard.");
+ _messageService.ShowMessage("Profile contents exported to clipboard.");
Session.Close();
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileImportViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileImportViewModel.cs
index cab90345a..ef89cef72 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileImportViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Dialogs/ProfileImportViewModel.cs
@@ -11,15 +11,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Dialogs
{
private readonly ISnackbarMessageQueue _mainMessageQueue;
private readonly IProfileService _profileService;
+ private readonly IMessageService _messageService;
private string _profileJson;
- public ProfileImportViewModel(ProfileModule profileModule, IProfileService profileService, ISnackbarMessageQueue mainMessageQueue)
+ public ProfileImportViewModel(ProfileModule profileModule, IProfileService profileService, IMessageService messageService)
{
ProfileModule = profileModule;
Document = new TextDocument();
_profileService = profileService;
- _mainMessageQueue = mainMessageQueue;
+ _messageService = messageService;
}
public ProfileModule ProfileModule { get; }
@@ -34,7 +35,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Dialogs
public void Accept()
{
ProfileDescriptor descriptor = _profileService.ImportProfile(Document.Text, ProfileModule);
- _mainMessageQueue.Enqueue("Profile imported.");
+ _messageService.ShowMessage("Profile imported.");
Session.Close(descriptor);
}
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
index d5d43ed53..23a9176b4 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs
@@ -22,10 +22,10 @@ namespace Artemis.UI.Screens.ProfileEditor
public class ProfileEditorViewModel : Screen
{
private readonly IModuleService _moduleService;
+ private readonly IMessageService _messageService;
private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService;
- private readonly ISnackbarMessageQueue _snackbarMessageQueue;
private PluginSetting _bottomPanelsHeight;
private PluginSetting _dataModelConditionsHeight;
private DisplayConditionsViewModel _displayConditionsViewModel;
@@ -47,13 +47,13 @@ namespace Artemis.UI.Screens.ProfileEditor
IDialogService dialogService,
ISettingsService settingsService,
IModuleService moduleService,
- ISnackbarMessageQueue snackbarMessageQueue)
+ IMessageService messageService)
{
_profileEditorService = profileEditorService;
_profileService = profileService;
_settingsService = settingsService;
_moduleService = moduleService;
- _snackbarMessageQueue = snackbarMessageQueue;
+ _messageService = messageService;
DisplayName = "PROFILE EDITOR";
Module = module;
@@ -242,7 +242,7 @@ namespace Artemis.UI.Screens.ProfileEditor
if (!_profileEditorService.UndoUpdateProfile())
{
- _snackbarMessageQueue.Enqueue("Nothing to undo");
+ _messageService.ShowMessage("Nothing to undo");
return;
}
@@ -256,7 +256,7 @@ namespace Artemis.UI.Screens.ProfileEditor
focusedElement?.Focus();
});
- _snackbarMessageQueue.Enqueue("Undid profile update", "REDO", Redo);
+ _messageService.ShowMessage("Undid profile update", "REDO", Redo);
}
public void Redo()
@@ -269,7 +269,7 @@ namespace Artemis.UI.Screens.ProfileEditor
if (!_profileEditorService.RedoUpdateProfile())
{
- _snackbarMessageQueue.Enqueue("Nothing to redo");
+ _messageService.ShowMessage("Nothing to redo");
return;
}
@@ -283,7 +283,7 @@ namespace Artemis.UI.Screens.ProfileEditor
focusedElement?.Focus();
});
- _snackbarMessageQueue.Enqueue("Redid profile update", "UNDO", Undo);
+ _messageService.ShowMessage("Redid profile update", "UNDO", Undo);
}
protected override void OnInitialActivate()
diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs
index 05b57b22c..d2b5db909 100644
--- a/src/Artemis.UI/Screens/RootViewModel.cs
+++ b/src/Artemis.UI/Screens/RootViewModel.cs
@@ -14,6 +14,7 @@ using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services;
using Artemis.UI.Services.Interfaces;
+using Artemis.UI.Shared.Services;
using Artemis.UI.Utilities;
using MaterialDesignExtensions.Controls;
using MaterialDesignThemes.Wpf;
@@ -25,6 +26,7 @@ namespace Artemis.UI.Screens
public sealed class RootViewModel : Conductor, IDisposable
{
private readonly IRegistrationService _builtInRegistrationService;
+ private readonly IMessageService _messageService;
private readonly PluginSetting _colorScheme;
private readonly ICoreService _coreService;
private readonly IWindowManager _windowManager;
@@ -34,7 +36,6 @@ namespace Artemis.UI.Screens
private readonly ISettingsService _settingsService;
private readonly Timer _frameTimeUpdateTimer;
private readonly SidebarViewModel _sidebarViewModel;
- private readonly ISnackbarMessageQueue _snackbarMessageQueue;
private readonly ThemeWatcher _themeWatcher;
private readonly PluginSetting _windowSize;
private bool _activeItemReady;
@@ -52,7 +53,7 @@ namespace Artemis.UI.Screens
IWindowManager windowManager,
IDebugService debugService,
IRegistrationService builtInRegistrationService,
- ISnackbarMessageQueue snackbarMessageQueue,
+ IMessageService messageService,
SidebarViewModel sidebarViewModel)
{
_kernel = kernel;
@@ -62,7 +63,7 @@ namespace Artemis.UI.Screens
_windowManager = windowManager;
_debugService = debugService;
_builtInRegistrationService = builtInRegistrationService;
- _snackbarMessageQueue = snackbarMessageQueue;
+ _messageService = messageService;
_sidebarViewModel = sidebarViewModel;
_frameTimeUpdateTimer = new Timer(500);
@@ -79,7 +80,7 @@ namespace Artemis.UI.Screens
PinSidebar = _settingsService.GetSetting("UI.PinSidebar", false);
AssemblyInformationalVersionAttribute versionAttribute = typeof(RootViewModel).Assembly.GetCustomAttribute();
- WindowTitle = $"Artemis {versionAttribute?.InformationalVersion}";
+ WindowTitle = $"Artemis {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumber}";
}
public PluginSetting PinSidebar { get; }
@@ -275,7 +276,7 @@ namespace Artemis.UI.Screens
protected override void OnInitialActivate()
{
- MainMessageQueue = _snackbarMessageQueue;
+ MainMessageQueue = _messageService.MainMessageQueue;
UpdateFrameTime();
_builtInRegistrationService.RegisterBuiltInDataModelDisplays();
@@ -295,7 +296,7 @@ namespace Artemis.UI.Screens
PluginSetting setupWizardCompleted = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
if (!setupWizardCompleted.Value)
ShowSetupWizard();
-
+
base.OnInitialActivate();
}
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml
index e02dbda31..ac964893d 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml
+++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml
@@ -195,6 +195,76 @@
+
+ Updating
+
+
+
+
+
+
+
+
+
+
+
+
+ Check for updates
+
+ If enabled, we'll check for updates on startup and periodically while running.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Automatically install updates
+
+ If enabled updates are installed automatically without asking first.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Update
+
+ Use the button on the right to check for updates now.
+
+
+
+
+ CHECK NOW
+
+
+
+
+
+
Profile editor
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs
index 02bf14207..d076f56e3 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs
@@ -8,9 +8,11 @@ using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard;
+using Artemis.UI.Services;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
+using MaterialDesignThemes.Wpf;
using Ninject;
using Serilog.Events;
using Stylet;
@@ -24,18 +26,23 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
private readonly IWindowManager _windowManager;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
+ private readonly IUpdateService _updateService;
+ private readonly IMessageService _messageService;
private List> _renderScales;
private List _sampleSizes;
private List> _targetFrameRates;
private readonly PluginSetting _defaultLayerBrushDescriptor;
+ private bool _canOfferUpdatesIfFound = true;
public GeneralSettingsTabViewModel(
- IKernel kernel,
- IWindowManager windowManager,
- IDialogService dialogService,
+ IKernel kernel,
+ IWindowManager windowManager,
+ IDialogService dialogService,
IDebugService debugService,
- ISettingsService settingsService,
- IPluginManagementService pluginManagementService)
+ ISettingsService settingsService,
+ IUpdateService updateService,
+ IPluginManagementService pluginManagementService,
+ IMessageService messageService)
{
DisplayName = "GENERAL";
@@ -44,6 +51,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
_dialogService = dialogService;
_debugService = debugService;
_settingsService = settingsService;
+ _updateService = updateService;
+ _messageService = messageService;
LogLevels = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel)));
ColorSchemes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(ApplicationColorScheme)));
@@ -124,6 +133,31 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
}
}
+ public bool CheckForUpdates
+ {
+ get => _settingsService.GetSetting("UI.CheckForUpdates", true).Value;
+ set
+ {
+ _settingsService.GetSetting("UI.CheckForUpdates", true).Value = value;
+ _settingsService.GetSetting("UI.CheckForUpdates", true).Save();
+ NotifyOfPropertyChange(nameof(CheckForUpdates));
+
+ if (!value)
+ AutoInstallUpdates = false;
+ }
+ }
+
+ public bool AutoInstallUpdates
+ {
+ get => _settingsService.GetSetting("UI.AutoInstallUpdates", false).Value;
+ set
+ {
+ _settingsService.GetSetting("UI.AutoInstallUpdates", false).Value = value;
+ _settingsService.GetSetting("UI.AutoInstallUpdates", false).Save();
+ NotifyOfPropertyChange(nameof(AutoInstallUpdates));
+ }
+ }
+
public bool ShowDataModelValues
{
get => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false).Value;
@@ -196,6 +230,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
}
}
+ public bool CanOfferUpdatesIfFound
+ {
+ get => _canOfferUpdatesIfFound;
+ set => SetAndNotify(ref _canOfferUpdatesIfFound, value);
+ }
+
public void ShowDebugger()
{
_debugService.ShowDebugger();
@@ -230,6 +270,20 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
}
}
+ public async void OfferUpdatesIfFound()
+ {
+ if (!CanOfferUpdatesIfFound)
+ return;
+
+ CanOfferUpdatesIfFound = false;
+ bool updateFound = await _updateService.OfferUpdatesIfFound();
+ if (!updateFound)
+ _messageService.ShowMessage("You are already running the latest Artemis build. (☞゚ヮ゚)☞");
+ else
+ _messageService.ShowMessage("You are already running the latest Artemis build. (☞゚ヮ゚)☞");
+ CanOfferUpdatesIfFound = true;
+ }
+
protected override void OnInitialActivate()
{
Task.Run(ApplyAutorun);
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs
index 46a7d04e3..86a0180b0 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs
@@ -20,17 +20,17 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService;
- private readonly ISnackbarMessageQueue _snackbarMessageQueue;
+ private IMessageService _messageService;
private bool _enabling;
-
+
public PluginFeatureViewModel(PluginFeature feature,
IDialogService dialogService,
IPluginManagementService pluginManagementService,
- ISnackbarMessageQueue snackbarMessageQueue)
+ IMessageService messageService)
{
_dialogService = dialogService;
_pluginManagementService = pluginManagementService;
- _snackbarMessageQueue = snackbarMessageQueue;
+ _messageService = messageService;
Feature = feature;
Icon = GetIconKind();
@@ -109,7 +109,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
}
catch (Exception e)
{
- _snackbarMessageQueue.Enqueue($"Failed to enable {Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
+ _messageService.ShowMessage($"Failed to enable {Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
}
finally
{
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs
index 3d75bc9f6..202b1dea8 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs
@@ -19,7 +19,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
- private readonly ISnackbarMessageQueue _snackbarMessageQueue;
+ private readonly IMessageService _messageService;
private readonly IWindowManager _windowManager;
private bool _enabling;
private Plugin _plugin;
@@ -29,7 +29,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
IWindowManager windowManager,
IDialogService dialogService,
IPluginManagementService pluginManagementService,
- ISnackbarMessageQueue snackbarMessageQueue)
+ IMessageService messageService)
{
Plugin = plugin;
@@ -37,7 +37,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
_windowManager = windowManager;
_dialogService = dialogService;
_pluginManagementService = pluginManagementService;
- _snackbarMessageQueue = snackbarMessageQueue;
+ _messageService = messageService;
Icon = PluginUtilities.GetPluginIcon(Plugin, Plugin.Info.Icon);
}
@@ -130,7 +130,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
}
catch (Exception e)
{
- _snackbarMessageQueue.Enqueue($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
+ _messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
}
finally
{
diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs
index cf75ee65b..a3c7d41f9 100644
--- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs
+++ b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceDetectInputViewModel.cs
@@ -13,17 +13,17 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
public class SurfaceDeviceDetectInputViewModel : DialogViewModelBase
{
private readonly IInputService _inputService;
+ private readonly IMessageService _messageService;
private readonly ListLedGroup _ledGroup;
- private readonly ISnackbarMessageQueue _mainMessageQueue;
- public SurfaceDeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, ISnackbarMessageQueue mainMessageQueue)
+ public SurfaceDeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, IMessageService messageService)
{
Device = device;
Title = $"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input";
IsMouse = Device.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse;
_inputService = inputService;
- _mainMessageQueue = mainMessageQueue;
+ _messageService = messageService;
_inputService.IdentifyDevice(Device);
_inputService.DeviceIdentified += InputServiceOnDeviceIdentified;
@@ -49,7 +49,7 @@ namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
private void InputServiceOnDeviceIdentified(object sender, EventArgs e)
{
Session?.Close(true);
- _mainMessageQueue.Enqueue($"{Device.RgbDevice.DeviceInfo.DeviceName} identified 😁");
+ _messageService.ShowMessage($"{Device.RgbDevice.DeviceInfo.DeviceName} identified 😁");
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs
index a6c2386d1..a513caa6f 100644
--- a/src/Artemis.UI/Screens/TrayViewModel.cs
+++ b/src/Artemis.UI/Screens/TrayViewModel.cs
@@ -1,13 +1,16 @@
-using Artemis.Core.Services;
+using System;
+using Artemis.Core.Services;
using Artemis.UI.Events;
using Artemis.UI.Screens.Splash;
+using Artemis.UI.Services;
using Artemis.UI.Services.Interfaces;
+using Artemis.UI.Shared.Services;
using Ninject;
using Stylet;
namespace Artemis.UI.Screens
{
- public class TrayViewModel : Screen
+ public class TrayViewModel : Screen, IMainWindowManager
{
private readonly IDebugService _debugService;
private readonly IEventAggregator _eventAggregator;
@@ -15,8 +18,16 @@ namespace Artemis.UI.Screens
private readonly IWindowManager _windowManager;
private bool _canShowRootViewModel;
private SplashViewModel _splashViewModel;
+ private RootViewModel _rootViewModel;
- public TrayViewModel(IKernel kernel, IWindowManager windowManager, IEventAggregator eventAggregator, ICoreService coreService, IDebugService debugService, ISettingsService settingsService)
+ public TrayViewModel(IKernel kernel,
+ IWindowManager windowManager,
+ IWindowService windowService,
+ IUpdateService updateService,
+ IEventAggregator eventAggregator,
+ ICoreService coreService,
+ IDebugService debugService,
+ ISettingsService settingsService)
{
_kernel = kernel;
_windowManager = windowManager;
@@ -24,13 +35,16 @@ namespace Artemis.UI.Screens
_debugService = debugService;
CanShowRootViewModel = true;
+ windowService.ConfigureMainWindowManager(this);
bool autoRunning = Bootstrapper.StartupArguments.Contains("--autorun");
bool showOnAutoRun = settingsService.GetSetting("UI.ShowOnStartup", true).Value;
if (!autoRunning || showOnAutoRun)
{
ShowSplashScreen();
- coreService.Initialized += (sender, args) => TrayBringToForeground();
+ coreService.Initialized += (_, _) => TrayBringToForeground();
}
+
+ updateService.AutoUpdate();
}
public bool CanShowRootViewModel
@@ -53,10 +67,12 @@ namespace Artemis.UI.Screens
{
_splashViewModel?.RequestClose();
_splashViewModel = null;
- RootViewModel rootViewModel = _kernel.Get();
- rootViewModel.Closed += RootViewModelOnClosed;
- _windowManager.ShowWindow(rootViewModel);
+ _rootViewModel = _kernel.Get();
+ _rootViewModel.Closed += RootViewModelOnClosed;
+ _windowManager.ShowWindow(_rootViewModel);
});
+
+ OnMainWindowOpened();
}
public void TrayActivateSidebarItem(string sidebarItem)
@@ -86,7 +102,53 @@ namespace Artemis.UI.Screens
private void RootViewModelOnClosed(object sender, CloseEventArgs e)
{
+ _rootViewModel.Closed -= RootViewModelOnClosed;
+ _rootViewModel = null;
+
CanShowRootViewModel = true;
+ OnMainWindowClosed();
}
+
+ #region Implementation of IMainWindowManager
+
+ ///
+ public bool IsMainWindowOpen { get; private set; }
+
+ ///
+ public bool OpenMainWindow()
+ {
+ if (CanShowRootViewModel)
+ return false;
+
+ TrayBringToForeground();
+ return true;
+ }
+
+ ///
+ public bool CloseMainWindow()
+ {
+ _rootViewModel.RequestClose();
+ return _rootViewModel.ScreenState == ScreenState.Closed;
+ }
+
+ ///
+ public event EventHandler MainWindowOpened;
+
+ ///
+ public event EventHandler MainWindowClosed;
+
+ protected virtual void OnMainWindowOpened()
+ {
+ IsMainWindowOpen = true;
+ MainWindowOpened?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnMainWindowClosed()
+ {
+ IsMainWindowOpen = false;
+ MainWindowClosed?.Invoke(this, EventArgs.Empty);
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/UpdateService.cs b/src/Artemis.UI/Services/UpdateService.cs
new file mode 100644
index 000000000..d29633ec8
--- /dev/null
+++ b/src/Artemis.UI/Services/UpdateService.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Artemis.Core;
+using Artemis.Core.Services;
+using Artemis.UI.Services.Interfaces;
+using Artemis.UI.Shared.Services;
+using Newtonsoft.Json.Linq;
+using Serilog;
+
+namespace Artemis.UI.Services
+{
+ public class UpdateService : IUpdateService
+ {
+ private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/";
+ private readonly PluginSetting _autoInstallUpdates;
+ private readonly PluginSetting _checkForUpdates;
+ private readonly ILogger _logger;
+ private readonly IDialogService _dialogService;
+ private readonly IWindowService _windowService;
+
+ public UpdateService(ILogger logger, ISettingsService settingsService, IDialogService dialogService, IWindowService windowService)
+ {
+ _logger = logger;
+ _dialogService = dialogService;
+ _windowService = windowService;
+ _windowService.MainWindowOpened += WindowServiceOnMainWindowOpened;
+
+ _checkForUpdates = settingsService.GetSetting("UI.CheckForUpdates", true);
+ _autoInstallUpdates = settingsService.GetSetting("UI.AutoInstallUpdates", false);
+
+ _checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged;
+ }
+
+ public async Task GetLatestBuildNumber()
+ {
+ // TODO: The URL is hardcoded, that should change in the future
+ string latestBuildUrl = ApiUrl + "build/builds?api-version=6.1-preview.6&branchName=refs/heads/master&resultFilter=succeeded&$top=1";
+ _logger.Debug("Getting latest build number from {latestBuildUrl}", latestBuildUrl);
+
+ // Make the request
+ using HttpClient client = new();
+ HttpResponseMessage httpResponseMessage = await client.GetAsync(latestBuildUrl);
+
+ // Ensure it returned correctly
+ if (!httpResponseMessage.IsSuccessStatusCode)
+ {
+ _logger.Warning("Failed to check for updates, request returned {statusCode}", httpResponseMessage.StatusCode);
+ return 0;
+ }
+
+ // Parse the response
+ string response = await httpResponseMessage.Content.ReadAsStringAsync();
+ try
+ {
+ JToken buildNumberToken = JObject.Parse(response).SelectToken("value[0].buildNumber");
+ if (buildNumberToken != null)
+ return buildNumberToken.Value();
+
+ _logger.Warning("Failed to find build number at \"value[0].buildNumber\"");
+ return 0;
+
+ }
+ catch (Exception e)
+ {
+ _logger.Warning(e, "Failed to retrieve build info JSON");
+ return 0;
+ }
+ }
+
+ public async Task OfferUpdatesIfFound()
+ {
+ _logger.Information("Checking for updates");
+
+ double buildNumber = await GetLatestBuildNumber();
+ _logger.Information("Latest build is {buildNumber}, we're running {localBuildNumber}", buildNumber, Constants.BuildInfo.BuildNumber);
+
+ if (buildNumber < Constants.BuildInfo.BuildNumber)
+ return false;
+
+ if (_windowService.IsMainWindowOpen)
+ {
+
+ }
+ else
+ {
+
+ }
+
+ return true;
+ }
+
+ public async Task IsUpdateAvailable()
+ {
+ double buildNumber = await GetLatestBuildNumber();
+ return buildNumber > Constants.BuildInfo.BuildNumber;
+ }
+
+ public void ApplyUpdate()
+ {
+ throw new NotImplementedException();
+ }
+
+ public async Task AutoUpdate()
+ {
+ if (!_checkForUpdates.Value)
+ return false;
+
+ return await OfferUpdatesIfFound();
+ }
+
+ #region Event handlers
+
+ private void CheckForUpdatesOnSettingChanged(object sender, EventArgs e)
+ {
+ // Run an auto-update as soon as the setting gets changed to enabled
+ if (_checkForUpdates.Value)
+ AutoUpdate();
+ }
+
+ private void WindowServiceOnMainWindowOpened(object? sender, EventArgs e)
+ {
+ _logger.Information("Main window opened!");
+ }
+
+ #endregion
+ }
+
+ public interface IUpdateService : IArtemisUIService
+ {
+ Task OfferUpdatesIfFound();
+ Task IsUpdateAvailable();
+ void ApplyUpdate();
+
+ ///
+ /// If auto-update is enabled this will offer updates if found
+ ///
+ Task AutoUpdate();
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/buildinfo.json b/src/Artemis.UI/buildinfo.json
index 248b88208..e5db2c3c5 100644
--- a/src/Artemis.UI/buildinfo.json
+++ b/src/Artemis.UI/buildinfo.json
@@ -1,6 +1,6 @@
{
"BuildId": 0,
"BuildNumber": 0,
- "SourceBranch": "",
- "SourceVersion": ""
+ "SourceBranch": "local",
+ "SourceVersion": "local"
}
\ No newline at end of file