Refactor with MVVM

This commit is contained in:
Jack251970 2025-05-21 12:58:55 +08:00
parent d0f0edb603
commit 6ce2cf9cac
8 changed files with 264 additions and 203 deletions

View file

@ -98,6 +98,10 @@ namespace Flow.Launcher
.AddTransient<SettingsPanePluginStoreViewModel>()
.AddTransient<SettingsPaneProxyViewModel>()
.AddTransient<SettingsPaneThemeViewModel>()
// Use transient instance for dialog view models because
// settings will change and we need to recreate them
.AddTransient<SelectBrowserViewModel>()
.AddTransient<SelectFileManagerViewModel>()
).Build();
Ioc.Default.ConfigureServices(host.Services);
}

View file

@ -6,10 +6,11 @@
xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Title="{DynamicResource defaultBrowserTitle}"
Width="550"
d:DataContext="{d:DesignInstance vm:SelectBrowserViewModel}"
Background="{DynamicResource PopuBGColor}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="Height"
@ -97,11 +98,11 @@
</ComboBox>
<Button
Margin="10 0 0 0"
Click="btnAdd_Click"
Command="{Binding AddCommand}"
Content="{DynamicResource add}" />
<Button
Margin="10 0 0 0"
Click="btnDelete_Click"
Command="{Binding DeleteCommand}"
Content="{DynamicResource delete}"
IsEnabled="{Binding CustomBrowser.Editable}" />

View file

