import managed file dialogs.
ADD
WalletWasabi.Gui/ManagedDialogs/ChildFitter.cs
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Layout;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ class ChildFitter : Decorator
+ {
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ return new Size(0, 0);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ Child.Measure(finalSize);
+ base.ArrangeOverride(finalSize);
+ return finalSize;
+ }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/InternalViewModelBase.cs
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ class InternalViewModelBase : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ [NotifyPropertyChangedInvocator]
+ protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (!EqualityComparer<T>.Default.Equals(field, value))
+ {
+ field = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ return true;
+ }
+
+ return false;
+ }
+
+ [NotifyPropertyChangedInvocator]
+ protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileChooser.xaml
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:dialogs="clr-namespace:WalletWasabi.Gui.ManagedDialogs"
+ xmlns:internal="clr-namespace:WalletWasabi.Gui.ManagedDialogs">
+ <UserControl.Resources>
+ <DrawingGroup x:Key="LevelUp">
+ <GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
+ <GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M14.5,0L6.39,0 5.39,2 2.504,2C1.677,2,1,2.673,1,3.5L1,10.582 1,10.586 1,15.414 3,13.414 3,16 7,16 7,13.414 9,15.414 9,13 14.5,13C15.327,13,16,12.327,16,11.5L16,1.5C16,0.673,15.327,0,14.5,0" />
+ <GeometryDrawing Brush="#FFDCB679" Geometry="F1M14,3L7.508,3 8.008,2 8.012,2 14,2z M14.5,1L7.008,1 6.008,3 2.504,3C2.227,3,2,3.224,2,3.5L2,9.582 4.998,6.586 9,10.586 9,12 14.5,12C14.775,12,15,11.776,15,11.5L15,1.5C15,1.224,14.775,1,14.5,1" />
+ <GeometryDrawing Brush="#FF00529C" Geometry="F1M8,11L5,8 2,11 2,13 4,11 4,15 6,15 6,11 8,13z" />
+ <GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M8.0001,1.9996L7.5001,3.0006 14.0001,3.0006 14.0001,1.9996z" />
+ </DrawingGroup>
+ <dialogs:ResourceSelectorConverter x:Key="Icons">
+ <DrawingGroup x:Key="Icon_Folder"><GeometryDrawing Brush="#00FFFFFF" Geometry="F1M0,0L16,0 16,16 0,16z" /><GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M1.5,1L9.61,1 10.61,3 13.496,3C14.323,3,14.996,3.673,14.996,4.5L14.996,12.5C14.996,13.327,14.323,14,13.496,14L1.5,14C0.673,14,0,13.327,0,12.5L0,2.5C0,1.673,0.673,1,1.5,1" /><GeometryDrawing Brush="#FFDCB67A" Geometry="F1M2,3L8.374,3 8.874,4 2,4z M13.496,4L10,4 9.992,4 8.992,2 1.5,2C1.225,2,1,2.224,1,2.5L1,12.5C1,12.776,1.225,13,1.5,13L13.496,13C13.773,13,13.996,12.776,13.996,12.5L13.996,4.5C13.996,4.224,13.773,4,13.496,4" /><GeometryDrawing Brush="#FFEFEFF0" Geometry="F1M2,3L8.374,3 8.874,4 2,4z" /></DrawingGroup>
+ <DrawingGroup x:Key="Icon_File"><GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" /><GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M4,15C3.03,15,2,14.299,2,13L2,3C2,1.701,3.03,1,4,1L10.061,1 14,4.556 14,13C14,13.97,13.299,15,12,15z" /><GeometryDrawing Brush="#FF9B4E96" Geometry="F1M12,13L4,13 4,3 9,3 9,6 12,6z M9.641,2L3.964,2C3.964,2,3,2,3,3L3,13C3,14,3.964,14,3.964,14L11.965,14C12.965,14,13,13,13,13L13,5z" /><GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M4,3L9,3 9,6 12,6 12,13 4,13z" /></DrawingGroup>
+ </dialogs:ResourceSelectorConverter>
+ </UserControl.Resources>
+ <UserControl.DataTemplates>
+ <DataTemplate DataType="internal:ManagedFileChooserItemViewModel">
+ <StackPanel Background="Transparent" Orientation="Horizontal"
+ Cursor="Hand">
+
+ <DrawingPresenter Width="16" Height="16" Drawing="{Binding IconKey, Converter={StaticResource Icons}}"/>
+ <TextBlock Text="{Binding DisplayName}"/>
+ </StackPanel>
+ </DataTemplate>
+ </UserControl.DataTemplates>
+ <DockPanel>
+ <DockPanel DockPanel.Dock="Top" Margin="5">
+
+ <dialogs:ChildFitter DockPanel.Dock="Right" Width="{Binding ElementName=Location, Path=Bounds.Height}">
+ <Button Command="{Binding GoUp}" >
+
+ <DrawingPresenter Drawing="{StaticResource LevelUp}" Stretch="Fill"/>
+
+ </Button>
+ </dialogs:ChildFitter>
+ <TextBox x:Name="Location" IsReadOnly="True" Text="{Binding Location}" Margin="0 0 5 0"/>
+ </DockPanel>
+ <DockPanel DockPanel.Dock="Bottom">
+ <StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
+ <CheckBox IsChecked="{Binding ShowHiddenFiles}">
+ <TextBlock>Show hidden files</TextBlock>
+ </CheckBox>
+ </StackPanel>
+ <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
+ <StackPanel.Styles>
+ <Style Selector="Button">
+ <Setter Property="Margin">4</Setter>
+ </Style>
+ </StackPanel.Styles>
+ <Button Command="{Binding Ok}">OK</Button>
+ <Button Command="{Binding Cancel}">Cancel</Button>
+ </StackPanel>
+ </DockPanel>
+ <DropDown DockPanel.Dock="Bottom"
+ IsVisible="{Binding ShowFilters}"
+ Items="{Binding Filters}"
+ SelectedItem="{Binding SelectedFilter}"
+ />
+ <ListBox Margin="5" BorderBrush="Transparent" x:Name="QuickLinks" Items="{Binding QuickLinks}"
+ SelectedIndex="{Binding QuickLinksSelectedIndex}"
+ DockPanel.Dock="Left" Background="{DynamicResource ThemeControlMidBrush}" Focusable="False" />
+ <ListBox x:Name="Files"
+ VirtualizationMode="Simple"
+ Items="{Binding Items}"
+ Margin="5"
+ SelectionMode="{Binding SelectionMode}"
+ SelectedItems="{Binding SelectedItems}"/>
+ </DockPanel>
+
+</UserControl>

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileChooser.xaml.cs
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ class ManagedFileChooser : UserControl
+ {
+ private Control _quickLinksRoot;
+ private ListBox _filesView;
+
+ public ManagedFileChooser()
+ {
+ AvaloniaXamlLoader.Load(this);
+ AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
+ _quickLinksRoot = this.FindControl<Control>("QuickLinks");
+ _filesView = this.FindControl<ListBox>("Files");
+ }
+
+ ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel;
+
+ private void OnPointerPressed(object sender, PointerPressedEventArgs e)
+ {
+ var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel;
+ if(model == null)
+ return;
+
+ var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control);
+ if (e.ClickCount == 2 || isQuickLink)
+ {
+ if (model.IsDirectory)
+ Model?.Navigate(model.Path);
+ else
+ Model?.SelectSingleFile(model);
+ e.Handled = true;
+ }
+ }
+
+ protected override async void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+ var model = (DataContext as ManagedFileChooserViewModel);
+ if (model == null)
+ return;
+ var preselected = model.SelectedItems.FirstOrDefault();
+ if(preselected == null)
+ return;
+
+ //Let everything to settle down and scroll to selected item
+ await Task.Delay(100);
+ if (preselected != model.SelectedItems.FirstOrDefault())
+ return;
+
+ // Workaround for ListBox bug, scroll to the previous file
+ var indexOfPreselected = model.Items.IndexOf(preselected);
+ if (indexOfPreselected > 1)
+ _filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]);
+ }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileChooserFilterViewModel.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ class ManagedFileChooserFilterViewModel : InternalViewModelBase
+ {
+ private readonly string[] _extensions;
+ public string Name { get; }
+
+ public ManagedFileChooserFilterViewModel(FileDialogFilter filter)
+ {
+ Name = filter.Name;
+ if (filter.Extensions.Contains("*"))
+ return;
+ _extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray();
+ }
+
+ public ManagedFileChooserFilterViewModel()
+ {
+ Name = "All files";
+ }
+
+ public bool Match(string filename)
+ {
+ if (_extensions == null)
+ return true;
+ foreach(var ext in _extensions)
+ if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase))
+ return true;
+ return false;
+ }
+
+ public override string ToString() => Name;
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileChooserItemViewModel.cs
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ class ManagedFileChooserItemViewModel : InternalViewModelBase
+ {
+ private string _displayName;
+ private string _path;
+ private bool _isDirectory;
+
+ public string DisplayName
+ {
+ get => _displayName;
+ set => RaiseAndSetIfChanged(ref _displayName, value);
+ }
+
+ public string Path
+ {
+ get => _path;
+ set => RaiseAndSetIfChanged(ref _path, value);
+ }
+
+ public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File";
+
+ public bool IsDirectory
+ {
+ get => _isDirectory;
+ set
+ {
+ if (RaiseAndSetIfChanged(ref _isDirectory, value))
+ {
+ RaisePropertyChanged(nameof(IconKey));
+ }
+ }
+ }
+
+ public ManagedFileChooserItemViewModel()
+ {
+
+ }
+
+ public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item)
+ {
+ IsDirectory = true;
+ Path = item.Path;
+ DisplayName = item.DisplayName;
+ }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileChooserSources.cs
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ public class ManagedFileChooserSources
+ {
+ public Func<ManagedFileChooserNavigationItem[]> GetUserDirectories { get; set; }
+ = DefaultGetUserDirectories;
+
+ public Func<ManagedFileChooserNavigationItem[]> GetFileSystemRoots { get; set; }
+ = DefaultGetFileSystemRoots;
+
+ public Func<ManagedFileChooserSources, ManagedFileChooserNavigationItem[]> GetAllItemsDelegate { get; set; }
+ = DefaultGetAllItems;
+
+ public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this);
+
+ public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources)
+ {
+ return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray();
+ }
+
+ private static Environment.SpecialFolder[] s_folders = new[]
+ {
+ Environment.SpecialFolder.Desktop,
+ Environment.SpecialFolder.UserProfile,
+ Environment.SpecialFolder.MyDocuments,
+ Environment.SpecialFolder.MyMusic,
+ Environment.SpecialFolder.MyPictures,
+ Environment.SpecialFolder.MyVideos
+ };
+
+ public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories()
+ {
+ return s_folders.Select(Environment.GetFolderPath).Distinct()
+ .Where(d => !string.IsNullOrWhiteSpace(d))
+ .Where(Directory.Exists)
+ .Select(d => new ManagedFileChooserNavigationItem
+ {
+ Path = d,
+ DisplayName = Path.GetFileName(d)
+ }).ToArray();
+ }
+
+ public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem
+ {
+ DisplayName = d.Name,
+ Path = d.RootDirectory.FullName
+ }).ToArray();
+ }
+
+ return new[]
+ {
+ new ManagedFileChooserNavigationItem
+ {
+ DisplayName = "File System",
+ Path = "/"
+ }
+ };
+ }
+ }
+
+ public class ManagedFileChooserNavigationItem
+ {
+ public string DisplayName { get; set; }
+ public string Path { get; set; }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileChooserViewModel.cs
+using System;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Threading;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ class ManagedFileChooserViewModel : InternalViewModelBase
+ {
+ public event Action CancelRequested;
+ public event Action<string[]> CompleteRequested;
+
+ public AvaloniaList<ManagedFileChooserItemViewModel> QuickLinks { get; } =
+ new AvaloniaList<ManagedFileChooserItemViewModel>();
+
+ public AvaloniaList<ManagedFileChooserItemViewModel> Items { get; } =
+ new AvaloniaList<ManagedFileChooserItemViewModel>();
+
+ public AvaloniaList<ManagedFileChooserFilterViewModel> Filters { get; } =
+ new AvaloniaList<ManagedFileChooserFilterViewModel>();
+
+ public AvaloniaList<ManagedFileChooserItemViewModel> SelectedItems { get; } =
+ new AvaloniaList<ManagedFileChooserItemViewModel>();
+
+ string _location;
+ private bool _showHiddenFiles;
+ private ManagedFileChooserFilterViewModel _selectedFilter;
+ private bool _selectingDirectory;
+ private bool _scheduledSelectionValidation;
+
+ public string Location
+ {
+ get => _location;
+ private set => RaiseAndSetIfChanged(ref _location, value);
+ }
+
+ public bool ShowFilters { get; }
+ public SelectionMode SelectionMode { get; }
+ public string Title { get; }
+
+ public int QuickLinksSelectedIndex
+ {
+ get
+ {
+ for (var index = 0; index < QuickLinks.Count; index++)
+ {
+ var i = QuickLinks[index];
+ if (i.Path == Location)
+ return index;
+ }
+
+ return -1;
+ }
+ set => RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
+ }
+
+ public ManagedFileChooserFilterViewModel SelectedFilter
+ {
+ get => _selectedFilter;
+ set
+ {
+ RaiseAndSetIfChanged(ref _selectedFilter, value);
+ Refresh();
+ }
+ }
+
+ public bool ShowHiddenFiles
+ {
+ get => _showHiddenFiles;
+ set
+ {
+ RaiseAndSetIfChanged(ref _showHiddenFiles, value);
+ Refresh();
+ }
+ }
+
+ public ManagedFileChooserViewModel(FileSystemDialog dialog)
+ {
+ var quickSources = AvaloniaLocator.Current.GetService<ManagedFileChooserSources>()
+ ?? new ManagedFileChooserSources();
+ QuickLinks.Clear();
+
+ QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i)));
+ Title = dialog.Title ?? (
+ dialog is OpenFileDialog ? "Open file"
+ : dialog is SaveFileDialog ? "Save file"
+ : dialog is OpenFolderDialog ? "Select directory"
+ : throw new ArgumentException(nameof(dialog)));
+
+ var directory = dialog.InitialDirectory;
+ if (directory == null || !Directory.Exists(directory))
+ directory = Directory.GetCurrentDirectory();
+
+ if (dialog is FileDialog fd)
+ {
+ if (fd.Filters?.Count > 0)
+ {
+ Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f)));
+ _selectedFilter = Filters[0];
+ ShowFilters = true;
+ }
+
+ if (dialog is OpenFileDialog ofd)
+ {
+ if (ofd.AllowMultiple)
+ SelectionMode = SelectionMode.Multiple;
+ }
+ }
+
+ _selectingDirectory = dialog is OpenFolderDialog;
+
+ Navigate(directory, (dialog as FileDialog)?.InitialFileName);
+ SelectedItems.CollectionChanged += OnSelectionChanged;
+ }
+
+ private async void OnSelectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if(_scheduledSelectionValidation)
+ return;
+ _scheduledSelectionValidation = true;
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ try
+ {
+ if(_selectingDirectory)
+ SelectedItems.Clear();
+ else
+ {
+ var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList();
+ foreach (var item in invalidItems)
+ SelectedItems.Remove(item);
+ }
+ }
+ finally
+ {
+ _scheduledSelectionValidation = false;
+ }
+ });
+ }
+
+ void NavigateRoot(string initialSelectionName)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName);
+ else
+ Navigate("/", initialSelectionName);
+ }
+
+ public void Refresh() => Navigate(Location);
+
+ public void Navigate(string path, string initialSelectionName = null)
+ {
+ if (!Directory.Exists(path))
+ NavigateRoot(initialSelectionName);
+ else
+ {
+ Location = path;
+ Items.Clear();
+ SelectedItems.Clear();
+ var infos = new DirectoryInfo(path).EnumerateFileSystemInfos();
+ if (!ShowHiddenFiles)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0);
+ else
+ infos = infos.Where(i => !i.Name.StartsWith("."));
+ }
+
+ if (SelectedFilter != null)
+ infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name));
+
+ Items.AddRange(infos.Select(info => new ManagedFileChooserItemViewModel
+ {
+ DisplayName = info.Name,
+ Path = info.FullName,
+ IsDirectory = info is DirectoryInfo
+ }).OrderByDescending(x => x.IsDirectory)
+ .ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase));
+
+ if (initialSelectionName != null)
+ {
+ var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName);
+ if (sel != null)
+ SelectedItems.Add(sel);
+ }
+
+ RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
+ }
+
+
+ }
+
+ public void GoUp()
+ {
+ var parent = Path.GetDirectoryName(Location);
+ if (string.IsNullOrWhiteSpace(parent))
+ return;
+ Navigate(parent);
+ }
+
+ public void Cancel()
+ {
+ CancelRequested?.Invoke();
+ }
+
+ public void Ok()
+ {
+ if (_selectingDirectory)
+ CompleteRequested?.Invoke(new[] {Location});
+ else
+ CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray());
+ }
+
+ public void SelectSingleFile(ManagedFileChooserItemViewModel item)
+ {
+ CompleteRequested?.Invoke(new[] {item.Path});
+ }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileDialog.xaml
+<Window xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:dialogs="clr-namespace:WalletWasabi.Gui.ManagedDialogs"
+ xmlns:internal="clr-namespace:WalletWasabi.Gui.ManagedDialogs"
+ Title="{Binding Title}" MinWidth="700" MinHeight="500">
+ <internal:ManagedFileChooser Margin="4"/>
+</Window>

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileDialog.xaml.cs
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ class ManagedFileDialog : Window
+ {
+ private ManagedFileChooserViewModel _model;
+ public ManagedFileDialog()
+ {
+ AvaloniaXamlLoader.Load(this);
+ #if DEBUG
+ this.AttachDevTools();
+ #endif
+ }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ManagedFileDialogExtensions.cs
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ public static class ManagedFileDialogExtensions
+ {
+ class ManagedSystemDialogImpl : ISystemDialogImpl
+ {
+ async Task<string[]> Show(SystemDialog d, IWindowImpl parent)
+ {
+ var model = new ManagedFileChooserViewModel((FileSystemDialog)d);
+
+ var dialog = new ManagedFileDialog
+ {
+ DataContext = model
+ };
+
+ string[] result = null;
+ model.CompleteRequested += items =>
+ {
+ result = items;
+ dialog.Close();
+ };
+ model.CancelRequested += dialog.Close;
+
+ await dialog.ShowDialog<object>(parent);
+ return result;
+ }
+
+ public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
+ {
+ return await Show(dialog, parent);
+ }
+
+ public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
+ {
+ return (await Show(dialog, parent))?.FirstOrDefault();
+ }
+ }
+
+ public static TAppBuilder UseManagedSystemDialogs<TAppBuilder>(this TAppBuilder builder)
+ where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
+ {
+ builder.AfterSetup(_ =>
+ AvaloniaLocator.CurrentMutable.Bind<ISystemDialogImpl>().ToSingleton<ManagedSystemDialogImpl>());
+ return builder;
+ }
+ }
+}

ADD
WalletWasabi.Gui/ManagedDialogs/ResourceSelectorConverter.cs
+using System;
+using System.Globalization;
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+
+namespace WalletWasabi.Gui.ManagedDialogs
+{
+ public class ResourceSelectorConverter : ResourceDictionary, IValueConverter
+ {
+ public object Convert(object key, Type targetType, object parameter, CultureInfo culture)
+ {
+ TryGetResource((string)key, out var value);
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}

MODIFY
WalletWasabi.Gui/Program.cs
WalletWasabi.Gui/Program.cs
@@ -11,6 +11,7 @@ using WalletWasabi.Gui.CommandLine;
using WalletWasabi.Gui.ViewModels;
using WalletWasabi.Logging;
using WalletWasabi.Gui.Controls.LockScreen;
+using WalletWasabi.Gui.ManagedDialogs;
namespace WalletWasabi.Gui
{
@@ -98,7 +99,8 @@ namespace WalletWasabi.Gui
{
result
.UseWin32()
- .UseSkia();
+ .UseSkia()
+ .UseManagedSystemDialogs();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{

import managed file dialogs.