diff --git a/RepoM.sln.DotSettings b/RepoM.sln.DotSettings index 4ae60fb1..024a8c7f 100644 --- a/RepoM.sln.DotSettings +++ b/RepoM.sln.DotSettings @@ -1,6 +1,7 @@  True True + True True True True @@ -11,5 +12,6 @@ True True True + True True True \ No newline at end of file diff --git a/src/RepoM.Api/Git/DefaultRepositoryInformationAggregator.cs b/src/RepoM.Api/Git/DefaultRepositoryInformationAggregator.cs index 703b013d..2913c523 100644 --- a/src/RepoM.Api/Git/DefaultRepositoryInformationAggregator.cs +++ b/src/RepoM.Api/Git/DefaultRepositoryInformationAggregator.cs @@ -28,6 +28,7 @@ public void Add(IRepository repository, IRepositoryMonitor repositoryMonitor) throw new NotImplementedException("We expect a Repository object."); } + // TODO: crashes here _dispatcher.Invoke(() => { var view = new RepositoryViewModel(repo, repositoryMonitor); diff --git a/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs b/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs index 8dc4f26d..786eebc0 100644 --- a/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs +++ b/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs @@ -97,7 +97,7 @@ private void ScanRepositoriesFromStoreAsync() foreach (var head in _repositoryStore.Get()) { _logger.LogDebug("{Method} - repo {Head}", nameof(ScanRepositoriesFromStoreAsync), head); - OnCheckKnownRepository(head, KnownRepositoryNotifications.WhenFound); + _ = OnCheckKnownRepository(head, KnownRepositoryNotifications.WhenFound); } }); } @@ -198,6 +198,7 @@ public void Reset() { Stop(); + // TODO: this is not thread safe. Needs urgent fixing foreach (IRepositoryObserver observer in _repositoryObservers.Values) { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract @@ -261,6 +262,7 @@ private void CreateRepositoryObserver(IRepository repo, string path) if (!_repositoryObservers.TryAdd(path, observer)) { + // TODO NOT THREAD SAFE observer.Dispose(); return; } @@ -304,7 +306,7 @@ private void OnRepositoryChangeDetected(IRepository repo) private void OnRepositoryObserverChange(IRepository repository) { _logger.LogDebug("{Method} - repo {Path}", nameof(OnRepositoryObserverChange), repository.Path); - OnCheckKnownRepository(repository.Path, KnownRepositoryNotifications.WhenFound | KnownRepositoryNotifications.WhenNotFound); + _ = OnCheckKnownRepository(repository.Path, KnownRepositoryNotifications.WhenFound | KnownRepositoryNotifications.WhenNotFound); } private void DestroyRepositoryObserver(string path) diff --git a/src/RepoM.App/ActionMenuCore/RepositoryTagsFactoryV2.cs b/src/RepoM.App/ActionMenuCore/RepositoryTagsFactoryV2.cs index 1bcb1a9d..d3ac879b 100644 --- a/src/RepoM.App/ActionMenuCore/RepositoryTagsFactoryV2.cs +++ b/src/RepoM.App/ActionMenuCore/RepositoryTagsFactoryV2.cs @@ -27,8 +27,5 @@ public RepositoryTagsFactoryV2( _filename = fileSystem.Path.Combine(appDataPathProvider.AppDataPath, "TagsV2.yaml"); } - public Task> GetTagsAsync(Repository repository) - { - return _newStyleActionMenuFactory.GetTagsAsync(repository, _filename); - } -} \ No newline at end of file + public Task> GetTagsAsync(Repository repository) => _newStyleActionMenuFactory.GetTagsAsync(repository,_filename); +} diff --git a/src/RepoM.App/App.xaml b/src/RepoM.App/App.xaml index d47a215c..91a24b3c 100644 --- a/src/RepoM.App/App.xaml +++ b/src/RepoM.App/App.xaml @@ -1,109 +1,54 @@ - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RepoM.App/App.xaml.cs b/src/RepoM.App/App.xaml.cs index c8770aaf..42e7eba0 100644 --- a/src/RepoM.App/App.xaml.cs +++ b/src/RepoM.App/App.xaml.cs @@ -2,39 +2,70 @@ namespace RepoM.App; +using Application = System.Windows.Application; +using Container = SimpleInjector.Container; +using ILogger = Microsoft.Extensions.Logging.ILogger; using System; +using System.Globalization; using System.IO; using System.IO.Abstractions; using System.Threading; using System.Windows; +using System.Windows.Markup; using Hardcodet.Wpf.TaskbarNotification; -using RepoM.Api.Git; -using RepoM.Api.IO; -using RepoM.App.i18n; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using RepoM.Api; +using RepoM.Api.Git; +using RepoM.Api.IO; using RepoM.Api.Plugins; +using RepoM.App.i18n; using RepoM.App.Plugins; +using RepoM.App.Services; using Serilog; using Serilog.Core; -using ILogger = Microsoft.Extensions.Logging.ILogger; -using RepoM.App.Services; -using Container = SimpleInjector.Container; -using RepoM.App.Services.HotKey; -using RepoM.Api; +using SimpleInjector; +using HotKeyService = RepoM.App.Services.HotKeyService; /// -/// Interaction logic for App.xaml +/// Interaction logic for App.xaml /// public partial class App : Application { - private static Mutex? _mutex; + private static Mutex? _mutex; private static IRepositoryMonitor? _repositoryMonitor; - private TaskbarIcon? _notifyIcon; - private ModuleService? _moduleService; - private HotKeyService? _hotKeyService; + private static App? _app; + + private HotKeyService? _hotKeyService; + private Window? _mainWindow; + private ModuleService? _moduleService; + private TaskbarIcon? _notifyIcon; private WindowSizeService? _windowSizeService; + public static string? AvailableUpdate + { + // TODO: This does nothing. Fix it. + get; + } + + public Window? MainWindowInstance + { + get + { + return _app?.MainWindow ?? _mainWindow; + } + + set + { + if (_app != null) + { + _app.MainWindow = value; + } + + _mainWindow = value; + } + } + [STAThread] public static void Main() { @@ -44,9 +75,16 @@ public static void Main() } Thread.CurrentThread.Name ??= "UI"; - var app = new App(); - app.InitializeComponent(); - app.Run(); + _app = new App(); + _app.InitializeComponent(); + + /* + * Run is called to start an application. + * Set properties and attach events before calling Run. + * Once run has been called - an application's OnStartup event + * is called immediately afterwards. + */ + _app.Run(); } protected override async void OnStartup(StartupEventArgs e) @@ -57,39 +95,45 @@ protected override async void OnStartup(StartupEventArgs e) // By default, WPF uses en-US as the culture, regardless of the system settings. // see: https://stackoverflow.com/a/520334/704281 FrameworkElement.LanguageProperty.OverrideMetadata( - typeof(FrameworkElement), - new FrameworkPropertyMetadata(System.Windows.Markup.XmlLanguage.GetLanguage(System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag))); + typeof(FrameworkElement), + new FrameworkPropertyMetadata( + XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))); - Application.Current.Resources.MergedDictionaries[0] = ResourceDictionaryTranslationService.ResourceDictionary; - _notifyIcon = FindResource("NotifyIcon") as TaskbarIcon; + Current.Resources.MergedDictionaries[0] = ResourceDictionaryTranslationService.ResourceDictionary; + _notifyIcon = FindResource("NotifyIcon") as TaskbarIcon; var fileSystem = new FileSystem(); // Create instance without DI, because we need it before the last registration of services. IHmacService hmacService = new HmacSha256Service(); - IPluginFinder pluginFinder = new PluginFinder(fileSystem, hmacService); + IPluginFinder pluginFinder = new PluginFinder(fileSystem, + hmacService); - IConfiguration config = SetupConfiguration(); + IConfiguration config = SetupConfiguration(); ILoggerFactory loggerFactory = CreateLoggerFactory(config); - ILogger logger = loggerFactory.CreateLogger(nameof(App)); + ILogger logger = loggerFactory.CreateLogger(nameof(App)); logger.LogInformation("Started"); Bootstrapper.RegisterLogging(loggerFactory); Bootstrapper.RegisterServices(fileSystem); - await Bootstrapper.RegisterPlugins(pluginFinder, fileSystem, loggerFactory).ConfigureAwait(true); + await Bootstrapper.RegisterPlugins(pluginFinder, + fileSystem, + loggerFactory) + .ConfigureAwait(true); -#if DEBUG - Bootstrapper.Container.Verify(SimpleInjector.VerificationOption.VerifyAndDiagnose); -#else - Bootstrapper.Container.Options.EnableAutoVerification = false; -#endif + #if DEBUG + Bootstrapper.Container.Verify(VerificationOption.VerifyAndDiagnose); + #else + Bootstrapper.Container.Options.EnableAutoVerification = false; + #endif EnsureStartup ensureStartup = Bootstrapper.Container.GetInstance(); - await ensureStartup.EnsureFilesAsync().ConfigureAwait(true); - + await ensureStartup.EnsureFilesAsync() + .ConfigureAwait(true); + UseRepositoryMonitor(Bootstrapper.Container); - _moduleService = Bootstrapper.Container.GetInstance(); - _hotKeyService = Bootstrapper.Container.GetInstance(); + _moduleService = Bootstrapper.Container.GetInstance(); + _hotKeyService = Bootstrapper.Container.GetInstance(); _windowSizeService = Bootstrapper.Container.GetInstance(); _hotKeyService.Register(); @@ -97,25 +141,27 @@ protected override async void OnStartup(StartupEventArgs e) try { - await _moduleService.StartAsync().ConfigureAwait(false); // don't care about ui thread + await _moduleService.StartAsync() + .ConfigureAwait(false); // don't care about ui thread } catch (Exception exception) { - logger.LogError(exception, "Could not start all modules."); + logger.LogError(exception, + "Could not start all modules."); } } - + protected override void OnExit(ExitEventArgs e) { _windowSizeService?.Unregister(); - - _moduleService?.StopAsync().GetAwaiter().GetResult(); + + _moduleService?.StopAsync() + .GetAwaiter() + .GetResult(); _hotKeyService?.Unregister(); -// #pragma warning disable CA1416 // Validate platform compatibility _notifyIcon?.Dispose(); -// #pragma warning restore CA1416 // Validate platform compatibility ReleaseAndDisposeMutex(); @@ -125,12 +171,15 @@ protected override void OnExit(ExitEventArgs e) private static IConfiguration SetupConfiguration() { const string FILENAME = "appsettings.serilog.json"; - var fullFilename = Path.Combine(DefaultAppDataPathProvider.Instance.AppDataPath, FILENAME); + var fullFilename = Path.Combine(DefaultAppDataPathProvider.Instance.AppDataPath, + FILENAME); IConfigurationBuilder builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile(fullFilename, optional: true, reloadOnChange: false) - .AddEnvironmentVariables(); + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile(fullFilename, + true, + false) + .AddEnvironmentVariables(); return builder.Build(); } @@ -139,10 +188,11 @@ private static ILoggerFactory CreateLoggerFactory(IConfiguration config) ILoggerFactory loggerFactory = new LoggerFactory(); LoggerConfiguration loggerConfiguration = new LoggerConfiguration() - .Enrich.WithThreadId() - .Enrich.WithThreadName() - .Enrich.WithProperty("ThreadName", "BG") - .ReadFrom.Configuration(config); + .Enrich.WithThreadId() + .Enrich.WithThreadName() + .Enrich.WithProperty("ThreadName", + "BG") + .ReadFrom.Configuration(config); Logger logger = loggerConfiguration.CreateLogger(); @@ -161,7 +211,9 @@ private static bool IsAlreadyRunning() { try { - _mutex = new Mutex(true, "Local\\github.com/coenm/RepoM", out var createdNew); + _mutex = new Mutex(true, + "Local\\github.com/coenm/RepoM", + out var createdNew); if (createdNew) { @@ -198,6 +250,5 @@ private static void ReleaseAndDisposeMutex() // ignore } } +} - public static string? AvailableUpdate { get; private set; } = null; -} \ No newline at end of file diff --git a/src/RepoM.App/Bootstrapper.cs b/src/RepoM.App/Bootstrapper.cs index 5eb543ce..f691147b 100644 --- a/src/RepoM.App/Bootstrapper.cs +++ b/src/RepoM.App/Bootstrapper.cs @@ -1,22 +1,32 @@ namespace RepoM.App; +using System; +using System.IO.Abstractions; +using System.Runtime.Caching; +using System.Threading.Tasks; +using System.Windows; +using Microsoft.Extensions.Logging; +using RepoM.Api; using RepoM.Api.Common; +using RepoM.Api.Git; using RepoM.Api.Git.AutoFetch; using RepoM.Api.Git.ProcessExecution; -using RepoM.Api.Git; -using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; using RepoM.Api.IO; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; using RepoM.Api.Ordering.Az; using RepoM.Api.Ordering.Composition; using RepoM.Api.Ordering.IsPinned; using RepoM.Api.Ordering.Label; using RepoM.Api.Ordering.Score; using RepoM.Api.Ordering.Sum; +using RepoM.Api.Plugins; using RepoM.Api.RepositoryActions.Decorators; +using RepoM.App.ActionMenuCore; using RepoM.App.i18n; +using RepoM.App.Plugins; using RepoM.App.RepositoryActions; -using RepoM.App.RepositoryFiltering.QueryMatchers; using RepoM.App.RepositoryFiltering; +using RepoM.App.RepositoryFiltering.QueryMatchers; using RepoM.App.RepositoryOrdering; using RepoM.App.Services; using RepoM.Core.Plugin.Common; @@ -24,18 +34,7 @@ namespace RepoM.App; using RepoM.Core.Plugin.RepositoryFiltering; using RepoM.Core.Plugin.RepositoryFinder; using RepoM.Core.Plugin.RepositoryOrdering; -using System.IO.Abstractions; -using System; -using System.Threading.Tasks; using SimpleInjector; -using Microsoft.Extensions.Logging; -using RepoM.Api.Plugins; -using RepoM.App.Plugins; -using RepoM.App.Services.HotKey; -using RepoM.Api; -using System.Runtime.Caching; -using System.Windows; -using RepoM.App.ActionMenuCore; internal static class Bootstrapper { @@ -82,22 +81,22 @@ public static void RegisterServices(IFileSystem fileSystem) Container.Collection.Append(Lifestyle.Singleton); Container.Collection.Append(Lifestyle.Singleton); Container.Collection.Append(Lifestyle.Singleton); - Container.Collection.Append(() => new FreeTextMatcher(ignoreCase: true, ignoreCaseTag: true), Lifestyle.Singleton); + Container.Collection.Append(() => new FreeTextMatcher(true, true), Lifestyle.Singleton); Container.Register(Lifestyle.Singleton); - + Container.Register(Lifestyle.Singleton); - Container.RegisterInstance(fileSystem); + Container.RegisterInstance(fileSystem); ActionMenu.Core.Bootstrapper.RegisterServices(Container); - + Container.RegisterSingleton(); Container.RegisterSingleton(); CoreBootstrapper.RegisterRepositoryComparerConfigurationsTypes(Container); CoreBootstrapper.RegisterRepositoryScorerConfigurationsTypes(Container); - + Container.Register, IsPinnedScorerFactory>(Lifestyle.Singleton); Container.Register, TagScorerFactory>(Lifestyle.Singleton); Container.Register, AzRepositoryComparerFactory>(Lifestyle.Singleton); @@ -108,9 +107,9 @@ public static void RegisterServices(IFileSystem fileSystem) Container.RegisterSingleton(); Container.Register(typeof(ICommandExecutor<>), new[] { typeof(CoreBootstrapper).Assembly, }, Lifestyle.Singleton); Container.RegisterDecorator( - typeof(ICommandExecutor<>), - typeof(LoggerCommandExecutorDecorator<>), - Lifestyle.Singleton); + typeof(ICommandExecutor<>), + typeof(LoggerCommandExecutorDecorator<>), + Lifestyle.Singleton); Container.RegisterSingleton(); Container.RegisterSingleton(); @@ -118,16 +117,15 @@ public static void RegisterServices(IFileSystem fileSystem) Container.RegisterSingleton(); } - public static async Task RegisterPlugins( - IPluginFinder pluginFinder, - IFileSystem fileSystem, - ILoggerFactory loggerFactory) + public static async Task RegisterPlugins(IPluginFinder pluginFinder, + IFileSystem fileSystem, + ILoggerFactory loggerFactory) { Container.Register(Lifestyle.Singleton); Container.RegisterInstance(pluginFinder); var coreBootstrapper = new CoreBootstrapper(pluginFinder, fileSystem, DefaultAppDataPathProvider.Instance, loggerFactory); - var baseDirectory = fileSystem.Path.Combine(AppDomain.CurrentDomain.BaseDirectory); + var baseDirectory = fileSystem.Path.Combine(AppDomain.CurrentDomain.BaseDirectory); await coreBootstrapper.LoadAndRegisterPluginsAsync(Container, baseDirectory).ConfigureAwait(false); } @@ -135,15 +133,15 @@ public static void RegisterLogging(ILoggerFactory loggerFactory) { // https://stackoverflow.com/questions/41243485/simple-injector-register-iloggert-by-using-iloggerfactory-createloggert - Container.RegisterInstance(loggerFactory); + Container.RegisterInstance(loggerFactory); Container.RegisterSingleton(typeof(ILogger<>), typeof(Logger<>)); Container.RegisterConditional( - typeof(ILogger), - c => c.Consumer == null - ? typeof(Logger) - : typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType), - Lifestyle.Singleton, - _ => true); + typeof(ILogger), + c => c.Consumer == null + ? typeof(Logger) + : typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType), + Lifestyle.Singleton, + _ => true); } } \ No newline at end of file diff --git a/src/RepoM.App/Controls/AcrylicContextMenu.cs b/src/RepoM.App/Controls/AcrylicContextMenu.cs deleted file mode 100644 index b730a7cd..00000000 --- a/src/RepoM.App/Controls/AcrylicContextMenu.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace RepoM.App.Controls; - -using System.Windows; -using System.Windows.Controls; -using RepoM.App.Services; - -public class AcrylicContextMenu : ContextMenu -{ - protected override void OnOpened(RoutedEventArgs e) - { - base.OnOpened(e); - - AcrylicHelper.EnableBlur(this); - } -} \ No newline at end of file diff --git a/src/RepoM.App/Controls/AcrylicMenuItem.cs b/src/RepoM.App/Controls/AcrylicMenuItem.cs deleted file mode 100644 index ce024f8f..00000000 --- a/src/RepoM.App/Controls/AcrylicMenuItem.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace RepoM.App.Controls; - -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using RepoM.App.Services; - -public class AcrylicMenuItem : MenuItem -{ - private static readonly Brush _solidColorBrush = new SolidColorBrush(Color.FromArgb(80, 0, 0, 0)); - - protected override void OnSubmenuOpened(RoutedEventArgs e) - { - base.OnSubmenuOpened(e); - - Dispatcher.BeginInvoke((Action)BlurSubMenu); - } - - private void BlurSubMenu() - { - DependencyObject firstSubItem = ItemContainerGenerator.ContainerFromIndex(0); - - if (firstSubItem == null) - { - return; - } - - if (VisualTreeHelper.GetParent(firstSubItem) is not Visual container) - { - return; - } - - DependencyObject parent = container; - var borderIndex = 0; - - while (parent != null) - { - if (parent is Border b) - { - // only put color on the first border (transparent colors will add up otherwise) - b.Background = borderIndex == 0 - ? _solidColorBrush - : Brushes.Transparent; - - borderIndex++; - } - - parent = VisualTreeHelper.GetParent(parent); - } - - AcrylicHelper.EnableBlur(container); - } -} \ No newline at end of file diff --git a/src/RepoM.App/Controls/ZTextBox.cs b/src/RepoM.App/Controls/ZTextBox.cs deleted file mode 100644 index e3a6deee..00000000 --- a/src/RepoM.App/Controls/ZTextBox.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace RepoM.App.Controls; - -using System; -using System.Collections.Generic; -using System.Windows.Controls; -using System.Windows.Input; - -public class ZTextBox : TextBox -{ - public event EventHandler? Finish; - - protected override void OnKeyUp(KeyEventArgs e) - { - base.OnKeyUp(e); - - if (e.Key == Key.Escape) - { - Clear(); - } - - if (FinisherKeys.Contains(e.Key)) - { - Finish?.Invoke(this, EventArgs.Empty); - } - } - - private static List FinisherKeys { get; } = new(3) - { - Key.Down, - Key.Return, - Key.Enter, - }; -} \ No newline at end of file diff --git a/src/RepoM.App/Converters/UtcToHumanizedLocalDateTimeConverter.cs b/src/RepoM.App/Converters/UtcToHumanizedLocalDateTimeConverter.cs index e75e9226..faac5d23 100644 --- a/src/RepoM.App/Converters/UtcToHumanizedLocalDateTimeConverter.cs +++ b/src/RepoM.App/Converters/UtcToHumanizedLocalDateTimeConverter.cs @@ -5,6 +5,12 @@ namespace RepoM.App.Converters; using System.Windows.Data; using RepoM.Api.Common; +/// +/// The UtcToHumanizedLocalDateTimeConverter class is a WPF value converter that +/// converts a UTC DateTime to a human-readable local time string. +/// It implements the IValueConverter interface and uses a HardcodededMiniHumanizer to format the date. +/// The Convert method handles the conversion, while the ConvertBack method is not implemented. +/// public class UtcToHumanizedLocalDateTimeConverter : IValueConverter { private static readonly HardcodededMiniHumanizer _humanizer = new(SystemClock.Instance); @@ -19,4 +25,4 @@ public object ConvertBack(object? value, Type targetType, object? parameter, Cul { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/src/RepoM.App/Converters/UtcToLocalDateTimeConverter.cs b/src/RepoM.App/Converters/UtcToLocalDateTimeConverter.cs index c31eef15..7fcc5ffb 100644 --- a/src/RepoM.App/Converters/UtcToLocalDateTimeConverter.cs +++ b/src/RepoM.App/Converters/UtcToLocalDateTimeConverter.cs @@ -4,15 +4,30 @@ namespace RepoM.App.Converters; using System.Globalization; using System.Windows.Data; +/// +/// Converts a UTC DateTime to local DateTime. +/// public class UtcToLocalDateTimeConverter : IValueConverter { - public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - return DateTime.SpecifyKind(DateTime.Parse(value?.ToString() ?? string.Empty), DateTimeKind.Utc).ToLocalTime(); - } + /// + /// Converts a UTC DateTime to local DateTime. + /// + /// The UTC DateTime value to convert. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A local DateTime value. + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => + DateTime.SpecifyKind(DateTime.Parse(value?.ToString() ?? string.Empty, CultureInfo.InvariantCulture), DateTimeKind.Utc).ToLocalTime(); - public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} \ No newline at end of file + /// + /// Converts a value back. This method is not implemented. + /// + /// The value that is produced by the binding target. + /// The type to convert to. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. + /// Always thrown. + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotImplementedException(); +} diff --git a/src/RepoM.App/MainWindow.xaml b/src/RepoM.App/MainWindow.xaml index da122d30..674493b1 100644 --- a/src/RepoM.App/MainWindow.xaml +++ b/src/RepoM.App/MainWindow.xaml @@ -1,375 +1,637 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RepoM.App/MainWindow.xaml.cs b/src/RepoM.App/MainWindow.xaml.cs index 8fa3e373..f99abc7c 100644 --- a/src/RepoM.App/MainWindow.xaml.cs +++ b/src/RepoM.App/MainWindow.xaml.cs @@ -2,8 +2,11 @@ namespace RepoM.App; using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO.Abstractions; using System.Linq; using System.Threading.Tasks; @@ -11,14 +14,12 @@ namespace RepoM.App; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; -using System.Windows.Forms; using System.Windows.Input; +using System.Windows.Media; using Microsoft.Extensions.Logging; using RepoM.ActionMenu.Interface.UserInterface; using RepoM.Api.Common; using RepoM.Api.Git; -using RepoM.Api.RepositoryActions; -using RepoM.App.Controls; using RepoM.App.Plugins; using RepoM.App.RepositoryActions; using RepoM.App.RepositoryFiltering; @@ -28,72 +29,92 @@ namespace RepoM.App; using RepoM.Core.Plugin.Common; using RepoM.Core.Plugin.RepositoryActions.Commands; using RepoM.Core.Plugin.RepositoryFiltering.Clause; -using SourceChord.FluentWPF; +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; +using Button = Wpf.Ui.Controls.Button; using Control = System.Windows.Controls.Control; using KeyEventArgs = System.Windows.Input.KeyEventArgs; +using MenuItem = Wpf.Ui.Controls.MenuItem; +using TextBlock = System.Windows.Controls.TextBlock; -/// -/// Interaction logic for MainWindow.xaml -/// -public partial class MainWindow +[SuppressMessage("ReSharper", "ArrangeAccessorOwnerBody")] +public partial class MainWindow : FluentWindow { - private volatile bool _refreshDelayed; - private DateTime _timeOfLastRefresh = DateTime.MinValue; - private bool _closeOnDeactivate = true; - private readonly IRepositoryIgnoreStore _repositoryIgnoreStore; - private readonly DefaultRepositoryMonitor? _monitor; - private readonly ITranslationService _translationService; - private readonly IFileSystem _fileSystem; - private readonly ActionExecutor _executor; + private enum IndexNavigator + { + GoToNext, + GoToPrevious, + GoToFirst, + GoToLast, + StickToCurrent, + } + +#pragma warning disable IDE1006 + // ReSharper disable once InconsistentNaming + private static readonly ImmutableDictionary ListBoxRepos_NavigationKeys = new Dictionary + { + { Key.Up, IndexNavigator.GoToPrevious }, + { Key.Down, IndexNavigator.GoToNext }, + { Key.PageUp, IndexNavigator.GoToFirst }, + { Key.PageDown, IndexNavigator.GoToLast }, + { Key.Space, IndexNavigator.GoToNext }, + }.ToImmutableDictionary(); +#pragma warning restore IDE1006 + + private readonly IAppDataPathProvider _appDataPathProvider; + private readonly ActionExecutor _executor; + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + private readonly DefaultRepositoryMonitor? _monitor; private readonly IRepositoryFilteringManager _repositoryFilteringManager; - private readonly IRepositoryMatcher _repositoryMatcher; - private readonly ILogger _logger; - private readonly IUserMenuActionMenuFactory _userMenuActionFactory; - private readonly IAppDataPathProvider _appDataPathProvider; - - public MainWindow( - IRepositoryInformationAggregator aggregator, - IRepositoryMonitor repositoryMonitor, - IRepositoryIgnoreStore repositoryIgnoreStore, - IAppSettingsService appSettingsService, - ITranslationService translationService, - IAppDataPathProvider appDataPathProvider, - IFileSystem fileSystem, - ActionExecutor executor, - IRepositoryComparerManager repositoryComparerManager, - IThreadDispatcher threadDispatcher, - IRepositoryFilteringManager repositoryFilteringManager, - IRepositoryMatcher repositoryMatcher, - IModuleManager moduleManager, - ILogger logger, - IUserMenuActionMenuFactory userMenuActionFactory) + private readonly IRepositoryIgnoreStore _repositoryIgnoreStore; + private readonly IRepositoryMatcher _repositoryMatcher; + private readonly ITranslationService _translationService; + private readonly IUserMenuActionMenuFactory _userMenuActionFactory; + private volatile bool _refreshDelayed; + private bool _keepMainWindowOpenWhenLosingFocus; + private DateTime _timeOfLastRefresh = DateTime.MinValue; + + public MainWindow(IRepositoryInformationAggregator aggregator, + IRepositoryMonitor repositoryMonitor, + IRepositoryIgnoreStore repositoryIgnoreStore, + IAppSettingsService appSettingsService, + ITranslationService translationService, + IAppDataPathProvider appDataPathProvider, + IFileSystem fileSystem, + ActionExecutor executor, + IRepositoryComparerManager repositoryComparerManager, + IThreadDispatcher threadDispatcher, + IRepositoryFilteringManager repositoryFilteringManager, + IRepositoryMatcher repositoryMatcher, + IModuleManager moduleManager, + ILogger logger, + IUserMenuActionMenuFactory userMenuActionFactory) { _repositoryFilteringManager = repositoryFilteringManager ?? throw new ArgumentNullException(nameof(repositoryFilteringManager)); - _repositoryMatcher = repositoryMatcher ?? throw new ArgumentNullException(nameof(repositoryMatcher)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _userMenuActionFactory = userMenuActionFactory ?? throw new ArgumentNullException(nameof(userMenuActionFactory)); - _translationService = translationService ?? throw new ArgumentNullException(nameof(translationService)); - _repositoryIgnoreStore = repositoryIgnoreStore ?? throw new ArgumentNullException(nameof(repositoryIgnoreStore)); - _appDataPathProvider = appDataPathProvider ?? throw new ArgumentNullException(nameof(appDataPathProvider)); - _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - _executor = executor ?? throw new ArgumentNullException(nameof(executor)); + _repositoryMatcher = repositoryMatcher ?? throw new ArgumentNullException(nameof(repositoryMatcher)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _userMenuActionFactory = userMenuActionFactory ?? throw new ArgumentNullException(nameof(userMenuActionFactory)); + _translationService = translationService ?? throw new ArgumentNullException(nameof(translationService)); + _repositoryIgnoreStore = repositoryIgnoreStore ?? throw new ArgumentNullException(nameof(repositoryIgnoreStore)); + _appDataPathProvider = appDataPathProvider ?? throw new ArgumentNullException(nameof(appDataPathProvider)); + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _executor = executor ?? throw new ArgumentNullException(nameof(executor)); InitializeComponent(); - SetAcrylicWindowStyle(this, AcrylicWindowStyle.None); - - var orderingsViewModel = new OrderingsViewModel(repositoryComparerManager, threadDispatcher); + var orderingsViewModel = new OrderingsViewModel(repositoryComparerManager, threadDispatcher); var queryParsersViewModel = new QueryParsersViewModel(_repositoryFilteringManager, threadDispatcher); - var filterViewModel = new FiltersViewModel(_repositoryFilteringManager, threadDispatcher); - var pluginsViewModel = new PluginCollectionViewModel(moduleManager); + var filterViewModel = new FiltersViewModel(_repositoryFilteringManager, threadDispatcher); + var pluginsViewModel = new PluginCollectionViewModel(moduleManager); DataContext = new MainWindowViewModel( - appSettingsService, - orderingsViewModel, - queryParsersViewModel, - filterViewModel, - pluginsViewModel, - new HelpViewModel(_translationService)); + appSettingsService, + orderingsViewModel, + queryParsersViewModel, + filterViewModel, + pluginsViewModel, + new HelpViewModel(_translationService)); SettingsMenu.DataContext = DataContext; // this is out of the visual tree _monitor = repositoryMonitor as DefaultRepositoryMonitor; @@ -102,67 +123,82 @@ public MainWindow( _monitor.OnScanStateChanged += OnScanStateChanged; ShowScanningState(_monitor.Scanning); } - - lstRepositories.ItemsSource = aggregator.Repositories; + + ListBoxRepos.ItemsSource = aggregator.Repositories; var view = (ListCollectionView)CollectionViewSource.GetDefaultView(aggregator.Repositories); - ((ICollectionView)view).CollectionChanged += View_CollectionChanged; - view.Filter = FilterRepositories; - view.CustomSort = repositoryComparerManager.Comparer; + ((ICollectionView)view).CollectionChanged += View_CollectionChanged; + view.Filter = FilterRepositories; + view.CustomSort = repositoryComparerManager.Comparer; repositoryComparerManager.SelectedRepositoryComparerKeyChanged += (_, _) => view.Refresh(); - repositoryFilteringManager.SelectedQueryParserChanged += (_, _) => view.Refresh(); - repositoryFilteringManager.SelectedFilterChanged += (_, _) => view.Refresh(); + repositoryFilteringManager.SelectedQueryParserChanged += (_, _) => view.Refresh(); + repositoryFilteringManager.SelectedFilterChanged += (_, _) => view.Refresh(); + + ApplicationThemeManager.ApplySystemTheme(true); // Applies the system theme for Apps, not for + ApplicationAccentColorManager.ApplySystemAccent(); + WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica); + SystemThemeWatcher.Watch(this); + + + //ApplicationThemeManager.Apply(ApplicationTheme.Light); + //ApplicationThemeManager.Apply(ApplicationTheme.HighContrast); + + ApplicationThemeManager.Changed += OnAppThemeChange; PlaceFormByTaskBarLocation(); } - private void View_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + public bool IsShown { - // use the list's itemsource directly, this one is not filtered (otherwise searching in the UI without matches could lead to the "no repositories yet"-screen) - var hasRepositories = lstRepositories.ItemsSource.OfType().Any(); - tbNoRepositories.Visibility = hasRepositories ? Visibility.Hidden : Visibility.Visible; + get + { + return Visibility == Visibility.Visible && IsActive; + } } - protected override void OnActivated(EventArgs e) + private void OnAppThemeChange(ApplicationTheme currentapplicationtheme, Color systemaccent) { - base.OnActivated(e); - ShowUpdateIfAvailable(); - txtFilter.Focus(); - txtFilter.SelectAll(); + // TODO: IMPLEMENT FUNCTION TO CHANGE SETTINGS + //throw new NotImplementedException(); } - protected override void OnDeactivated(EventArgs e) + private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) { - base.OnDeactivated(e); + // TODO: Move tome things here from the constructor + } - if (_closeOnDeactivate) - { - Hide(); - } + + private void View_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + // use the list's items source directly, this one is not filtered (otherwise searching in + // the UI without matches could lead to the "no repositories yet"-screen) + var hasRepositories = ListBoxRepos.ItemsSource.OfType().Any(); + TbNoRepositories.SetCurrentValue(VisibilityProperty, hasRepositories ? Visibility.Collapsed : Visibility.Visible); } + /// + /// Window fires the Closing event before it closes. If the + /// user cancels the closing event, the window is not closed. + /// Otherwise, the window is closed and the Closed event is + /// fired. + /// Callers must have UIPermission(UIPermissionWindow.AllWindows) to call this API. + /// protected override void OnClosing(CancelEventArgs e) { e.Cancel = true; Hide(); } - protected override void OnPreviewKeyDown(KeyEventArgs e) - { - base.OnPreviewKeyDown(e); - - if (e.Key != Key.Escape) - { - return; - } - - var isFilterActive = txtFilter.IsFocused && !string.IsNullOrEmpty(txtFilter.Text); - if (!isFilterActive) - { - Hide(); - } - } - + /// + /// Show and activates the window + /// + /// + /// Calling Show() on window is the same as setting the + /// Visibility property to Visibility.Visible. + /// Calling Activate() calls SetForegroundWindow on the hWnd, + /// thus the rules for SetForegroundWindow apply to this method. + /// Activate() returns bool, indicating whether the window was activated or not + /// public void ShowAndActivate() { Dispatcher.Invoke(() => @@ -175,8 +211,8 @@ public void ShowAndActivate() } Activate(); - txtFilter.Focus(); - txtFilter.SelectAll(); + SearchBar_TextBox.Focus(); + SearchBar_TextBox.SelectAll(); }); } @@ -185,9 +221,9 @@ private void OnScanStateChanged(object? sender, bool isScanning) Dispatcher.Invoke(() => ShowScanningState(isScanning)); } - private async void LstRepositories_MouseDoubleClick(object? sender, MouseButtonEventArgs e) + private async void ListBoxRepos_MouseDoubleClick(object? sender, MouseButtonEventArgs e) { - // prevent doubleclicks from scrollbars and other non-data areas + // prevent double clicks from scrollbars and other non-data areas if (e.OriginalSource is not (Grid or TextBlock)) { return; @@ -203,149 +239,126 @@ private async void LstRepositories_MouseDoubleClick(object? sender, MouseButtonE } } - private async void LstRepositories_ContextMenuOpening(object? sender, ContextMenuEventArgs e) + private async void ListBoxRepos_ContextMenuOpening(object? sender, ContextMenuEventArgs e) { + // This triggers only when the Context Menu is opened via right click + // opening the context menu via Key.Left or Key.Right does not trigger this if (sender == null) { e.Handled = true; return; } - // var currentCursor = ((FrameworkElement)e.Source).Cursor; - // ((FrameworkElement)e.Source).Cursor = Cursors.AppStarting; - var lstRepositoriesContextMenuOpening = await LstRepositoriesContextMenuOpeningWrapperAsync(((FrameworkElement)e.Source).ContextMenu).ConfigureAwait(true); - if (!lstRepositoriesContextMenuOpening) + var listBoxReposContextMenuOpening = await ListBoxRepos_BuildContextMenuAsync(((FrameworkElement)e.Source).ContextMenu).ConfigureAwait(true); + if (!listBoxReposContextMenuOpening) { e.Handled = true; } } - - private async Task LstRepositoriesContextMenuOpeningWrapperAsync(ContextMenu ctxMenu) - { - try - { - return await LstRepositoriesContextMenuOpeningAsync(ctxMenu).ConfigureAwait(true); - } - catch (Exception e) - { - _logger.LogError(e, "Could not create menu."); - - ctxMenu.Items.Clear(); - ctxMenu.Items.Add(new AcrylicMenuItem - { - Header = "Error", - IsEnabled = false, - }); - ctxMenu.Items.Add(new AcrylicMenuItem - { - Header = e.Message, - IsEnabled = false, - }); - - return false; - } - } - private async Task LstRepositoriesContextMenuOpeningAsync(ContextMenu ctxMenu) + private async Task ListBoxRepos_BuildContextMenuAsync(ContextMenu? ctxMenu) { - if (lstRepositories.SelectedItem is not RepositoryViewModel vm) + if (ListBoxRepos.SelectedItem is not RepositoryViewModel vm) { return false; } - var items = new List(); - // ItemCollection items = ctxMenu.Items; - // ItemCollection items = new ItemCollection(); - //items.Clear(); - - // foreach (var item in ctxMenu.Items) - // { - // if (item is Control c) - // { - // c.IsEnabled = false; - // } - // } - - ctxMenu.Items.Clear(); - ctxMenu.Items.Add(new AcrylicMenuItem - { - Header = "Loading ..", - IsEnabled = true, - }); - - await foreach (UserInterfaceRepositoryActionBase action in _userMenuActionFactory.CreateMenuAsync(vm.Repository).ConfigureAwait(true)) + if (null == ctxMenu) { - if (action is UserInterfaceSeparatorRepositoryAction) + ListBoxRepos.SetCurrentValue(ContextMenuProperty, new ContextMenu()); + if (null != ListBoxRepos.ContextMenu) { - if (items.Count > 0 && items[^1] is not Separator) - { - items.Add(new Separator()); - } + ctxMenu = ListBoxRepos.ContextMenu; } - else if (action is DeferredSubActionsUserInterfaceRepositoryAction or UserInterfaceRepositoryAction) + else { - Control? controlItem = CreateMenuItemNewStyleAsync(action, vm); - if (controlItem != null) - { - items.Add(controlItem); - } + return false; } } - - ctxMenu.Items.Clear(); - foreach (Control item in items) + else { - ctxMenu.Items.Add(item); + ctxMenu.Items.Clear(); } - return true; - } - - - private async void LstRepositories_KeyDown(object? sender, KeyEventArgs e) - { - if (e.Key is Key.Return or Key.Enter) + try { - try + var items = new List(); + + ctxMenu.Items.Add(new MenuItem + { + Header = "Loading...", + IsEnabled = true, + }); + + await foreach (UserInterfaceRepositoryActionBase action in _userMenuActionFactory.CreateMenuAsync(vm.Repository).ConfigureAwait(true)) { - await InvokeActionOnCurrentRepositoryAsync().ConfigureAwait(false); + switch (action) + { + case UserInterfaceSeparatorRepositoryAction: + { + if (items.Count > 0 && items[^1] is not Separator) + { + items.Add(new Separator()); + } + + break; + } + + case DeferredSubActionsUserInterfaceRepositoryAction: + { + Control? controlItem = CreateMenuItemNewStyleAsync(action, vm); + if (controlItem != null) + { + items.Add(controlItem); + } + + break; + } + + case UserInterfaceRepositoryAction: + { + Control? controlItem = CreateMenuItemNewStyleAsync(action, vm); + if (controlItem != null) + { + items.Add(controlItem); + } + + break; + } + } } - catch (Exception exception) + + ctxMenu.Items.Clear(); + foreach (Control item in items) { - Console.WriteLine(exception); + ctxMenu.Items.Add(item); } - - return; - } - if (e.Key is Key.Left or Key.Right) + return true; + } + catch (Exception e) { - if (sender == null) - { - e.Handled = true; - return; - } + _logger.LogError(e, "Could not create menu."); - // try open context menu. - ContextMenu? ctxMenu = ((FrameworkElement)e.Source).ContextMenu; - if (ctxMenu == null) - { - return; - } + ctxMenu.Items.Clear(); + ctxMenu.Items.Add(new MenuItem + { + Header = "Error", + IsEnabled = false, + }); + ctxMenu.Items.Add(new MenuItem + { + Header = e.Message, + IsEnabled = false, + }); - var lstRepositoriesContextMenuOpening = await LstRepositoriesContextMenuOpeningWrapperAsync(ctxMenu).ConfigureAwait(true); - if (lstRepositoriesContextMenuOpening) - { - ctxMenu.Placement = PlacementMode.Left; - ctxMenu.PlacementTarget = (UIElement)e.OriginalSource; - ctxMenu.IsOpen = true; - } + return false; } } - + private async Task InvokeActionOnCurrentRepositoryAsync() { - if (lstRepositories.SelectedItem is not RepositoryViewModel selectedView) + if (ListBoxRepos.SelectedItem is not RepositoryViewModel selectedView) { return; } @@ -362,10 +375,10 @@ private async Task InvokeActionOnCurrentRepositoryAsync() } UserInterfaceRepositoryActionBase uiRepositoryAction = await _userMenuActionFactory - .CreateMenuAsync(selectedView.Repository) - .Skip(skip) - .FirstAsync() - .ConfigureAwait(false); + .CreateMenuAsync(selectedView.Repository) + .Skip(skip) + .FirstAsync() + .ConfigureAwait(false); if (uiRepositoryAction is not UserInterfaceRepositoryAction action) { @@ -382,29 +395,40 @@ private async Task InvokeActionOnCurrentRepositoryAsync() private void HelpButton_Click(object sender, RoutedEventArgs e) { - transitionerMain.SelectedIndex = transitionerMain.SelectedIndex == 0 ? 1 : 0; + ListBoxRepos.UnselectAll(); + if (RepoGrid.Visibility == Visibility.Visible) + { + RepoGrid.SetCurrentValue(VisibilityProperty, Visibility.Collapsed); + HelpStackPanel.SetCurrentValue(VisibilityProperty, Visibility.Visible); + } + else + { + RepoGrid.SetCurrentValue(VisibilityProperty, Visibility.Visible); + HelpStackPanel.SetCurrentValue(VisibilityProperty, Visibility.Collapsed); + } } private void MenuButton_Click(object sender, RoutedEventArgs e) { - if (MenuButton.ContextMenu != null) - { - MenuButton.ContextMenu.IsOpen = true; - } + MenuButton.ContextMenu?.SetCurrentValue(ContextMenu.IsOpenProperty, true); } private void ScanButton_Click(object sender, RoutedEventArgs e) { + ListBoxRepos.UnselectAll(); _monitor?.ScanForLocalRepositoriesAsync(); } private void ClearButton_Click(object sender, RoutedEventArgs e) { + ListBoxRepos.UnselectAll(); + SearchBar_TextBox.Clear(); _monitor?.Reset(); } private void ResetIgnoreRulesButton_Click(object sender, RoutedEventArgs e) { + ListBoxRepos.UnselectAll(); _repositoryIgnoreStore.Reset(); } @@ -435,16 +459,6 @@ private void StarButton_Click(object sender, RoutedEventArgs e) Navigate("https://github.com/coenm/RepoM"); } - private void FollowButton_Click(object sender, RoutedEventArgs e) - { - Navigate("https://twitter.com/Waescher"); - } - - private void SponsorButton_Click(object sender, RoutedEventArgs e) - { - Navigate("https://github.com/sponsors/awaescher"); - } - private static void Navigate(string url) { Process.Start(new ProcessStartInfo(url) @@ -455,123 +469,102 @@ private static void Navigate(string url) private void PlaceFormByTaskBarLocation() { - Point position = GetTopLeftPlaceFormByTaskBarLocation( - SystemParameters.WorkArea, - Height, - Width, - Screen.PrimaryScreen); - Left = position.X; - Top = position.Y; - } - - private static Point GetTopLeftPlaceFormByTaskBarLocation(Rect workArea, double height, double width, Screen? primaryScreen) - { - var topY = workArea.Top; - var bottomY = workArea.Height - height; - var leftX = workArea.Left; - var rightX = workArea.Width - width; - - return TaskBarLocator.GetTaskBarLocation(primaryScreen) switch - { - TaskBarLocator.TaskBarLocation.Top => new Point(rightX, topY), - TaskBarLocator.TaskBarLocation.Left => new Point(leftX, bottomY), - TaskBarLocator.TaskBarLocation.Bottom or TaskBarLocator.TaskBarLocation.Right => new Point(rightX, bottomY), - _ => new Point(rightX, bottomY), - }; + SetCurrentValue(TopProperty, SystemParameters.WorkArea.BottomRight.Y - ActualHeight - 5); + SetCurrentValue(LeftProperty, SystemParameters.WorkArea.BottomRight.X - ActualWidth - 10); } private void ShowUpdateIfAvailable() { var updateHint = _translationService.Translate("Update hint", App.AvailableUpdate ?? "?.?"); - UpdateButton.Visibility = App.AvailableUpdate == null ? Visibility.Hidden : Visibility.Visible; - UpdateButton.ToolTip = App.AvailableUpdate == null ? "" : updateHint; - UpdateButton.Tag = App.AvailableUpdate; + UpdateButton.SetCurrentValue(VisibilityProperty, App.AvailableUpdate == null ? Visibility.Hidden : Visibility.Visible); + UpdateButton.SetCurrentValue(ToolTipProperty, App.AvailableUpdate == null ? "" : updateHint); + UpdateButton.SetCurrentValue(TagProperty, App.AvailableUpdate); var parent = (Grid)UpdateButton.Parent; - parent.ColumnDefinitions[Grid.GetColumn(UpdateButton)].Width = App.AvailableUpdate == null ? new GridLength(0) : GridLength.Auto; + parent.ColumnDefinitions[Grid.GetColumn(UpdateButton)].SetCurrentValue(ColumnDefinition.WidthProperty, App.AvailableUpdate == null ? new GridLength(0) : GridLength.Auto); } - private Control? /*MenuItem*/ CreateMenuItem(RepositoryActionBase action, RepositoryViewModel? affectedViews = null) - { - if (action is RepositorySeparatorAction) - { - return new Separator(); - } - - if (action is not RepositoryAction repositoryAction) - { - // throw?? - return null; - } - - Action clickAction = (object clickSender, object clickArgs) => - { - if (repositoryAction?.Action is null or NullRepositoryCommand) - { - return; - } - - // run actions in the UI async to not block it - if (repositoryAction.ExecutionCausesSynchronizing) - { - Task.Run(() => SetVmSynchronizing(affectedViews, true)) - .ContinueWith(t => _executor.Execute(action.Repository, action.Action)) - .ContinueWith(t => SetVmSynchronizing(affectedViews, false)); - } - else - { - Task.Run(() => _executor.Execute(action.Repository, action.Action)); - } - }; - - var item = new AcrylicMenuItem - { - Header = repositoryAction.Name, - IsEnabled = repositoryAction.CanExecute, - }; - item.Click += new RoutedEventHandler(clickAction); - - // this is a deferred submenu. We want to make sure that the context menu can pop up - // fast, while submenus are not evaluated yet. We don't want to make the context menu - // itself slow because the creation of the submenu items takes some time. - if (repositoryAction is DeferredSubActionsRepositoryAction deferredRepositoryAction && deferredRepositoryAction.DeferredSubActionsEnumerator != null) - { - // this is a template submenu item to enable submenus under the current - // menu item. this item gets removed when the real subitems are created - item.Items.Add(string.Empty); - - void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) - { - item.SubmenuOpened -= SelfDetachingEventHandler; - item.Items.Clear(); - - foreach (RepositoryActionBase subAction in deferredRepositoryAction.DeferredSubActionsEnumerator()) - { - Control? controlItem = CreateMenuItem(subAction); - if (controlItem != null) - { - item.Items.Add(controlItem); - } - } - } - - item.SubmenuOpened += SelfDetachingEventHandler; - } - else if (repositoryAction.SubActions != null) - { - foreach (RepositoryActionBase subAction in repositoryAction.SubActions) - { - Control? controlItem = CreateMenuItem(subAction); - if (controlItem != null) - { - item.Items.Add(controlItem); - } - } - } - - return item; - } + //private Control? /*MenuItem*/ CreateMenuItem(RepositoryActionBase action, RepositoryViewModel? affectedViews = null) + //{ + // if (action is RepositorySeparatorAction) + // { + // return new Separator(); + // } + + // if (action is not RepositoryAction repositoryAction) + // { + // // throw?? + // return null; + // } + + // Action clickAction = (clickSender, clickArgs) => + // { + // if (repositoryAction.Action is null or NullRepositoryCommand) + // { + // return; + // } + + // // run actions in the UI async to not block it + // if (repositoryAction.ExecutionCausesSynchronizing) + // { + // Task.Run(() => SetVmSynchronizing(affectedViews, true)) + // .ContinueWith(t => _executor.Execute(action.Repository, action.Action)) + // .ContinueWith(t => SetVmSynchronizing(affectedViews, false)); + // } + // else + // { + // Task.Run(() => _executor.Execute(action.Repository, action.Action)); + // } + // }; + + // var item = new MenuItem + // { + // Header = repositoryAction.Name, + // IsEnabled = repositoryAction.CanExecute, + // }; + // item.Click += new RoutedEventHandler(clickAction); + + // // this is a deferred submenu. We want to make sure that the context menu can pop up + // // fast, while submenus are not evaluated yet. We don't want to make the context menu + // // itself slow because the creation of the submenu items takes some time. + // if (repositoryAction is DeferredSubActionsRepositoryAction deferredRepositoryAction && deferredRepositoryAction.DeferredSubActionsEnumerator != null) + // { + // // this is a template submenu item to enable submenus under the current + // // menu item. this item gets removed when the real subitems are created + // item.Items.Add(string.Empty); + + // void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) + // { + // item.SubmenuOpened -= SelfDetachingEventHandler; + // item.Items.Clear(); + + // foreach (RepositoryActionBase subAction in deferredRepositoryAction.DeferredSubActionsEnumerator()) + // { + // Control? controlItem = CreateMenuItem(subAction); + // if (controlItem != null) + // { + // item.Items.Add(controlItem); + // } + // } + // } + + // item.SubmenuOpened += SelfDetachingEventHandler; + // } + // else if (repositoryAction.SubActions != null) + // { + // foreach (RepositoryActionBase subAction in repositoryAction.SubActions) + // { + // Control? controlItem = CreateMenuItem(subAction); + // if (controlItem != null) + // { + // item.Items.Add(controlItem); + // } + // } + // } + + // return item; + //} private Control? /*MenuItem*/ CreateMenuItemNewStyleAsync(UserInterfaceRepositoryActionBase action, RepositoryViewModel? affectedViews = null) { @@ -609,7 +602,7 @@ void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) } }; - var item = new AcrylicMenuItem + var item = new MenuItem { Header = repositoryAction.Name, IsEnabled = repositoryAction.CanExecute, @@ -629,7 +622,7 @@ async void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) { item.SubmenuOpened -= SelfDetachingEventHandler; item.Items.Clear(); - + foreach (UserInterfaceRepositoryActionBase subAction in await deferredRepositoryAction.GetAsync().ConfigureAwait(true)) { Control? controlItem = CreateMenuItemNewStyleAsync(subAction); @@ -659,12 +652,17 @@ async void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) item.SubmenuOpened += SelfDetachingEventHandler; } - else if (repositoryAction.SubActions != null) + else { + if (repositoryAction.SubActions == null) + { + return item; + } + // this is a template submenu item to enable submenus under the current // menu item. this item gets removed when the real subitems are created item.Items.Add("Loading.."); - + async void SelfDetachingEventHandler1(object _, RoutedEventArgs evtArgs) { item.SubmenuOpened -= SelfDetachingEventHandler1; @@ -713,45 +711,74 @@ private static void SetVmSynchronizing(RepositoryViewModel? affectedVm, bool syn private void ShowScanningState(bool isScanning) { - ScanMenuItem.IsEnabled = !isScanning; - ScanMenuItem.Header = isScanning - ? _translationService.Translate("Scanning") - : _translationService.Translate("ScanComputer"); + ScanMenuItem.SetCurrentValue(IsEnabledProperty, !isScanning); + ScanMenuItem.SetCurrentValue(HeaderedItemsControl.HeaderProperty, + isScanning + ? _translationService.Translate("Scanning") + : _translationService.Translate("ScanComputer")); } - protected override void OnKeyDown(KeyEventArgs e) + private bool FilterRepositories(object item) { - base.OnKeyDown(e); + var query = SearchBar_TextBox.Text.Trim(); + + if (_refreshDelayed) + { + return false; + } + + if (item is not RepositoryViewModel viewModelItem) + { + return false; + } + + try + { + IQuery? alwaysVisibleFilter = _repositoryFilteringManager.AlwaysVisibleFilter; + if (alwaysVisibleFilter != null && _repositoryMatcher.Matches(viewModelItem.Repository, alwaysVisibleFilter)) + { + return true; + } + } + catch (Exception) + { + return false; + } - if (e.Key == Key.F && Keyboard.IsKeyDown(Key.LeftCtrl)) + try { - txtFilter.Focus(); - txtFilter.SelectAll(); + if (!_repositoryMatcher.Matches(viewModelItem.Repository, _repositoryFilteringManager.PreFilter)) + { + return false; + } + } + catch (Exception) + { + return false; } - if (e.Key == Key.Down && txtFilter.IsFocused) + if (string.IsNullOrWhiteSpace(query)) { - lstRepositories.Focus(); + return true; } - // show/hide the titlebar to move the window for screenshots, for example - if (e.Key == Key.F11) + if (_refreshDelayed) { - AcrylicWindowStyle currentStyle = GetAcrylicWindowStyle(this); - AcrylicWindowStyle newStyle = currentStyle == AcrylicWindowStyle.None - ? AcrylicWindowStyle.Normal - : AcrylicWindowStyle.None; - SetAcrylicWindowStyle(this, newStyle); + return false; } - // keep window open on deactivate to make screenshots, for example - if (e.Key == Key.F12) + try + { + IQuery queryObject = _repositoryFilteringManager.QueryParser.Parse(query); + return _repositoryMatcher.Matches(viewModelItem.Repository, queryObject); + } + catch (Exception) { - _closeOnDeactivate = !_closeOnDeactivate; + return false; } } - private void OnTxtFilterTextChanged(object? sender, TextChangedEventArgs e) + private void OnSearchBar_TextBoxTextChanged(object? sender, TextChangedEventArgs e) { // Text has changed, capture the timestamp if (sender != null) @@ -769,7 +796,7 @@ private void OnTxtFilterTextChanged(object? sender, TextChangedEventArgs e) _refreshDelayed = true; await Task.Delay(200); _refreshDelayed = false; - OnTxtFilterTextChanged(null, e); + OnSearchBar_TextBoxTextChanged(null, e); }); } @@ -777,82 +804,279 @@ private void OnTxtFilterTextChanged(object? sender, TextChangedEventArgs e) } // Refresh the view - ICollectionView view = CollectionViewSource.GetDefaultView(lstRepositories.ItemsSource); + ICollectionView view = CollectionViewSource.GetDefaultView(ListBoxRepos.ItemsSource); view.Refresh(); } - private bool FilterRepositories(object item) + private void ListBoxRepos_ChangeCurrentItem(IndexNavigator navigator, bool focus) { - var query = txtFilter.Text.Trim(); + if (ListBoxRepos.Items.IsEmpty) { return; } - if (_refreshDelayed) + if (focus && !ListBoxRepos.IsFocused) { - return false; + ListBoxRepos.Focus(); } - if (item is not RepositoryViewModel viewModelItem) - { - return false; - } + var currentIndex = ListBoxRepos.SelectedIndex; + int newIndex; - try + switch (navigator) { - IQuery? alwaysVisibleFilter = _repositoryFilteringManager.AlwaysVisibleFilter; - if (alwaysVisibleFilter != null && _repositoryMatcher.Matches(viewModelItem.Repository, alwaysVisibleFilter)) - { - return true; - } + case IndexNavigator.GoToNext: + { + newIndex = currentIndex + 1; + if (newIndex >= ListBoxRepos.Items.Count) + { + newIndex = 0; + } + + break; + } + + case IndexNavigator.GoToPrevious: + { + newIndex = currentIndex - 1; + if (newIndex < 0) + { + newIndex = ListBoxRepos.Items.Count - 1; + } + + break; + } + + case IndexNavigator.GoToFirst: + { + newIndex = 0; + //if (newIndex == currentIndex) + //{ + // return; + //} + + break; + } + + case IndexNavigator.GoToLast: + { + newIndex = ListBoxRepos.Items.Count - 1; + //if (newIndex == currentIndex) + //{ + // return; + //} + + break; + } + + case IndexNavigator.StickToCurrent: + { + newIndex = currentIndex; + break; + } + + default: + { + #pragma warning disable CA2254 + _logger.LogError(new ArgumentOutOfRangeException(nameof(navigator), navigator, null).ToString()); + #pragma warning restore CA2254 + throw new ArgumentOutOfRangeException(nameof(navigator), navigator, null); + } } - catch (Exception) + + ListBoxRepos.SetCurrentValue(Selector.SelectedIndexProperty, newIndex); + var item = (ListBoxItem)ListBoxRepos.ItemContainerGenerator.ContainerFromIndex(newIndex); + + if (focus) { - return false; + item?.Focus(); } + } - try + private void SearchBar_TextBox_OnKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) { - if (!_repositoryMatcher.Matches(viewModelItem.Repository, _repositoryFilteringManager.PreFilter)) - { - return false; - } + case Key.Enter: + { + ListBoxRepos_ChangeCurrentItem(IndexNavigator.GoToFirst, true); + break; + } + + case var _ when ListBoxRepos_NavigationKeys.TryGetValue(e.Key, out IndexNavigator kValue): + { + ListBoxRepos_ChangeCurrentItem(kValue, false); + break; + } } - catch (Exception) + } + + private async void ListBoxRepos_KeyDown(object? sender, KeyEventArgs e) + { + if (null == sender || ListBoxRepos.Items.IsEmpty || ListBoxRepos.SelectedIndex < 0) { - return false; + e.Handled = true; + return; } - - if (string.IsNullOrWhiteSpace(query)) + + switch (e.Key) { - return true; + case Key.Enter: + { + try + { + ListBoxRepos_ChangeCurrentItem(IndexNavigator.StickToCurrent, true); + _ = InvokeActionOnCurrentRepositoryAsync().ConfigureAwait(false); + } + catch (Exception exception) + { + _logger.LogError(exception, "Could not invoke action on current repository."); + } + + break; + } + + case var _ when ListBoxRepos_NavigationKeys.TryGetValue(e.Key, out IndexNavigator kValue): + { + ListBoxRepos_ChangeCurrentItem(kValue, true); + break; + } + + case Key.Left or Key.Right: + { + // try open context menu. + ContextMenu? ctxMenu = ((FrameworkElement)e.Source).ContextMenu; + + var listBoxReposContextMenuOpening = await ListBoxRepos_BuildContextMenuAsync(ctxMenu).ConfigureAwait(true); + if (listBoxReposContextMenuOpening) + { + // ReSharper disable once PossibleNullReferenceException + ctxMenu.Placement = PlacementMode.Left; + ctxMenu.PlacementTarget = (UIElement)e.OriginalSource; + ctxMenu.IsOpen = true; + } + + break; + } } + } - if (_refreshDelayed) + private void MainWindow_OnKeyDown(object sender, KeyEventArgs e) + { + // SearchBar_TextBox.IsFocused => We deal with this in the SearchBar_TextBox_OnKeyDown method, but they are caught here too + // ListBoxRepos.IsFocused => We deal with this in the ListBoxRepos_KeyDown method, but they are caught here too + + switch (e.Key) { - return false; + case var _ when ListBoxRepos_NavigationKeys.TryGetValue(e.Key, out IndexNavigator kValue): + { + ListBoxRepos_ChangeCurrentItem(kValue, true); + break; + } + + case Key.F1: + HelpButton_Click(sender, e); + break; + + case Key.F2: + MenuButton_Click(sender, e); + break; + + case Key.F3: + ScanButton_Click(sender, e); + break; + + case Key.F4: + ClearButton_Click(sender, e); + break; + + case Key.F12: + // keep window open on deactivate to make screenshots, for example + _keepMainWindowOpenWhenLosingFocus = !_keepMainWindowOpenWhenLosingFocus; + break; } + } - try + private void OnKBPress_Escape(object sender, ExecutedRoutedEventArgs e) + { + // triggers as soon as the Key.Escape / Key.Clear / Key.Cancel is pressed, not when it is released. The equivalent of OnPreviewKeyDown. + + if (string.IsNullOrEmpty(SearchBar_TextBox.Text)) { - IQuery queryObject = _repositoryFilteringManager.QueryParser.Parse(query); - return _repositoryMatcher.Matches(viewModelItem.Repository, queryObject); + Hide(); } - catch (Exception) + else { - return false; + SearchBar_TextBox.Clear(); + ListBoxRepos.UnselectAll(); + SearchBar_TextBox.Focus(); } } - private void TxtFilter_Finish(object sender, EventArgs e) + /// + /// This event is raised when the window is activated + /// + private void MainWindow_OnActivated(object? sender, EventArgs e) { - lstRepositories.Focus(); - if (lstRepositories.Items.Count <= 0) + ShowUpdateIfAvailable(); + SearchBar_TextBox.Focus(); + SearchBar_TextBox.SelectAll(); + } + + /// + /// This event is raised when the window becomes a background window. + /// + /// + /// A window is deactivated (becomes a background window) when: + /// * A user switches to another window in the current application. + /// * A user switches to the window in another application by using ALT+TAB or by using Task Manager. + /// * A user clicks the taskbar button for a window in another application. + /// + /// + private void MainWindow_OnDeactivated(object? sender, EventArgs e) + { + if (_keepMainWindowOpenWhenLosingFocus) { return; } - lstRepositories.SelectedIndex = 0; - var item = (ListBoxItem)lstRepositories.ItemContainerGenerator.ContainerFromIndex(0); - item?.Focus(); + /* + * Calling Hide on window is the same as setting the + * Visibility property to Visibility.Hidden + */ + Hide(); + } + + /// + /// This event is raised when the window and its content is rendered. + /// + private void MainWindow_OnContentRendered(object? sender, EventArgs e) + { + // TODO + } + + /// + /// This even fires after the window source is created before it is shown. + /// + /// It enables connection to the Win32 API. + private void MainWindow_OnSourceInitialized(object? sender, EventArgs e) + { + // TODO + } + + private async void UnpinRepo_Click(object sender, RoutedEventArgs e) + { + // prevent double clicks from scrollbars and other non-data areas + //if (e.OriginalSource is not (Button or TextBlock)) + //{ + // return; + //} + + try + { + // TODO Implement Unpinning CALL + } + catch (Exception exception) + { + //_logger.LogError(exception, "Could not invoke action on current repository."); + } } +} - public bool IsShown => Visibility == Visibility.Visible && IsActive; -} \ No newline at end of file diff --git a/src/RepoM.App/NotifyIconResources.xaml b/src/RepoM.App/NotifyIconResources.xaml index 39e087e7..4948002e 100644 --- a/src/RepoM.App/NotifyIconResources.xaml +++ b/src/RepoM.App/NotifyIconResources.xaml @@ -1,40 +1,46 @@ - + - - + - - - - - - - + --> + + + + + + + - - + + - - - - - + + + + + \ No newline at end of file diff --git a/src/RepoM.App/NotifyIconViewModel.cs b/src/RepoM.App/NotifyIconViewModel.cs index 20cf354f..ebbf98db 100644 --- a/src/RepoM.App/NotifyIconViewModel.cs +++ b/src/RepoM.App/NotifyIconViewModel.cs @@ -5,44 +5,48 @@ namespace RepoM.App; using RepoM.App.Services; /// -/// Provides bindable properties and commands for the NotifyIcon. In this sample, the -/// view model is assigned to the NotifyIcon in XAML. Alternatively, the startup routing -/// in App.xaml.cs could have created this view model, and assigned it to the NotifyIcon. +/// Provides bindable properties and commands for the NotifyIcon. In this sample, the +/// view model is assigned to the NotifyIcon in XAML. Alternatively, the startup routing +/// in App.xaml.cs could have created this view model, and assigned it to the NotifyIcon. /// public class NotifyIconViewModel { private const string APP_NAME = "RepoM"; +#pragma warning disable CA1822 /// - /// Shows a window, if none is already open. + /// Shows a window, if none is already open. /// + // ReSharper disable once MemberCanBeMadeStatic.Global public ICommand OpenCommand => new DelegateCommand { CanExecuteFunc = () => (Application.Current.MainWindow as MainWindow)?.IsShown == false, - CommandAction = () => (Application.Current.MainWindow as MainWindow)?.ShowAndActivate(), + CommandAction = () => (Application.Current.MainWindow as MainWindow)?.ShowAndActivate(), }; public ICommand StartWithWindows => new DelegateCommand { CanExecuteFunc = () => !AutoStart.IsStartup(APP_NAME), - CommandAction = () => AutoStart.SetStartup(APP_NAME, true), + CommandAction = () => AutoStart.SetStartup(APP_NAME, true), }; public ICommand DoNotStartWithWindows => new DelegateCommand { CanExecuteFunc = () => AutoStart.IsStartup(APP_NAME), - CommandAction = () => AutoStart.SetStartup(APP_NAME, false), + CommandAction = () => AutoStart.SetStartup(APP_NAME, false), }; /// - /// Shuts down the application. + /// Shuts down the application. /// + public ICommand ExitApplicationCommand => new DelegateCommand { CommandAction = () => Application.Current.Shutdown(), }; +#pragma warning restore CA1822 } \ No newline at end of file diff --git a/src/RepoM.App/Properties/AssemblyInfo.cs b/src/RepoM.App/Properties/AssemblyInfo.cs index 092db2e0..b8d22141 100644 --- a/src/RepoM.App/Properties/AssemblyInfo.cs +++ b/src/RepoM.App/Properties/AssemblyInfo.cs @@ -6,7 +6,7 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("RepoM")] -[assembly: AssemblyDescription("Git repository information aggregator with Windows Explorer- & CLI-enhancements")] +[assembly: AssemblyDescription("Git repository information aggregator with Windows Explorer enhancements")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("RepoM")] diff --git a/src/RepoM.App/RepoM.App.csproj b/src/RepoM.App/RepoM.App.csproj index 524fa33b..a91271b3 100644 --- a/src/RepoM.App/RepoM.App.csproj +++ b/src/RepoM.App/RepoM.App.csproj @@ -1,10 +1,10 @@ - + net8.0-windows WinExe RepoM false - True + False true true false @@ -16,60 +16,82 @@ App.ico True + latest - - - - - - - - - - + + + + + + + + + + - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + True True Settings.settings - + PreserveNewest - + SettingsSingleFileGenerator Settings.Designer.cs - \ No newline at end of file + + + + diff --git a/src/RepoM.App/Resources/Accent.xaml b/src/RepoM.App/Resources/Accent.xaml new file mode 100644 index 00000000..3afb5244 --- /dev/null +++ b/src/RepoM.App/Resources/Accent.xaml @@ -0,0 +1,42 @@ + + + + #3379d9 + + + + + #559ce4 + + #80b9ee + + #add8ff + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RepoM.App/Resources/Fonts.xaml b/src/RepoM.App/Resources/Fonts.xaml new file mode 100644 index 00000000..d70feefe --- /dev/null +++ b/src/RepoM.App/Resources/Fonts.xaml @@ -0,0 +1,13 @@ + + + Segoe Ui + + Segoe Fluent Icons + + + pack://application:,,,/RepoM;component/Resources/Fonts/#FluentSystemIcons-Regular + + + pack://application:,,,/RepoM;component/Resources/Fonts/#FluentSystemIcons-Filled + + diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Black.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Black.otf new file mode 100644 index 00000000..fb6858c6 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Black.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-BlackIt.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-BlackIt.otf new file mode 100644 index 00000000..d0f5c8e2 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-BlackIt.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Bold.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Bold.otf new file mode 100644 index 00000000..61e08898 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Bold.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-BoldIt.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-BoldIt.otf new file mode 100644 index 00000000..257bfe97 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-BoldIt.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-ExtraLight.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-ExtraLight.otf new file mode 100644 index 00000000..ba25ac0a Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-ExtraLight.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-ExtraLightIt.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-ExtraLightIt.otf new file mode 100644 index 00000000..b846506d Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-ExtraLightIt.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-It.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-It.otf new file mode 100644 index 00000000..be19552c Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-It.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Light.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Light.otf new file mode 100644 index 00000000..34500d79 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Light.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-LightIt.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-LightIt.otf new file mode 100644 index 00000000..42482bdd Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-LightIt.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Medium.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Medium.otf new file mode 100644 index 00000000..bcb70e44 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Medium.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-MediumIt.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-MediumIt.otf new file mode 100644 index 00000000..a9ecfabb Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-MediumIt.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Regular.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Regular.otf new file mode 100644 index 00000000..16c7b0be Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Regular.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Semibold.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Semibold.otf new file mode 100644 index 00000000..11e64fb8 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-Semibold.otf differ diff --git a/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-SemiboldIt.otf b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-SemiboldIt.otf new file mode 100644 index 00000000..c6815675 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/AdobeSourceCodePro/SourceCodePro-SemiboldIt.otf differ diff --git a/src/RepoM.App/Resources/Fonts/Deprecated/FluentSystemIcons-Filled.ttf b/src/RepoM.App/Resources/Fonts/Deprecated/FluentSystemIcons-Filled.ttf new file mode 100644 index 00000000..21e76708 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/Deprecated/FluentSystemIcons-Filled.ttf differ diff --git a/src/RepoM.App/Resources/Fonts/Deprecated/FluentSystemIcons-Regular.ttf b/src/RepoM.App/Resources/Fonts/Deprecated/FluentSystemIcons-Regular.ttf new file mode 100644 index 00000000..ff12b065 Binary files /dev/null and b/src/RepoM.App/Resources/Fonts/Deprecated/FluentSystemIcons-Regular.ttf differ diff --git a/src/RepoM.App/Resources/Palette.xaml b/src/RepoM.App/Resources/Palette.xaml new file mode 100644 index 00000000..02dee6e8 --- /dev/null +++ b/src/RepoM.App/Resources/Palette.xaml @@ -0,0 +1,50 @@ + + + + + #333333 + #F44336 + #E91E63 + #9C27B0 + #673AB7 + #3F51B5 + #2196F3 + #03A9F4 + #00BCD4 + #009688 + #4CAF50 + #8BC34A + #CDDC39 + #FFEB3B + #FFC107 + #FF9800 + #FF5722 + #795548 + #9E9E9E + #607D8B + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RepoM.App/Resources/StaticColors.xaml b/src/RepoM.App/Resources/StaticColors.xaml new file mode 100644 index 00000000..1d47c560 --- /dev/null +++ b/src/RepoM.App/Resources/StaticColors.xaml @@ -0,0 +1,39 @@ + + + #FFFFFF + #C5FFFFFF + #87FFFFFF + #5DFFFFFF + #E4000000 + + + + + + + + #E4000000 + #BE000000 + #A2000000 + #5C000000 + #FFFFFF + + + + + + + + #FFFAFAFA + + + #FF202020 + + + #B3FFFFFF + + + #72000000 + + + diff --git a/src/RepoM.App/Resources/Theme/Dark.xaml b/src/RepoM.App/Resources/Theme/Dark.xaml new file mode 100644 index 00000000..b92b36b5 --- /dev/null +++ b/src/RepoM.App/Resources/Theme/Dark.xaml @@ -0,0 +1,663 @@ + + + #FF202020 + + + #87FFFFFF + + + + + #FFFFFF + #C5FFFFFF + #87FFFFFF + #5DFFFFFF + #87FFFFFF + #E4000000 + + #5DFFFFFF + #FFFFFF + #000000 + #80000000 + #77000000 + + #0FFFFFFF + #15FFFFFF + #08FFFFFF + #0BFFFFFF + #00FFFFFF + #B31E1E1E + + #8BFFFFFF + #3FFFFFFF + + #454545 + + #00FFFFFF + #0FFFFFFF + #0AFFFFFF + #00FFFFFF + + #00FFFFFF + #19000000 + #0BFFFFFF + #12FFFFFF + #00FFFFFF + + #B31C1C1C + #1A1A1A + #131313 + #1E1E1E + + #28FFFFFF + + #12FFFFFF + #18FFFFFF + #C5FFFFFF + #14FFFFFF + #23000000 + #37000000 + #33000000 + + #6B000000 + + #19000000 + #1C1C1C + + #8BFFFFFF + #28FFFFFF + + #66757575 + #33000000 + #0F000000 + + #15FFFFFF + + #FFFFFF + #B3000000 + + #0DFFFFFF + #08FFFFFF + + #4D000000 + + #4C3A3A3A + #0DFFFFFF + #09FFFFFF + #09FFFFFF + + + #2C2C2C + + #733A3A3A + #0FFFFFFF + #2C2C2C + #00FFFFFF + + #202020 + #1C1C1C + #282828 + #2C2C2C + #00202020 + #0A0A0A + + #4cc2ff + #9d9d9d + #6CCB5F + #FCE100 + #FF99A4 + #8BFFFFFF + #9D9D9D + #08FFFFFF + #393D1B + #433519 + #442726 + #08FFFFFF + #2E2E2E + #2E2E2E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RepoM.App/Resources/Theme/HC1.xaml b/src/RepoM.App/Resources/Theme/HC1.xaml new file mode 100644 index 00000000..a0a35280 --- /dev/null +++ b/src/RepoM.App/Resources/Theme/HC1.xaml @@ -0,0 +1,669 @@ + + + + #4cc2ff + #4cc2ff + #4cc2ff + + + #FFFFFF + #2D3236 + #212D3B + #ABCFF2 + #B6F6F0 + #2D3236 + #70EBDE + #A6A6A6 + + + #2D3236 + + + + #3D3D3D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + diff --git a/src/RepoM.App/Resources/Theme/HC2.xaml b/src/RepoM.App/Resources/Theme/HC2.xaml new file mode 100644 index 00000000..428add26 --- /dev/null +++ b/src/RepoM.App/Resources/Theme/HC2.xaml @@ -0,0 +1,668 @@ + + + + #4cc2ff + #4cc2ff + #4cc2ff + + + #FFFFFF + #000000 + #2B2B2B + #D6B4FD + #FFEE32 + #000000 + #8080FF + #A6A6A6 + + #000000 + + + + #3D3D3D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + diff --git a/src/RepoM.App/Resources/Theme/HCBlack.xaml b/src/RepoM.App/Resources/Theme/HCBlack.xaml new file mode 100644 index 00000000..d831959f --- /dev/null +++ b/src/RepoM.App/Resources/Theme/HCBlack.xaml @@ -0,0 +1,668 @@ + + + + #4cc2ff + #4cc2ff + #4cc2ff + + + #FFFFFF + #202020 + #263B50 + #8EE3F0 + #FFFFFF + #202020 + #75E9FC + #A6A6A6 + + #202020 + + + + #3D3D3D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + diff --git a/src/RepoM.App/Resources/Theme/HCWhite.xaml b/src/RepoM.App/Resources/Theme/HCWhite.xaml new file mode 100644 index 00000000..928d6c06 --- /dev/null +++ b/src/RepoM.App/Resources/Theme/HCWhite.xaml @@ -0,0 +1,668 @@ + + + + #4cc2ff + #4cc2ff + #4cc2ff + + + #3D3D3D + #FFFAEF + #FFF5E3 + #903909 + #202020 + #FFFAEF + #1C5E75 + #676767 + + #FFFAEF + + + + #3D3D3D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + #FF0000 + diff --git a/src/RepoM.App/Resources/Theme/Light.xaml b/src/RepoM.App/Resources/Theme/Light.xaml new file mode 100644 index 00000000..a0a87263 --- /dev/null +++ b/src/RepoM.App/Resources/Theme/Light.xaml @@ -0,0 +1,664 @@ + + + #FFFAFAFA + + + #BE000000 + + + + + #E4000000 + #9E000000 + #72000000 + #5C000000 + #9E000000 + #FFFFFF + + #5C000000 + #FFFFFF + #FFFFFF + #B3FFFFFF + #A3FFFFFF + + #B3FFFFFF + #80F9F9F9 + #4DF9F9F9 + #4DF9F9F9 + #00FFFFFF + #FFFFFF + + #72000000 + #51000000 + + #FFFFFF + + #00FFFFFF + #09000000 + #06000000 + #00FFFFFF + + #00FFFFFF + #06000000 + #0F000000 + #18000000 + #00FFFFFF + + #C9FFFFFF + #F3F3F3 + #EBEBEB + #00FFFFFF + + #37000000 + + #0F000000 + #29000000 + #9E000000 + #14FFFFFF + #66000000 + #37000000 + #0F000000 + + #59FFFFFF + + #0F000000 + #EBEBEB + + #72000000 + #37000000 + + #66757575 + #0F000000 + #15FFFFFF + + #0F000000 + + #E4000000 + #B3FFFFFF + + #B3FFFFFF + #80F6F6F6 + + #4D000000 + + #80FFFFFF + #FFFFFF + #40FFFFFF + #40FFFFFF + + + #F9F9F9 + + #B3FFFFFF + #0A000000 + #F9F9F9 + #00000000 + + #F3F3F3 + #EEEEEE + #F9F9F9 + #FFFFFF + #00F3F3F3 + #DADADA + + + #0078d4 + #8A8A8A + #0F7B0F + #9D5D00 + #C42B1C + #72000000 + #8A8A8A + #80F6F6F6 + #DFF6DD + #FFF4CE + #FDE7E9 + #06000000 + #F7F7F7 + #F3F3F3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RepoM.App/Resources/Typography.xaml b/src/RepoM.App/Resources/Typography.xaml new file mode 100644 index 00000000..47fb3a30 --- /dev/null +++ b/src/RepoM.App/Resources/Typography.xaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/RepoM.App/Resources/Variables.xaml b/src/RepoM.App/Resources/Variables.xaml new file mode 100644 index 00000000..7ccc7f46 --- /dev/null +++ b/src/RepoM.App/Resources/Variables.xaml @@ -0,0 +1,44 @@ + + + 16 + + 14 + 4,4,4,4 + 4,4,4,4 + 8,8,8,8 + + + + 1 + 2 + 10,8,10,7 + + 14 + 24 + 0 + 32 + 24 + 24 + 0 + 0 + + 0,1,0,2 + 0,1,0,2 + 9,0,0,1 + 10,0,30,0 + + 24 + 12,1,0,3 + 32 + + diff --git a/src/RepoM.App/Services/AcrylicHelper.cs b/src/RepoM.App/Services/AcrylicHelper.cs deleted file mode 100644 index bd2dc61d..00000000 --- a/src/RepoM.App/Services/AcrylicHelper.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace RepoM.App.Services; - -using System.Windows; -using System.Windows.Interop; -using System.Windows.Media; - -public static class AcrylicHelper -{ - public static void EnableBlur(Visual visual) - { - if (PresentationSource.FromVisual(visual) is HwndSource hwnd) - { - WindowsCompositionHelper.EnableBlur(hwnd.Handle); - } - } -} \ No newline at end of file diff --git a/src/RepoM.App/Services/HotKey/HotKeyWindowsRegistration.cs b/src/RepoM.App/Services/HotKey/HotKeyWindowsRegistration.cs deleted file mode 100644 index 7fd3ebd5..00000000 --- a/src/RepoM.App/Services/HotKey/HotKeyWindowsRegistration.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace RepoM.App.Services.HotKey; - -using System; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Interop; - -internal partial class HotKeyWindowsRegistration -{ - [LibraryImport("User32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool RegisterHotKey( - IntPtr hWnd, - int id, - uint fsModifiers, - uint vk); - - [LibraryImport("User32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool UnregisterHotKey( - IntPtr hWnd, - int id); - - public const uint VK_R = 0x52; - public const uint MOD_ALT = 0x0001; - public const uint MOD_CTRL = 0x0002; - public const uint MOD_SHIFT = 0x0004; - public const uint MOD_WIN = 0x0008; - - private IntPtr _handle; - private Action? _hotKeyActionToCall; - private readonly int _id; - - public HotKeyWindowsRegistration(int id) - { - _id = id; - } - - public void Register(Window window, uint key, uint modifiers, Action hotKeyActionToCall) - { - var helper = new WindowInteropHelper(window); - _handle = helper.EnsureHandle(); - _hotKeyActionToCall = hotKeyActionToCall; - - var source = HwndSource.FromHwnd(_handle); - source?.AddHook(HwndHook); - - if (!RegisterHotKey(_handle, _id, modifiers, key)) - { - // handle error - } - } - - public void Unregister() - { - UnregisterHotKey(_handle, _id); - } - - private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - const int WM_HOT_KEY = 0x0312; - switch (msg) - { - case WM_HOT_KEY: - if (wParam.ToInt32() == _id) - { - _hotKeyActionToCall?.Invoke(); - handled = true; - } - - break; - } - - return IntPtr.Zero; - } -} \ No newline at end of file diff --git a/src/RepoM.App/Services/HotKeyService.cs b/src/RepoM.App/Services/HotKeyService.cs new file mode 100644 index 00000000..6f5b920e --- /dev/null +++ b/src/RepoM.App/Services/HotKeyService.cs @@ -0,0 +1,97 @@ +namespace RepoM.App.Services; + +using System; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using Microsoft.Extensions.Logging; +using Key = System.Windows.Input.Key; + +internal partial class HotKeyService +{ + private const int HOT_KEY_ID = 47110815; + + private readonly Action? _hotKeyActionToCall; + private readonly IntPtr _hotKeyHook; + private readonly ILogger _logger; + private readonly MainWindow _mainWindow; + + public HotKeyService(MainWindow mainWindow, ILogger logger) + { + _mainWindow = mainWindow; + _logger = logger; + _hotKeyActionToCall = OnHotKeyPressed; // This is the function that will ultimately be called when the hotkey is pressed + + var helper = new WindowInteropHelper(_mainWindow); // This is the window that will receive the hotkey message + _hotKeyHook = helper.EnsureHandle(); // This is the handle of the window that will receive the hotkey message + + var source = HwndSource.FromHwnd(_hotKeyHook); // This is the source of the window that will receive the hotkey message + source?.AddHook(HwndHook); // This is the proxy function that will be called when the hotkey message is received + } + + [LibraryImport("User32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool RegisterHotKey(IntPtr hWnd, + int id, + uint fsModifiers, + uint vk); + + [LibraryImport("User32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool UnregisterHotKey(IntPtr hWnd, + int id); + + public void Register() + { + EnsureWindowHandle(); + + var successHotKeyRegistration = RegisterHotKey(_hotKeyHook, HOT_KEY_ID, (uint)(ModifierKeys.Shift | ModifierKeys.Control), (uint)KeyInterop.VirtualKeyFromKey(Key.R)); + if (successHotKeyRegistration) + { + _logger.LogInformation("Hotkey registered successfully"); + } + else + { + _logger.LogError("Hotkey registration failed"); + } + } + + public void Unregister() + { + UnregisterHotKey(_hotKeyHook, HOT_KEY_ID); + } + + private void EnsureWindowHandle() + { + // We noticed that the hotkey registration at app start causes a high CPU utilization if the main window was not shown before. + // To fix this, we need to make the window visible. However, to prevent flickering we move the window out of the screen bounds to show and hide it. + _mainWindow.SetCurrentValue(Window.LeftProperty, -9999.0); + _mainWindow.Show(); + _mainWindow.Hide(); + // Make sure you run PlaceFormByTaskBarLocation() after the window is hidden again. + } + + private void OnHotKeyPressed() + { + _mainWindow.ShowAndActivate(); + } + + private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + const int WM_HOT_KEY = 0x0312; + switch (msg) + { + case WM_HOT_KEY: + if (wParam.ToInt32() == HOT_KEY_ID) + { + _hotKeyActionToCall?.Invoke(); + handled = true; + } + + break; + } + + return IntPtr.Zero; + } +} \ No newline at end of file diff --git a/src/RepoM.App/Services/Hotkey/HotKeyService.cs b/src/RepoM.App/Services/Hotkey/HotKeyService.cs deleted file mode 100644 index 47ce26b3..00000000 --- a/src/RepoM.App/Services/Hotkey/HotKeyService.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace RepoM.App.Services.HotKey; - -using System; -using System.Windows; - -internal class HotKeyService -{ - private readonly MainWindow _mainWindow; - private HotKeyWindowsRegistration? _hotKey; - - public HotKeyService(MainWindow mainWindow) - { - _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); - } - - public void Register() - { - // We noticed that the hotkey registration causes a high CPU utilization if the window was not shown before. - // To fix this, we need to make the window visible in EnsureWindowHandle() but we set the opacity to 0.0 to prevent flickering - EnsureWindowHandle(_mainWindow); - - _hotKey = new HotKeyWindowsRegistration(47110815); - _hotKey.Register(_mainWindow, HotKeyWindowsRegistration.VK_R, HotKeyWindowsRegistration.MOD_ALT | HotKeyWindowsRegistration.MOD_CTRL, OnHotKeyPressed); - } - - public void Unregister() - { - _hotKey?.Unregister(); - } - - private static void EnsureWindowHandle(Window window) - { - // We noticed that the hotkey registration at app start causes a high CPU utilization if the main window was not shown before. - // To fix this, we need to make the window visible. However, to prevent flickering we move the window out of the screen bounds to show and hide it. - window.Left = -9999; - window.Show(); - window.Hide(); - } - - private void OnHotKeyPressed() - { - _mainWindow.ShowAndActivate(); - } -} \ No newline at end of file diff --git a/src/RepoM.App/Services/TaskBarLocator.cs b/src/RepoM.App/Services/TaskBarLocator.cs deleted file mode 100644 index d2d10b55..00000000 --- a/src/RepoM.App/Services/TaskBarLocator.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace RepoM.App.Services; - -using System.Windows.Forms; - -public static class TaskBarLocator -{ - public enum TaskBarLocation - { - Top, - Bottom, - Left, - Right, - } - - public static TaskBarLocation GetTaskBarLocation(Screen? primaryScreen) - { - if (primaryScreen == null) - { - return TaskBarLocation.Bottom; - } - - var taskBarOnTopOrBottom = primaryScreen.WorkingArea.Width == primaryScreen.Bounds.Width; - - if (taskBarOnTopOrBottom) - { - return primaryScreen.WorkingArea.Top > 0 - ? TaskBarLocation.Top - : TaskBarLocation.Bottom; - } - - return primaryScreen.WorkingArea.Left > 0 - ? TaskBarLocation.Left - : TaskBarLocation.Right; - } -} \ No newline at end of file diff --git a/src/RepoM.App/Services/WindowSizeService.cs b/src/RepoM.App/Services/WindowSizeService.cs index fa08cb0c..12713127 100644 --- a/src/RepoM.App/Services/WindowSizeService.cs +++ b/src/RepoM.App/Services/WindowSizeService.cs @@ -13,108 +13,114 @@ namespace RepoM.App.Services; internal class WindowSizeService : IDisposable { - private const string UNKNOWN_RESOLUTION = "unknown"; - private volatile string _currentResolution = UNKNOWN_RESOLUTION; - private readonly Window _mainWindow; - private readonly IAppSettingsService _appSettings; - private readonly ILogger _logger; - private IDisposable? _registrationWindowSizeChanged; - private IDisposable? _registrationDisplaySettingsChanged; - private readonly SynchronizationContext _uiDispatcher; - protected static readonly TimeSpan ThrottleWindowSizeChanged = TimeSpan.FromSeconds(5); - protected static readonly TimeSpan ThrottleDisplaySettingsChanged = TimeSpan.FromSeconds(1); + private const string UNKNOWN_RESOLUTION = "unknown"; + protected static readonly TimeSpan ThrottleWindowSizeChanged = TimeSpan.FromSeconds(5); + private static readonly TimeSpan _throttleDisplaySettingsChanged = TimeSpan.FromSeconds(1); + private readonly IAppSettingsService _appSettings; + private readonly ILogger _logger; + private readonly Window _mainWindow; + private readonly SynchronizationContext _uiDispatcher; + private volatile string _currentResolution = UNKNOWN_RESOLUTION; + private IDisposable? _registrationDisplaySettingsChanged; + private IDisposable? _registrationWindowSizeChanged; public WindowSizeService(Window mainWindow, IAppSettingsService appSettings, IThreadDispatcher threadDispatcher, ILogger logger) { - _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); + _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); _appSettings = appSettings ?? throw new ArgumentNullException(nameof(appSettings)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); ArgumentNullException.ThrowIfNull(threadDispatcher); _uiDispatcher = threadDispatcher.SynchronizationContext; } + public void Dispose() + { + _registrationWindowSizeChanged?.Dispose(); + _registrationWindowSizeChanged = null; + _registrationDisplaySettingsChanged?.Dispose(); + _registrationDisplaySettingsChanged = null; + } + public void Register() { _currentResolution = GetResolution(); - + if (_appSettings.TryGetMenuSize(_currentResolution, out MenuSize? size)) { - _mainWindow.Width = size.Value.MenuWidth; - _mainWindow.Height = size.Value.MenuHeight; + _mainWindow.SetCurrentValue(FrameworkElement.WidthProperty, size.Value.MenuWidth); + _mainWindow.SetCurrentValue(FrameworkElement.HeightProperty, size.Value.MenuHeight); } else { _appSettings.UpdateMenuSize( - _currentResolution, - new MenuSize - { - MenuHeight = _mainWindow.Height, - MenuWidth = _mainWindow.Width, - }); + _currentResolution, + new MenuSize + { + MenuHeight = _mainWindow.Height, + MenuWidth = _mainWindow.Width, + }); } - + _registrationDisplaySettingsChanged = Observable - .FromEventPattern( - handler => SystemEvents.DisplaySettingsChanged += handler, - handler => SystemEvents.DisplaySettingsChanged -= handler) - .ObserveOn(Scheduler.Default) - .Throttle(ThrottleDisplaySettingsChanged) - .Select(eventPattern => - { - try - { - // update resolution in select is not very nice. - _currentResolution = GetResolution(); + .FromEventPattern( + handler => SystemEvents.DisplaySettingsChanged += handler, + handler => SystemEvents.DisplaySettingsChanged -= handler) + .ObserveOn(Scheduler.Default) + .Throttle(_throttleDisplaySettingsChanged) + .Select(eventPattern => + { + try + { + // update resolution in select is not very nice. + _currentResolution = GetResolution(); - _ = _appSettings.TryGetMenuSize(_currentResolution, out MenuSize? menuSize); - return menuSize; - } - catch (Exception e) - { - _logger.LogError(e, "Could not get resolution or menu for current screen."); - return null; - } - }) - .Where(menuSize => menuSize.HasValue) - .Select(menuSize => menuSize!.Value) - .ObserveOn(_uiDispatcher) // Accessing the mainWindow should be done from UI thread. - .Where(menuSize => - { - try - { - return Math.Abs(_mainWindow.Width - menuSize.MenuWidth) > 0.001 - || - Math.Abs(_mainWindow.Height - menuSize.MenuHeight) > 0.001; - } - catch (Exception e) - { - _logger.LogError(e, "Could not determine if window size has changed."); - return true; - } + _ = _appSettings.TryGetMenuSize(_currentResolution, out MenuSize? menuSize); + return menuSize; + } + catch (Exception e) + { + _logger.LogError(e, "Could not get resolution or menu for current screen."); + return null; + } + }) + .Where(menuSize => menuSize.HasValue) + .Select(menuSize => menuSize!.Value) + .ObserveOn(_uiDispatcher) // Accessing the mainWindow should be done from UI thread. + .Where(menuSize => + { + try + { + return Math.Abs(_mainWindow.Width - menuSize.MenuWidth) > 0.001 + || Math.Abs(_mainWindow.Height - menuSize.MenuHeight) > 0.001; + } + catch (Exception e) + { + _logger.LogError(e, "Could not determine if window size has changed."); + return true; + } + }) + .Subscribe(menuSize => + { + _mainWindow.SetCurrentValue(FrameworkElement.WidthProperty, menuSize!.MenuWidth); + _mainWindow.SetCurrentValue(FrameworkElement.HeightProperty, menuSize!.MenuHeight); + }); - }) - .Subscribe(menuSize => - { - _mainWindow.Width = menuSize!.MenuWidth; - _mainWindow.Height = menuSize!.MenuHeight; - }); - _registrationWindowSizeChanged = Observable - .FromEventPattern( - handler => _mainWindow.SizeChanged += handler, - handler => _mainWindow.SizeChanged -= handler) - .ObserveOn(Scheduler.Default) - .Throttle(ThrottleWindowSizeChanged) - .Subscribe(sizeChangedEvent => - { - _appSettings.UpdateMenuSize( - _currentResolution, // Yes, This possibliy can go wrong - new MenuSize - { - MenuHeight = sizeChangedEvent.EventArgs.NewSize.Height, - MenuWidth = sizeChangedEvent.EventArgs.NewSize.Width, - }); - }); + .FromEventPattern( + handler => _mainWindow.SizeChanged += handler, + handler => _mainWindow.SizeChanged -= handler) + .ObserveOn(Scheduler.Default) + .Throttle(ThrottleWindowSizeChanged) + .Subscribe(sizeChangedEvent => + { + _appSettings.UpdateMenuSize( + _currentResolution, // Yes, This possibility can go wrong + new MenuSize + { + MenuHeight = sizeChangedEvent.EventArgs.NewSize.Height, + MenuWidth = sizeChangedEvent.EventArgs.NewSize.Width, + }); + }); _currentResolution = GetResolution(); } @@ -124,18 +130,11 @@ public void Unregister() Dispose(); } - public void Dispose() - { - _registrationWindowSizeChanged?.Dispose(); - _registrationWindowSizeChanged = null; - _registrationDisplaySettingsChanged?.Dispose(); - _registrationDisplaySettingsChanged = null; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] protected virtual string GetResolution() { Screen? screen = Screen.PrimaryScreen; return screen == null ? UNKNOWN_RESOLUTION : $"{screen.Bounds.Width}x{screen.Bounds.Height}"; } -} \ No newline at end of file +} + diff --git a/src/RepoM.App/Services/WindowsCompositionHelper.cs b/src/RepoM.App/Services/WindowsCompositionHelper.cs deleted file mode 100644 index 155e9ba7..00000000 --- a/src/RepoM.App/Services/WindowsCompositionHelper.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace RepoM.App.Services; - -using System; -using System.Drawing.Printing; -using System.Runtime.InteropServices; - -public static class WindowsCompositionHelper -{ - [DllImport("user32.dll")] - private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); - - [DllImport("dwmapi.dll", PreserveSig = true)] - private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize); - - [DllImport("dwmapi.dll")] - private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMarInset); - - public static void EnableBlur(IntPtr hwnd) - { - try - { - var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(accent); - accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; - - IntPtr accentPtr = Marshal.AllocHGlobal(accentStructSize); - Marshal.StructureToPtr(accent, accentPtr, false); - - var data = new WindowCompositionAttributeData - { - Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY, - SizeOfData = accentStructSize, - Data = accentPtr, - }; - - _ = SetWindowCompositionAttribute(hwnd, ref data); - - Marshal.FreeHGlobal(accentPtr); - } - catch (Exception) - { - // don't do anything in case this did not work. We won't have blur then ... - } - } - - public static bool EnableDropShadow(IntPtr hwnd, Margins margins) - { - // workaround for unused param. decided to keep param - _ = margins; - - try - { - var val = 2; - var ret1 = DwmSetWindowAttribute(hwnd, 2, ref val, 4); - - if (ret1 == 0) - { - var m = new Margins - { - Bottom = 0, - Left = 0, - Right = 0, - Top = 0, - }; - var ret2 = DwmExtendFrameIntoClientArea(hwnd, ref m); - return ret2 == 0; - } - else - { - return false; - } - } - catch (Exception) - { - return false; - } - } -} - -internal enum AccentState -{ - ACCENT_DISABLED = 1, - ACCENT_ENABLE_GRADIENT = 0, - ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, - ACCENT_ENABLE_BLURBEHIND = 3, - ACCENT_INVALID_STATE = 4, -} - -[StructLayout(LayoutKind.Sequential)] -internal struct AccentPolicy -{ - public AccentState AccentState; - public int AccentFlags; - public int GradientColor; - public int AnimationId; -} - -[StructLayout(LayoutKind.Sequential)] -internal struct WindowCompositionAttributeData -{ - public WindowCompositionAttribute Attribute; - public IntPtr Data; - public int SizeOfData; -} - -internal enum WindowCompositionAttribute -{ - WCA_ACCENT_POLICY = 19, -} \ No newline at end of file diff --git a/src/RepoM.App/ViewModels/MenuItemViewModel.cs b/src/RepoM.App/ViewModels/MenuItemViewModel.cs index 8e338602..3561581a 100644 --- a/src/RepoM.App/ViewModels/MenuItemViewModel.cs +++ b/src/RepoM.App/ViewModels/MenuItemViewModel.cs @@ -4,9 +4,8 @@ namespace RepoM.App.ViewModels; using System.ComponentModel; using System.Runtime.CompilerServices; -// https://stackoverflow.com/questions/5912687/styling-contextmenu-and-contextmenu-items // https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/contextmenu-styles-and-templates?view=netframeworkdesktop-4.8 -// https://itecnote.com/tecnote/wpf-how-to-bind-an-observablecollection-of-viewmodels-to-a-menuitem/ + public class MenuItemViewModel : INotifyPropertyChanged { public string Header { get; set; } = string.Empty; @@ -33,4 +32,4 @@ protected bool SetField(ref T field, T value, [CallerMemberName] string? prop OnPropertyChanged(propertyName); return true; } -} \ No newline at end of file +} diff --git a/src/RepoM.App/i18n/de-DE.xaml b/src/RepoM.App/i18n/de-DE.xaml index 3ccb9a71..800bfa5e 100644 --- a/src/RepoM.App/i18n/de-DE.xaml +++ b/src/RepoM.App/i18n/de-DE.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> Suche - 🔍 Suche + Suche Hier gibt es noch keine Repositories 😐 Navigiere zu einem Git Repository und wechsle den aktuellen Branch oder ändere eine Datei. RepoM wird die Änderung erkennen und das Repository hier aufführen. @@ -10,8 +10,8 @@ Navigiere zu einem Git Repository und wechsle den aktuellen Branch oder ändere Alternativ, kann der Computer auch über das Menü oben rechts nach Repositories durchsucht werden. Hilfe Über GitHub sponsern - Twitter-Follower werden: @Waescher - Einen GitHub-Stern spendieren + Einen GitHub-Stern spendieren + About Danke sagen ♥︎ Aggresiv Adäquat diff --git a/src/RepoM.App/i18n/en-us.xaml b/src/RepoM.App/i18n/en-us.xaml index b76dd9ac..a23c83aa 100644 --- a/src/RepoM.App/i18n/en-us.xaml +++ b/src/RepoM.App/i18n/en-us.xaml @@ -1,8 +1,8 @@ - Search - 🔍 Search + Search There are no repositories yet 😐 @@ -11,9 +11,9 @@ Go ahead and switch a branch or change a file in any repository on your computer Alternatively, you can scan your computer manually for repositories in the settings menu on the top right. Help Sponsor me on GitHub - Follow me on Twitter: @Waescher - Star RepoM on GitHub - Ping back ♥︎ + Star RepoM on GitHub + About + Ping back ♥︎ Aggressive Adequate Discretely diff --git a/src/RepoM.App/i18n/nl-NL.xaml b/src/RepoM.App/i18n/nl-NL.xaml index 2b62085a..ecc05874 100644 --- a/src/RepoM.App/i18n/nl-NL.xaml +++ b/src/RepoM.App/i18n/nl-NL.xaml @@ -1,8 +1,8 @@ - Zoeken - 🔍 Zoeken + Zoeken Er zijn nog geen repositories gevonden 😐 @@ -11,9 +11,9 @@ Verander van Git branch, of pas een bestand aan in een repository op de computer Handmatig zoeken naar Git repositories is ook mogelijk via het instellingen menu rechts boven. Help Sponsor mij op GitHub - Volg mij op Twitter: @Waescher - Geef RepoM een ster op GitHub - Ping back ♥︎ + Geef RepoM een ster op GitHub + About + Ping back ♥︎ Agressief Adequaat Discreet diff --git a/src/RepoM.App/i18n/zh-cn.xaml b/src/RepoM.App/i18n/zh-cn.xaml index fe79e844..f6127d26 100644 --- a/src/RepoM.App/i18n/zh-cn.xaml +++ b/src/RepoM.App/i18n/zh-cn.xaml @@ -1,8 +1,8 @@ - 搜索 - 🔍 搜索 + 搜索 还没有任何仓库记录😐 在计算机上的任何仓库中切换分支以使其显示在此处。 @@ -10,8 +10,8 @@ 或者,您可以手动扫描计算机中的仓库(右上角菜单)。 帮助 捐赠 - 关注 @Waescher 在 GitHub 上 为RepoM 点赞 + About 反馈 ♥︎ 积极 适中 diff --git a/src/RepoM.Plugin.Statistics/Ordering/LastOpenedComparer.cs b/src/RepoM.Plugin.Statistics/Ordering/LastOpenedComparer.cs index ab1312b3..0dd93100 100644 --- a/src/RepoM.Plugin.Statistics/Ordering/LastOpenedComparer.cs +++ b/src/RepoM.Plugin.Statistics/Ordering/LastOpenedComparer.cs @@ -61,6 +61,7 @@ private DateTime GetLast(IRepository repository) { IReadOnlyList items = _service.GetRecordings(repository); + // TODO lots of crashes here return items.Count == 0 ? DateTime.MinValue : items.MaxBy(x => x); diff --git a/src/RepoM.Plugin.Statistics/RepositoryStatistics.cs b/src/RepoM.Plugin.Statistics/RepositoryStatistics.cs index c935df8f..da35757f 100644 --- a/src/RepoM.Plugin.Statistics/RepositoryStatistics.cs +++ b/src/RepoM.Plugin.Statistics/RepositoryStatistics.cs @@ -39,6 +39,7 @@ int IReadOnlyRepositoryStatistics.GetRecordingCount(DateTime from, DateTime to) int IReadOnlyRepositoryStatistics.GetRecordingCountFrom(DateTime from) { + // TODO consant errors from here. return Recordings.Count(recordingDate => recordingDate >= from); } diff --git a/tests/RepoM.App.Tests/Services/TaskBarLocatorTests.cs b/tests/RepoM.App.Tests/Services/TaskBarLocatorTests.cs deleted file mode 100644 index 570c138c..00000000 --- a/tests/RepoM.App.Tests/Services/TaskBarLocatorTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace RepoM.App.Tests.Services; - -using FluentAssertions; -using RepoM.App.Services; -using Xunit; - -public class TaskBarLocatorTests -{ - [Fact] - public void GetTaskBarLocation_ShouldReturnBottom_WhenNoScreenGiven() - { - // arrange - - // act - TaskBarLocator.TaskBarLocation result = TaskBarLocator.GetTaskBarLocation(null); - - // assert - result.Should().Be(TaskBarLocator.TaskBarLocation.Bottom); - } -} \ No newline at end of file diff --git a/tests/RepoM.App.Tests/Services/WindowSizeServiceTests.cs b/tests/RepoM.App.Tests/Services/WindowSizeServiceTests.cs index 00e31060..974af1e3 100644 --- a/tests/RepoM.App.Tests/Services/WindowSizeServiceTests.cs +++ b/tests/RepoM.App.Tests/Services/WindowSizeServiceTests.cs @@ -143,7 +143,7 @@ public void Register_ShouldSubscribeAndHandleSizeChangedEvents() } - public void Dispose() + void IDisposable.Dispose() { try {