@ -1,38 +1,18 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using Flow.Launcher.Infrastructure.UserSettings;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher
{
[INotifyPropertyChanged]
public partial class SelectBrowserWindow : Window
{
private readonly Settings _settings;
private readonly SelectBrowserViewModel _viewModel;
private int selectedCustomBrowserIndex;
public int SelectedCustomBrowserIndex
public SelectBrowserWindow()
{
get => selectedCustomBrowserIndex;
set
{
selectedCustomBrowserIndex = value;
OnPropertyChanged(nameof(CustomBrowser));
}
}
public ObservableCollection<CustomBrowserViewModel> CustomBrowsers { get; }
public CustomBrowserViewModel CustomBrowser => CustomBrowsers[SelectedCustomBrowserIndex];
public SelectBrowserWindow(Settings settings)
{
_settings = settings;
CustomBrowsers = new ObservableCollection<CustomBrowserViewModel>(_settings.CustomBrowserList.Select(x => x.Copy()));
SelectedCustomBrowserIndex = _settings.CustomBrowserIndex;
_viewModel = Ioc.Default.GetRequiredService<SelectBrowserViewModel>();
DataContext = _viewModel;
InitializeComponent();
}
@ -43,32 +23,19 @@ namespace Flow.Launcher
private void btnDone_Click(object sender, RoutedEventArgs e)
{
_settings.CustomBrowserList = CustomBrowsers.ToList();
_settings.CustomBrowserIndex = SelectedCustomBrowserIndex;
Close();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
CustomBrowsers.Add(new()
if (_viewModel.SaveSettings())
{
Name = "New Profile"
});
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
CustomBrowsers.RemoveAt(SelectedCustomBrowserIndex--);
Close();
}
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
var dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
{
TextBox path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
var path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
path.Text = dlg.FileName;
path.Focus();
((Button)sender).Focus();

View file

@ -6,10 +6,11 @@
xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Title="{DynamicResource fileManagerWindow}"
Width="600"
d:DataContext="{d:DesignInstance vm:SelectFileManagerViewModel}"
Background="{DynamicResource PopuBGColor}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="Height"
@ -78,7 +79,8 @@
x:Name="btnTips"
Margin="0 7 0 7"
HorizontalAlignment="Left"
Click="btnTips_Click"
Command="{Binding OpenFilesTipsCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Content="{DynamicResource fileManager_files_btn}" />
<TextBlock Margin="10 4 0 4" VerticalAlignment="Center">
<Hyperlink NavigateUri="https://www.flowlauncher.com/docs/#/filemanager" RequestNavigate="Hyperlink_RequestNavigate">
@ -115,11 +117,11 @@
</ComboBox>
<Button
Margin="10 0 0 0"
Click="btnAdd_Click"
Command="{Binding AddCommand}"
Content="{DynamicResource add}" />
<Button
Margin="10 0 0 0"
Click="btnDelete_Click"
Command="{Binding DeleteCommand}"
Content="{DynamicResource delete}"
IsEnabled="{Binding CustomExplorer.Editable}" />

View file

@ -1,46 +1,19 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.ComponentModel;
using Flow.Launcher.Infrastructure.UserSettings;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.ViewModel;
using ModernWpf.Controls;
namespace Flow.Launcher
{
[INotifyPropertyChanged]
public partial class SelectFileManagerWindow : Window
{
private readonly Settings _settings;
private readonly SelectFileManagerViewModel _viewModel;
private int selectedCustomExplorerIndex;
public int SelectedCustomExplorerIndex
public SelectFileManagerWindow()
{
get => selectedCustomExplorerIndex;
set
{
selectedCustomExplorerIndex = value;
OnPropertyChanged(nameof(CustomExplorer));
}
}
public ObservableCollection<CustomExplorerViewModel> CustomExplorers { get; }
public CustomExplorerViewModel CustomExplorer => CustomExplorers[SelectedCustomExplorerIndex];
public SelectFileManagerWindow(Settings settings)
{
_settings = settings;
CustomExplorers = new ObservableCollection<CustomExplorerViewModel>(_settings.CustomExplorerList.Select(x => x.Copy()));
SelectedCustomExplorerIndex = _settings.CustomExplorerIndex;
_viewModel = Ioc.Default.GetRequiredService<SelectFileManagerViewModel>();
DataContext = _viewModel;
InitializeComponent();
}
@ -48,133 +21,28 @@ namespace Flow.Launcher
{
Close();
}
private void btnDone_Click(object sender, RoutedEventArgs e)
{
if (_viewModel.SaveSettings())
{
Close();
}
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
App.API.OpenUrl(e.Uri.AbsoluteUri);
e.Handled = true;
}
private void btnDone_Click(object sender, RoutedEventArgs e)
{
// Check if the selected file manager path is valid
if (!IsFileManagerValid(CustomExplorer.Path))
{
var result = App.API.ShowMsgBox(
string.Format(App.API.GetTranslation("fileManagerPathNotFound"),
CustomExplorer.Name, CustomExplorer.Path),
App.API.GetTranslation("fileManagerPathError"),
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result == MessageBoxResult.No)
{
return;
}
}
_settings.CustomExplorerList = CustomExplorers.ToList();
_settings.CustomExplorerIndex = SelectedCustomExplorerIndex;
Close();
}
private bool IsFileManagerValid(string path)
{
if (string.Equals(path, "explorer", StringComparison.OrdinalIgnoreCase))
return true;
if (Path.IsPathRooted(path))
{
return File.Exists(path);
}
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "where",
Arguments = path,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return !string.IsNullOrEmpty(output);
}
catch
{
return false;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
private async void btnTips_Click(object sender, RoutedEventArgs e)
{
var tipText = (string)Application.Current.Resources["fileManager_files_tips"];
var url = "https://files.community/docs/contributing/updates";
var textBlock = new TextBlock
{
FontSize = 14,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 0, 0, 0)
};
textBlock.Inlines.Add(tipText);
var hyperlink = new Hyperlink
{
NavigateUri = new Uri(url)
};
hyperlink.Inlines.Add(url);
hyperlink.RequestNavigate += (s, args) =>
{
App.API.OpenUrl(args.Uri.AbsoluteUri);
args.Handled = true;
};
textBlock.Inlines.Add(hyperlink);
var tipsDialog = new ContentDialog()
{
Owner = Window.GetWindow((DependencyObject)sender),
Title = (string)Application.Current.Resources["fileManager_files_btn"],
Content = textBlock,
PrimaryButtonText = (string)Application.Current.Resources["commonOK"],
CornerRadius = new CornerRadius(8),
Style = (Style)Application.Current.Resources["ContentDialog"]
};
await tipsDialog.ShowAsync();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
CustomExplorers.Add(new()
{
Name = "New Profile"
});
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
CustomExplorers.RemoveAt(SelectedCustomExplorerIndex--);
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
var dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
{
TextBox path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
var path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
path.Text = dlg.FileName;
path.Focus();
((Button)sender).Focus();

View file

@ -335,14 +335,14 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
[RelayCommand]
private void SelectFileManager()
{
var fileManagerChangeWindow = new SelectFileManagerWindow(Settings);
var fileManagerChangeWindow = new SelectFileManagerWindow();
fileManagerChangeWindow.ShowDialog();
}
[RelayCommand]
private void SelectBrowser()
{
var browserWindow = new SelectBrowserWindow(Settings);
var browserWindow = new SelectBrowserWindow();
browserWindow.ShowDialog();
}
}

View file

@ -0,0 +1,58 @@
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel;
public partial class SelectBrowserViewModel : BaseModel
{
private readonly Settings _settings;
private int selectedCustomBrowserIndex;
public int SelectedCustomBrowserIndex
{
get => selectedCustomBrowserIndex;
set
{
selectedCustomBrowserIndex = value;
OnPropertyChanged(nameof(CustomBrowser));
}
}
public ObservableCollection<CustomBrowserViewModel> CustomBrowsers { get; }
public CustomBrowserViewModel CustomBrowser => CustomBrowsers[SelectedCustomBrowserIndex];
public SelectBrowserViewModel(Settings settings)
{
_settings = settings;
CustomBrowsers = new ObservableCollection<CustomBrowserViewModel>(_settings.CustomBrowserList.Select(x => x.Copy()));
SelectedCustomBrowserIndex = _settings.CustomBrowserIndex;
}
public bool SaveSettings()
{
_settings.CustomBrowserList = CustomBrowsers.ToList();
_settings.CustomBrowserIndex = SelectedCustomBrowserIndex;
return true;
}
[RelayCommand]
private void Add()
{
CustomBrowsers.Add(new()
{
Name = "New Profile"
});
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
}
[RelayCommand]
private void Delete()
{
CustomBrowsers.RemoveAt(SelectedCustomBrowserIndex--);
}
}

View file

@ -0,0 +1,161 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using ModernWpf.Controls;
namespace Flow.Launcher.ViewModel;
public partial class SelectFileManagerViewModel : BaseModel
{
private readonly Settings _settings;
private int selectedCustomExplorerIndex;
public int SelectedCustomExplorerIndex
{
get => selectedCustomExplorerIndex;
set
{
if (selectedCustomExplorerIndex != value)
{
selectedCustomExplorerIndex = value;
OnPropertyChanged(nameof(CustomExplorer));
}
}
}
public ObservableCollection<CustomExplorerViewModel> CustomExplorers { get; }
public CustomExplorerViewModel CustomExplorer => CustomExplorers[SelectedCustomExplorerIndex];
public SelectFileManagerViewModel(Settings settings)
{
_settings = settings;
CustomExplorers = new ObservableCollection<CustomExplorerViewModel>(_settings.CustomExplorerList.Select(x => x.Copy()));
SelectedCustomExplorerIndex = _settings.CustomExplorerIndex;
}
public bool SaveSettings()
{
// Check if the selected file manager path is valid
if (!IsFileManagerValid(CustomExplorer.Path))
{
var result = App.API.ShowMsgBox(
string.Format(App.API.GetTranslation("fileManagerPathNotFound"),
CustomExplorer.Name, CustomExplorer.Path),
App.API.GetTranslation("fileManagerPathError"),
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result == MessageBoxResult.No)
{
return false;
}
}
_settings.CustomExplorerList = CustomExplorers.ToList();
_settings.CustomExplorerIndex = SelectedCustomExplorerIndex;
return true;
}
private static bool IsFileManagerValid(string path)
{
if (string.Equals(path, "explorer", StringComparison.OrdinalIgnoreCase))
return true;
if (Path.IsPathRooted(path))
{
return File.Exists(path);
}
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "where",
Arguments = path,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return !string.IsNullOrEmpty(output);
}
catch
{
return false;
}
}
[RelayCommand]
private async Task OpenFilesTipsAsync(Button button)
{
var tipText = App.API.GetTranslation("fileManager_files_tips");
var url = "https://files.community/docs/contributing/updates";
var textBlock = new TextBlock
{
FontSize = 14,
TextWrapping = TextWrapping.Wrap,
Margin = new Thickness(0, 0, 0, 0)
};
textBlock.Inlines.Add(tipText);
var hyperlink = new Hyperlink
{
NavigateUri = new Uri(url)
};
hyperlink.Inlines.Add(url);
hyperlink.RequestNavigate += (s, args) =>
{
App.API.OpenUrl(args.Uri.AbsoluteUri);
args.Handled = true;
};
textBlock.Inlines.Add(hyperlink);
var tipsDialog = new ContentDialog()
{
Owner = Window.GetWindow(button),
Title = (string)Application.Current.Resources["fileManager_files_btn"],
Content = textBlock,
PrimaryButtonText = (string)Application.Current.Resources["commonOK"],
CornerRadius = new CornerRadius(8),
Style = (Style)Application.Current.Resources["ContentDialog"]
};
await tipsDialog.ShowAsync();
}
[RelayCommand]
private void Add()
{
CustomExplorers.Add(new()
{
Name = "New Profile"
});
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
}
[RelayCommand]
private void Delete()
{
CustomExplorers.RemoveAt(SelectedCustomExplorerIndex--);
}
}