diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 7ead7459f..33072b53d 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -52,6 +52,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public double SettingWindowHeight { get; set; } = 700; public double SettingWindowTop { get; set; } public double SettingWindowLeft { get; set; } + public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal; public int CustomExplorerIndex { get; set; } = 0; diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 813a44527..1e7f70b0a 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -98,6 +98,7 @@ + @@ -120,7 +121,7 @@ - + diff --git a/Flow.Launcher/Resources/Dark.xaml b/Flow.Launcher/Resources/Dark.xaml index 445e07c63..674e04deb 100644 --- a/Flow.Launcher/Resources/Dark.xaml +++ b/Flow.Launcher/Resources/Dark.xaml @@ -69,6 +69,8 @@ + #272727 + #202020 #2b2b2b #1d1d1d diff --git a/Flow.Launcher/Resources/Light.xaml b/Flow.Launcher/Resources/Light.xaml index cee8b63cd..8b04196dd 100644 --- a/Flow.Launcher/Resources/Light.xaml +++ b/Flow.Launcher/Resources/Light.xaml @@ -62,6 +62,8 @@ + #f6f6f6 + #f3f3f3 #ffffff #e5e5e5 diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index 3c0905aa7..950694db5 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -11,6 +11,7 @@ xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure" xmlns:vm="clr-namespace:Flow.Launcher.ViewModel" + xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel" Title="{DynamicResource flowlauncher_settings}" Width="{Binding SettingWindowWidth, Mode=TwoWay}" Height="{Binding SettingWindowHeight, Mode=TwoWay}" @@ -47,7 +48,25 @@ + + + + + + + - + - + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + @@ -1763,7 +1683,9 @@ + ScrollViewer.CanContentScroll="True" + VirtualizingStackPanel.IsVirtualizing="True" + VirtualizingStackPanel.ScrollUnit="Pixel"> @@ -2339,14 +2261,16 @@ + ScrollViewer.CanContentScroll="True" + VirtualizingStackPanel.IsVirtualizing="True" + VirtualizingStackPanel.ScrollUnit="Pixel"> - + @@ -2510,7 +2434,7 @@ Click="OnAddCustomHotkeyClick" Content="{DynamicResource add}" /> - + + ScrollViewer.CanContentScroll="True" + VirtualizingStackPanel.IsVirtualizing="True" + VirtualizingStackPanel.ScrollUnit="Pixel"> @@ -2805,7 +2731,9 @@ + ScrollViewer.CanContentScroll="True" + VirtualizingStackPanel.IsVirtualizing="True" + VirtualizingStackPanel.ScrollUnit="Pixel"> (DependencyObject child) where T : DependencyObject { - _ = viewModel.RefreshExternalPluginsAsync(); - } + //get parent item + DependencyObject parentObject = VisualTreeHelper.GetParent(child); + //we've reached the end of the tree + if (parentObject == null) return null; + + //check if the parent matches the type we're looking for + T parent = parentObject as T; + if (parent != null) + return parent; + else + return FindParent(parentObject); + } + private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e) { - if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) + if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button) { - viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); + return; } + + if (storeClickedButton != null) + { + FlyoutService.GetFlyout(storeClickedButton).Hide(); + } + + viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); } private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e) @@ -317,18 +337,30 @@ namespace Flow.Launcher viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); } + } private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e) { + if (storeClickedButton != null) + { + FlyoutService.GetFlyout(storeClickedButton).Hide(); + } + if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) viewModel.DisplayPluginQuery($"uninstall {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); + } private void OnExternalPluginUpdateClick(object sender, RoutedEventArgs e) { + if (storeClickedButton != null) + { + FlyoutService.GetFlyout(storeClickedButton).Hide(); + } if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) viewModel.DisplayPluginQuery($"update {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); + } private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */ @@ -522,6 +554,7 @@ namespace Flow.Launcher Top = WindowTop(); Left = WindowLeft(); } + WindowState = settings.SettingWindowState; } public double WindowLeft() { @@ -541,5 +574,21 @@ namespace Flow.Launcher return top; } + private Button storeClickedButton; + + private void StoreListItem_Click(object sender, RoutedEventArgs e) + { + if (sender is not Button button) + return; + + storeClickedButton = button; + + var flyout = FlyoutService.GetFlyout(button); + flyout.Closed += (_, _) => + { + storeClickedButton = null; + }; + + } } } diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 63b9c29c9..fa6520523 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -20,10 +20,11 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedModels; using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.Input; namespace Flow.Launcher.ViewModel { - public class SettingWindowViewModel : BaseModel + public partial class SettingWindowViewModel : BaseModel { private readonly Updater _updater; private readonly IPortable _portable; @@ -330,7 +331,8 @@ namespace Flow.Launcher.ViewModel } } - public async Task RefreshExternalPluginsAsync() + [RelayCommand] + private async Task RefreshExternalPluginsAsync() { await PluginsManifest.UpdateManifestAsync(); OnPropertyChanged(nameof(ExternalPlugins)); @@ -535,6 +537,8 @@ namespace Flow.Launcher.ViewModel var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.StreamSource = memStream; + bitmap.DecodePixelWidth = 800; + bitmap.DecodePixelHeight = 600; bitmap.EndInit(); var brush = new ImageBrush(bitmap) { diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index ad7387f10..316aaaac3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -601,92 +601,97 @@ namespace Flow.Launcher.Plugin.Program.Programs // windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size // windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx - string path; - if (uri.Contains("\\")) - { - path = Path.Combine(Package.Location, uri); - } - else + string path = Path.Combine(Package.Location, uri); + + var logoPath = TryToFindLogo(uri, path); + if (String.IsNullOrEmpty(logoPath)) { + // TODO: Don't know why, just keep it at the moment + // Maybe on older version of Windows 10? // for C:\Windows\MiracastView etc - path = Path.Combine(Package.Location, "Assets", uri); + return TryToFindLogo(uri, Path.Combine(Package.Location, "Assets", uri)); } + return logoPath; - var extension = Path.GetExtension(path); - if (extension != null) + string TryToFindLogo(string uri, string path) { - var end = path.Length - extension.Length; - var prefix = path.Substring(0, end); - var paths = new List + var extension = Path.GetExtension(path); + if (extension != null) { - path - }; + //if (File.Exists(path)) + //{ + // return path; // shortcut, avoid enumerating files + //} - var scaleFactors = new Dictionary> - { - // scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables, + var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44 + var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets + if (String.IsNullOrEmpty(logoNamePrefix) || String.IsNullOrEmpty(logoDir) || !Directory.Exists(logoDir)) { - PackageVersion.Windows10, new List - { - 100, - 125, - 150, - 200, - 400 - } - }, + // Known issue: Edge always triggers it since logo is not at uri + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + + $"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Package.Location}", new FileNotFoundException()); + return string.Empty; + } + + var files = Directory.EnumerateFiles(logoDir); + + // Currently we don't care which one to choose + // Just ignore all qualifiers + // select like logo.[xxx_yyy].png + // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast + var logos = files.Where(file => + Path.GetFileName(file)?.StartsWith(logoNamePrefix, StringComparison.OrdinalIgnoreCase) ?? false + && extension.Equals(Path.GetExtension(file), StringComparison.OrdinalIgnoreCase) + ); + + var selected = logos.FirstOrDefault(); + var closest = selected; + int min = int.MaxValue; + foreach(var logo in logos) { - PackageVersion.Windows81, new List + + var imageStream = File.OpenRead(logo); + var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None); + var height = decoder.Frames[0].PixelHeight; + var width = decoder.Frames[0].PixelWidth; + int pixelCountDiff = Math.Abs(height * width - 1936); // 44*44=1936 + if(pixelCountDiff < min) { - 100, - 120, - 140, - 160, - 180 - } - }, - { - PackageVersion.Windows8, new List - { - 100 + // try to find the closest to 44x44 logo + closest = logo; + if (pixelCountDiff == 0) + break; // found 44x44 + min = pixelCountDiff; } } - }; - if (scaleFactors.ContainsKey(Package.Version)) - { - foreach (var factor in scaleFactors[Package.Version]) + selected = closest; + if (!string.IsNullOrEmpty(selected)) { - paths.Add($"{prefix}.scale-{factor}{extension}"); + return selected; + } + else + { + ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + + $"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Package.Location}", new FileNotFoundException()); + return string.Empty; } - } - - var selected = paths.FirstOrDefault(File.Exists); - if (!string.IsNullOrEmpty(selected)) - { - return selected; } else { ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + - $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException()); + $"|Unable to find extension from {uri} for {UserModelId} " + + $"in package location {Package.Location}", new FileNotFoundException()); return string.Empty; } } - else - { - ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + - $"|Unable to find extension from {uri} for {UserModelId} " + - $"in package location {Package.Location}", new FileNotFoundException()); - return string.Empty; - } } public ImageSource Logo() { var logo = ImageFromPath(LogoPath); - var plated = PlatedImage(logo); + var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package? // todo magic! temp fix for cross thread object plated.Freeze(); diff --git a/appveyor.yml b/appveyor.yml index a71779fdc..d467792d7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,10 @@ init: - sc config WSearch start= auto # Starts Windows Search service- Needed for running ExplorerTest - net start WSearch +cache: + - '%USERPROFILE%\.nuget\packages -> **.sln, **.csproj' # preserve nuget folder (packages) unless the solution or projects change + + assembly_info: patch: true file: SolutionAssemblyInfo.cs @@ -28,7 +32,9 @@ before_build: build: project: Flow.Launcher.sln verbosity: minimal -after_build: +test_script: + - dotnet test --no-build -c Release +after_test: - ps: .\Scripts\post_build.ps1 artifacts: