Fallback if filedialog fails.
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/TransactionBroadcasterViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/TransactionBroadcasterViewModel.cs
@@ -82,7 +82,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Title = "Import Transaction"
};
- var selected = await ofd.ShowAsync(Application.Current.MainWindow);
+ var selected = await ofd.ShowAsync(Application.Current.MainWindow, fallBack: true);
if (selected != null && selected.Any())
{
var path = selected.First();

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewerView.xaml
WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewerView.xaml
<controls:GroupBox Title="{Binding Title}" TextBlock.FontSize="30" Padding="20" Margin="10">
<DockPanel LastChildFill="True">
<Grid Classes="content" DockPanel.Dock="Bottom">
- <Button HorizontalAlignment="Right" Command="{Binding ExportBinaryPsbtCommand}" Content="Export Binary PSBT" />
+ <DockPanel LastChildFill="True">
+ <Button DockPanel.Dock="Right" Command="{Binding ExportBinaryPsbtCommand}" Content="Export Binary PSBT" />
+ <StackPanel Orientation="Vertical">
+ <TextBlock Text="{Binding WarningMessage}" Classes="warningMessage" />
+ <TextBlock Text="{Binding SuccessMessage}" Classes="validationMessage" Foreground="Green" />
+ </StackPanel>
+ </DockPanel>
</Grid>
<Grid Classes="content" ScrollViewer.VerticalScrollBarVisibility="Auto">

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewerViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewerViewModel.cs
@@ -67,7 +67,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Title = "Export Binary PSBT"
};
- string fileFullName = await sfd.ShowAsync(Application.Current.MainWindow);
+ string fileFullName = await sfd.ShowAsync(Application.Current.MainWindow, fallBack: true);
if (!string.IsNullOrWhiteSpace(fileFullName))
{
var ext = Path.GetExtension(fileFullName);

ADD
WalletWasabi.Gui/Dialogs/AvaloniaControlsExtensions.cs
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using WalletWasabi.Gui.Dialogs;
+using WalletWasabi.Gui.ViewModels;
+using WalletWasabi.Helpers;
+
+namespace Avalonia.Controls
+{
+ public static class AvaloniaControlsExtensions
+ {
+ public static async Task<string> ShowAsync(this SaveFileDialog me, Window parent, bool fallBack)
+ {
+ var ret = await ShowAsync(me as FileDialog, parent, fallBack);
+ return Guard.Correct(ret?.FirstOrDefault());
+ }
+
+ public static async Task<string[]> ShowAsync(this OpenFileDialog me, Window parent, bool fallBack)
+ {
+ return await ShowAsync(me as FileDialog, parent, fallBack);
+ }
+
+ private static async Task<string[]> ShowAsync(this FileDialog me, Window parent, bool fallBack)
+ {
+ if (fallBack)
+ {
+ try
+ {
+ throw new Exception();
+ return await ShowOpenSaveFileDialogAsync(me, parent);
+ }
+ catch (Exception ex)
+ {
+ WalletWasabi.Logging.Logger.LogWarning(ex, me.GetType().Name);
+
+ string title =
+ !string.IsNullOrWhiteSpace(me.Title) ? me.Title :
+ me is OpenFileDialog ? "Open File" :
+ me is SaveFileDialog ? "Save File" :
+ throw new NotImplementedException();
+
+ string instructions =
+ me is OpenFileDialog ? $"Failed to use your operating system's {nameof(OpenFileDialog)}. Please provide the path of the file you want to open manually:" :
+ me is SaveFileDialog ? $"Failed to use your operating system's {nameof(SaveFileDialog)}. Please provide the path where you want your file to be saved to:" :
+ throw new NotImplementedException();
+
+ string exampleFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
+ @"C:\path\to\the\file.ext" :
+ @"/path/to/the/file";
+
+ string defaultTextInput =
+ me is OpenFileDialog ? Path.Combine(me.InitialDirectory ?? "", me.InitialFileName ?? "") :
+ me is SaveFileDialog sfd ? Path.ChangeExtension(
+ Path.Combine(
+ string.IsNullOrEmpty(me.InitialDirectory) ? Path.GetDirectoryName(exampleFilePath) : me.InitialDirectory,
+ string.IsNullOrEmpty(me.InitialFileName) ? Path.GetFileName(exampleFilePath) : me.InitialFileName),
+ string.IsNullOrEmpty(sfd.DefaultExtension) ? "ext" : sfd.DefaultExtension) :
+ throw new NotImplementedException();
+
+ var dialog = new TextInputDialogViewModel(title, instructions, exampleFilePath, defaultTextInput);
+ var success = await MainWindowViewModel.Instance.ShowDialogAsync(dialog);
+
+ if (success)
+ {
+ var path = dialog.TextInput.Trim();
+ return new string[] { path };
+ }
+ else
+ {
+ return new string[0];
+ }
+ }
+ }
+ else
+ {
+ return await ShowOpenSaveFileDialogAsync(me, parent);
+ }
+ }
+
+ private static async Task<string[]> ShowOpenSaveFileDialogAsync(FileDialog me, Window parent)
+ {
+ if (me is OpenFileDialog ofd)
+ {
+ return await ofd.ShowAsync(parent);
+ }
+ else if (me is SaveFileDialog sfd)
+ {
+ return new string[] { await sfd.ShowAsync(parent) };
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}

MODIFY
WalletWasabi.Gui/Dialogs/CannotCloseDialogViewModel.cs
WalletWasabi.Gui/Dialogs/CannotCloseDialogViewModel.cs
@@ -11,7 +11,7 @@ using WalletWasabi.Models;
namespace WalletWasabi.Gui.Dialogs
{
- internal class CannotCloseDialogViewModel : ModalDialogViewModelBase
+ public class CannotCloseDialogViewModel : ModalDialogViewModelBase
{
private bool _isBusy;
private string _warningMessage;
@@ -63,8 +63,7 @@ namespace WalletWasabi.Gui.Dialogs
}
// OK pressed.
Close(false);
- },
- canOk);
+ }, canOk);
CancelCommand = ReactiveCommand.CreateFromTask(async () =>
{

ADD
WalletWasabi.Gui/Dialogs/TextInputDialogView.xaml.cs
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace WalletWasabi.Gui.Dialogs
+{
+ public class TextInputDialogView : UserControl
+ {
+ public TextInputDialogView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}

ADD
WalletWasabi.Gui/Dialogs/TextInputDialogViewModel.cs
+using AvalonStudio.Extensibility.Dialogs;
+using ReactiveUI;
+using System;
+using System.Collections.Generic;
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Text;
+using WalletWasabi.Helpers;
+
+namespace WalletWasabi.Gui.Dialogs
+{
+ public class TextInputDialogViewModel : ModalDialogViewModelBase
+ {
+ private string _textInput;
+ public new ReactiveCommand<Unit, Unit> OKCommand { get; set; }
+ public new ReactiveCommand<Unit, Unit> CancelCommand { get; set; }
+ public string Instructions { get; }
+ public string Watermark { get; }
+
+ public TextInputDialogViewModel(string title, string instructions, string watermark = "", string defaultTextInput = "") : base(Guard.Correct(title), false, false)
+ {
+ Instructions = Guard.Correct(instructions);
+ Watermark = Guard.Correct(watermark);
+ TextInput = Guard.Correct(defaultTextInput);
+
+ var canOk = this.WhenAnyValue(x => x.TextInput)
+ .Select(x => !string.IsNullOrWhiteSpace(TextInput));
+
+ OKCommand = ReactiveCommand.Create(() =>
+ {
+ Close(true);
+ }, canOk);
+
+ CancelCommand = ReactiveCommand.Create(() =>
+ {
+ TextInput = "";
+ Close(false);
+ });
+
+ OKCommand.ThrownExceptions.Subscribe(ex => Logging.Logger.LogWarning<TextInputDialogViewModel>(ex));
+ CancelCommand.ThrownExceptions.Subscribe(ex => Logging.Logger.LogWarning<TextInputDialogViewModel>(ex));
+ }
+
+ public string TextInput
+ {
+ get => _textInput;
+ set => this.RaiseAndSetIfChanged(ref _textInput, value);
+ }
+ }
+}

MODIFY
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
@@ -19,6 +19,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Gui.Controls.WalletExplorer;
+using WalletWasabi.Gui.Dialogs;
using WalletWasabi.Gui.Models;
using WalletWasabi.Gui.ViewModels;
using WalletWasabi.Helpers;
@@ -106,7 +107,7 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
AllowMultiple = false,
Title = "Import Coldcard"
};
- var selected = await ofd.ShowAsync(Application.Current.MainWindow);
+ var selected = await ofd.ShowAsync(Application.Current.MainWindow, fallBack: true);
if (selected != null && selected.Any())
{
var path = selected.First();

Fallback if filedialog fails.