Merge branch 'master' into ca1835
This commit is contained in:
Коммит
af48d9c0e7
|
@ -260,3 +260,6 @@ dotnet_diagnostic.CA1816.severity = none
|
|||
|
||||
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
||||
dotnet_diagnostic.CA1835.severity = warning
|
||||
|
||||
# CA2200: Rethrow to preserve stack details
|
||||
dotnet_diagnostic.CA2200.severity = warning
|
||||
|
|
|
@ -332,7 +332,7 @@ namespace WalletWasabi.Backend.Controllers
|
|||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogDebug(ex);
|
||||
throw ex;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ using WalletWasabi.Gui.CrashReport;
|
|||
using WalletWasabi.Gui.ViewModels;
|
||||
using WalletWasabi.Helpers;
|
||||
using WalletWasabi.Logging;
|
||||
using WalletWasabi.Services;
|
||||
using WalletWasabi.Services.Terminate;
|
||||
using WalletWasabi.Wallets;
|
||||
|
||||
|
@ -28,6 +29,8 @@ namespace WalletWasabi.Fluent.Desktop
|
|||
|
||||
private static readonly TerminateService TerminateService = new TerminateService(TerminateApplicationAsync);
|
||||
|
||||
private static SingleInstanceChecker? SingleInstanceChecker { get; set; }
|
||||
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
|
@ -38,7 +41,11 @@ namespace WalletWasabi.Fluent.Desktop
|
|||
|
||||
try
|
||||
{
|
||||
Global = CreateGlobal();
|
||||
string dataDir = EnvironmentHelpers.GetDataDir(Path.Combine("WalletWasabi", "Client"));
|
||||
var (uiConfig, config) = LoadOrCreateConfigs(dataDir);
|
||||
|
||||
SingleInstanceChecker = new SingleInstanceChecker(config.Network);
|
||||
Global = CreateGlobal(dataDir, uiConfig, config);
|
||||
|
||||
// TODO only required due to statusbar vm... to be removed.
|
||||
Locator.CurrentMutable.RegisterConstant(Global);
|
||||
|
@ -46,6 +53,8 @@ namespace WalletWasabi.Fluent.Desktop
|
|||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
|
||||
SingleInstanceChecker.EnsureSingleOrThrowAsync().GetAwaiter().GetResult();
|
||||
|
||||
runGui = ProcessCliCommands(args);
|
||||
|
||||
if (CrashReporter.IsReport)
|
||||
|
@ -72,17 +81,23 @@ namespace WalletWasabi.Fluent.Desktop
|
|||
TerminateAppAndHandleException(appException, runGui);
|
||||
}
|
||||
|
||||
private static Global CreateGlobal()
|
||||
private static (UiConfig uiConfig, Config config) LoadOrCreateConfigs(string dataDir)
|
||||
{
|
||||
string dataDir = EnvironmentHelpers.GetDataDir(Path.Combine("WalletWasabi", "Client"));
|
||||
Directory.CreateDirectory(dataDir);
|
||||
string torLogsFile = Path.Combine(dataDir, "TorLogs.txt");
|
||||
|
||||
var uiConfig = new UiConfig(Path.Combine(dataDir, "UiConfig.json"));
|
||||
UiConfig uiConfig = new(Path.Combine(dataDir, "UiConfig.json"));
|
||||
uiConfig.LoadOrCreateDefaultFile();
|
||||
var config = new Config(Path.Combine(dataDir, "Config.json"));
|
||||
|
||||
Config config = new(Path.Combine(dataDir, "Config.json"));
|
||||
config.LoadOrCreateDefaultFile();
|
||||
config.CorrectMixUntilAnonymitySet();
|
||||
|
||||
return (uiConfig, config);
|
||||
}
|
||||
|
||||
private static Global CreateGlobal(string dataDir, UiConfig uiConfig, Config config)
|
||||
{
|
||||
string torLogsFile = Path.Combine(dataDir, "TorLogs.txt");
|
||||
var walletManager = new WalletManager(config.Network, new WalletDirectories(dataDir));
|
||||
|
||||
return new Global(dataDir, torLogsFile, config, uiConfig, walletManager);
|
||||
|
@ -151,6 +166,11 @@ namespace WalletWasabi.Fluent.Desktop
|
|||
Logger.LogSoftwareStopped("Wasabi GUI");
|
||||
}
|
||||
|
||||
if (SingleInstanceChecker is { } single)
|
||||
{
|
||||
await single.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logger.LogSoftwareStopped("Wasabi");
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<ContentPresenter Name="PART_CaptionPresenter" Content="{TemplateBinding Caption}" />
|
||||
</StackPanel>
|
||||
<Panel DockPanel.Dock="Bottom">
|
||||
<Button Name="PART_CancelButton" Classes="invisible" IsVisible="{TemplateBinding EnableCancel}" Content="{TemplateBinding CancelContent}" Margin="0,10,10,10" HorizontalAlignment="Left" Command="{Binding CancelCommand}">
|
||||
<Button Name="PART_CancelButton" IsCancel="{TemplateBinding EnableCancel}" Classes="invisible" IsVisible="{TemplateBinding EnableCancel}" Content="{TemplateBinding CancelContent}" Margin="0,10,10,10" HorizontalAlignment="Left" Command="{Binding CancelCommand}">
|
||||
<i:Interaction.Behaviors>
|
||||
<behaviors:FocusOnAttachedBehavior IsEnabled="{Binding FocusCancel, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</i:Interaction.Behaviors>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace WalletWasabi.Fluent.Controls
|
||||
{
|
||||
|
@ -12,6 +15,12 @@ namespace WalletWasabi.Fluent.Controls
|
|||
public static readonly StyledProperty<bool> IsDialogOpenProperty =
|
||||
AvaloniaProperty.Register<Dialog, bool>(nameof(IsDialogOpen));
|
||||
|
||||
public static readonly StyledProperty<bool> EnableCancelOnPressedProperty =
|
||||
AvaloniaProperty.Register<Dialog, bool>(nameof(EnableCancelOnPressed));
|
||||
|
||||
public static readonly StyledProperty<bool> EnableCancelOnEscapeProperty =
|
||||
AvaloniaProperty.Register<Dialog, bool>(nameof(EnableCancelOnEscape));
|
||||
|
||||
public static readonly StyledProperty<double> MaxContentHeightProperty =
|
||||
AvaloniaProperty.Register<Dialog, double>(nameof(MaxContentHeight), double.PositiveInfinity);
|
||||
|
||||
|
@ -24,6 +33,18 @@ namespace WalletWasabi.Fluent.Controls
|
|||
set => SetValue(IsDialogOpenProperty, value);
|
||||
}
|
||||
|
||||
public bool EnableCancelOnPressed
|
||||
{
|
||||
get => GetValue(EnableCancelOnPressedProperty);
|
||||
set => SetValue(EnableCancelOnPressedProperty, value);
|
||||
}
|
||||
|
||||
public bool EnableCancelOnEscape
|
||||
{
|
||||
get => GetValue(EnableCancelOnEscapeProperty);
|
||||
set => SetValue(EnableCancelOnEscapeProperty, value);
|
||||
}
|
||||
|
||||
public double MaxContentHeight
|
||||
{
|
||||
get => GetValue(MaxContentHeightProperty);
|
||||
|
@ -51,7 +72,31 @@ namespace WalletWasabi.Fluent.Controls
|
|||
base.OnApplyTemplate(e);
|
||||
|
||||
var overlayButton = e.NameScope.Find<Panel>("PART_Overlay");
|
||||
overlayButton.PointerPressed += (_, __) => IsDialogOpen = false;
|
||||
overlayButton.PointerPressed += (_, __) =>
|
||||
{
|
||||
if (EnableCancelOnPressed)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
};
|
||||
|
||||
if (this.GetVisualRoot() is TopLevel topLevel)
|
||||
{
|
||||
topLevel.AddHandler(KeyDownEvent, CancelKeyDown, RoutingStrategies.Tunnel);
|
||||
}
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
IsDialogOpen = false;
|
||||
}
|
||||
|
||||
private void CancelKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape && EnableCancelOnEscape)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -10,12 +10,15 @@ using WalletWasabi.Stores;
|
|||
using NBitcoin;
|
||||
using WalletWasabi.Fluent.ViewModels.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using WalletWasabi.Fluent.Helpers;
|
||||
using WalletWasabi.Fluent.ViewModels.AddWallet.Create;
|
||||
using WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet;
|
||||
using WalletWasabi.Gui.Validation;
|
||||
using WalletWasabi.Models;
|
||||
using WalletWasabi.Fluent.ViewModels.NavBar;
|
||||
using WalletWasabi.Helpers;
|
||||
using WalletWasabi.Legal;
|
||||
using WalletWasabi.Logging;
|
||||
|
||||
namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
||||
{
|
||||
|
@ -65,7 +68,28 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
RecoverWalletCommand = ReactiveCommand.Create(
|
||||
() => { Navigate().To(new RecoverWalletViewModel(WalletName, network, walletManager)); });
|
||||
|
||||
ImportWalletCommand = ReactiveCommand.Create(() => new ImportWalletViewModel(WalletName, walletManager));
|
||||
ImportWalletCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = await FileDialogHelper.ShowOpenFileDialogAsync("Import wallet file", new[] { "json" });
|
||||
|
||||
if (filePath is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isColdcardJson = await ImportWalletHelper.ImportWalletAsync(walletManager, WalletName, filePath);
|
||||
|
||||
// TODO: get the type from the wallet file
|
||||
Navigate().To(new AddedWalletPageViewModel(WalletName, isColdcardJson ? WalletType.Coldcard : WalletType.Normal));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
await ShowErrorAsync(ex.ToUserFriendlyString(), "The wallet file was not valid or compatible with Wasabi.");
|
||||
}
|
||||
});
|
||||
|
||||
ConnectHardwareWalletCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
|
@ -75,10 +99,10 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
CreateWalletCommand = ReactiveCommand.CreateFromTask(
|
||||
async () =>
|
||||
{
|
||||
var result = await NavigateDialog(
|
||||
var dialogResult = await NavigateDialog(
|
||||
new EnterPasswordViewModel("Type the password of the wallet and click Continue."));
|
||||
|
||||
if (result is { } password)
|
||||
if (dialogResult.Result is { } password)
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
{
|
||||
public AddedWalletPageViewModel(string walletName, WalletType type)
|
||||
{
|
||||
Title = "Success";
|
||||
WalletName = walletName;
|
||||
Type = type;
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet.Create
|
|||
|
||||
public ConfirmRecoveryWordsViewModel(List<RecoveryWordViewModel> mnemonicWords, KeyManager keyManager, WalletManager walletManager)
|
||||
{
|
||||
Title = "Confirm recovery words";
|
||||
|
||||
var confirmationWordsSourceList = new SourceList<RecoveryWordViewModel>();
|
||||
|
||||
var finishCommandCanExecute =
|
||||
|
|
|
@ -14,6 +14,8 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet.Create
|
|||
Mnemonic mnemonic,
|
||||
WalletManager walletManager)
|
||||
{
|
||||
Title = "Recovery words";
|
||||
|
||||
MnemonicWords = new List<RecoveryWordViewModel>();
|
||||
|
||||
for (int i = 0; i < mnemonic.Words.Length; i++)
|
||||
|
|
|
@ -19,8 +19,9 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet
|
|||
|
||||
public ConnectHardwareWalletViewModel(string walletName, Network network, WalletManager walletManager)
|
||||
{
|
||||
Title = "Hardware Wallet";
|
||||
_message = "";
|
||||
IsBusy = true;
|
||||
|
||||
WalletName = walletName;
|
||||
HardwareWalletOperations = new HardwareWalletOperations(walletManager, network);
|
||||
HardwareWalletOperations.NoHardwareWalletFound += OnNoHardwareWalletFound;
|
||||
|
@ -47,8 +48,6 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet
|
|||
// TODO: Create an up-to-date article
|
||||
OpenBrowserCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
await IoHelpers.OpenBrowserAsync("https://docs.wasabiwallet.io/using-wasabi/ColdWasabi.html#using-hardware-wallet-step-by-step"));
|
||||
|
||||
_message = "";
|
||||
}
|
||||
|
||||
public string WalletName { get; }
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet
|
|||
{
|
||||
public DetectedHardwareWalletViewModel(HardwareWalletOperations hardwareWalletOperations, string walletName)
|
||||
{
|
||||
Title = "Hardware Wallet";
|
||||
WalletName = walletName;
|
||||
|
||||
switch (hardwareWalletOperations.SelectedDevice!.Model)
|
||||
|
@ -50,8 +51,8 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet
|
|||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// TODO: Notify the user
|
||||
Logger.LogError(ex);
|
||||
await ShowErrorAsync(ex.ToUserFriendlyString(), "Error occured during adding your wallet.");
|
||||
Navigate().Back();
|
||||
}
|
||||
|
||||
|
|
|
@ -143,8 +143,7 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet
|
|||
var detectedHardwareWallets =
|
||||
(await Client.EnumerateAsync(timeoutCts.Token)
|
||||
.ConfigureAwait(false))
|
||||
.Where(wallet => WalletManager.GetWallets()
|
||||
.Any(x => x.KeyManager.MasterFingerprint == wallet.Fingerprint) == false)
|
||||
.Where(wallet => !WalletManager.WalletExists(wallet.Fingerprint))
|
||||
.ToArray();
|
||||
|
||||
detectionCts.Token.ThrowIfCancellationRequested();
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WalletWasabi.Blockchain.Keys;
|
||||
using WalletWasabi.Fluent.Helpers;
|
||||
using WalletWasabi.Fluent.ViewModels.Navigation;
|
||||
using WalletWasabi.Helpers;
|
||||
using WalletWasabi.Logging;
|
||||
using WalletWasabi.Wallets;
|
||||
|
||||
namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
||||
{
|
||||
public class ImportWalletViewModel : RoutableViewModel
|
||||
{
|
||||
private const string _walletExistsErrorMessage = "Wallet with the same fingerprint already exists!";
|
||||
|
||||
public ImportWalletViewModel(string walletName, WalletManager walletManager)
|
||||
{
|
||||
WalletName = walletName;
|
||||
WalletManager = walletManager;
|
||||
|
||||
ImportWallet();
|
||||
}
|
||||
|
||||
private string WalletName { get; }
|
||||
private WalletManager WalletManager { get; }
|
||||
|
||||
private async void ImportWallet()
|
||||
{
|
||||
var filePath = await FileDialogHelper.ShowOpenFileDialogAsync("Import wallet file", new []{ "json" });
|
||||
|
||||
// Dialog canceled.
|
||||
if (filePath is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var walletFullPath = WalletManager.WalletDirectories.GetWalletFilePaths(WalletName).walletFilePath;
|
||||
|
||||
try
|
||||
{
|
||||
string jsonString = await File.ReadAllTextAsync(filePath);
|
||||
var jsonWallet = JObject.Parse(jsonString);
|
||||
|
||||
// TODO: Better logic to distinguish wallets.
|
||||
// If Count <= 3 then it is a possible Coldcard json otherwise possible Wasabi json
|
||||
KeyManager km = jsonWallet.Count <= 3 ? GetKeyManagerByColdcardJson(jsonWallet, walletFullPath) : GetKeyManagerByWasabiJson(filePath, walletFullPath);
|
||||
|
||||
WalletManager.AddWallet(km);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: Notify the user
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
|
||||
Navigate().Clear();
|
||||
}
|
||||
|
||||
private bool IsWalletExists(HDFingerprint? fingerprint) => WalletManager.GetWallets().Any(x => fingerprint is { } && x.KeyManager.MasterFingerprint == fingerprint);
|
||||
|
||||
private KeyManager GetKeyManagerByWasabiJson(string filePath, string walletFullPath)
|
||||
{
|
||||
var km = KeyManager.FromFile(filePath);
|
||||
|
||||
if (IsWalletExists(km.MasterFingerprint))
|
||||
{
|
||||
throw new InvalidOperationException(_walletExistsErrorMessage);
|
||||
}
|
||||
|
||||
km.SetFilePath(walletFullPath);
|
||||
|
||||
return km;
|
||||
}
|
||||
|
||||
private KeyManager GetKeyManagerByColdcardJson(JObject jsonWallet, string walletFullPath)
|
||||
{
|
||||
var xpubString = jsonWallet["ExtPubKey"].ToString();
|
||||
var mfpString = jsonWallet["MasterFingerprint"].ToString();
|
||||
|
||||
// https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066
|
||||
// Coldcard 2.1.0 improperly implemented Wasabi skeleton fingerprint at first, so we must reverse byte order.
|
||||
// The solution was to add a ColdCardFirmwareVersion json field from 2.1.1 and correct the one generated by 2.1.0.
|
||||
var coldCardVersionString = jsonWallet["ColdCardFirmwareVersion"]?.ToString();
|
||||
var reverseByteOrder = false;
|
||||
if (coldCardVersionString is null)
|
||||
{
|
||||
reverseByteOrder = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Version coldCardVersion = new (coldCardVersionString);
|
||||
|
||||
if (coldCardVersion == new Version("2.1.0")) // Should never happen though.
|
||||
{
|
||||
reverseByteOrder = true;
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = ByteHelpers.FromHex(Guard.NotNullOrEmptyOrWhitespace(nameof(mfpString), mfpString, trim: true));
|
||||
HDFingerprint mfp = reverseByteOrder ? new HDFingerprint(bytes.Reverse().ToArray()) : new HDFingerprint(bytes);
|
||||
|
||||
if (IsWalletExists(mfp))
|
||||
{
|
||||
throw new InvalidOperationException(_walletExistsErrorMessage);
|
||||
}
|
||||
|
||||
ExtPubKey extPubKey = NBitcoinHelpers.BetterParseExtPubKey(xpubString);
|
||||
|
||||
return KeyManager.CreateNewHardwareWalletWatchOnly(mfp, extPubKey, walletFullPath);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,8 +14,8 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
{
|
||||
public LegalDocumentsViewModel(string content)
|
||||
{
|
||||
Title = "Terms and Conditions";
|
||||
Content = content;
|
||||
|
||||
NextCommand = BackCommand;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
Network network,
|
||||
WalletManager walletManager)
|
||||
{
|
||||
Title = "Enter recovery words";
|
||||
Suggestions = new Mnemonic(Wordlist.English, WordCount.Twelve).WordList.GetWords();
|
||||
|
||||
Mnemonics.ToObservableChangeSet().ToCollection()
|
||||
|
@ -57,7 +58,7 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
AdvancedOptionsInteraction.RegisterHandler(
|
||||
async interaction =>
|
||||
interaction.SetOutput(
|
||||
await new AdvancedRecoveryOptionsViewModel(interaction.Input).ShowDialogAsync()));
|
||||
(await new AdvancedRecoveryOptionsViewModel(interaction.Input).ShowDialogAsync()).Result));
|
||||
|
||||
AdvancedRecoveryOptionsDialogCommand = ReactiveCommand.CreateFromTask(
|
||||
async () =>
|
||||
|
@ -80,40 +81,48 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
{
|
||||
IsBusy = true;
|
||||
|
||||
try
|
||||
{
|
||||
var result = await NavigateDialog(
|
||||
new EnterPasswordViewModel(
|
||||
"Type the password of the wallet to be able to recover and click Continue."));
|
||||
var dialogResult = await NavigateDialog(
|
||||
new EnterPasswordViewModel(
|
||||
"Type the password of the wallet to be able to recover and click Continue."));
|
||||
|
||||
if (result is { } password)
|
||||
if (dialogResult.Result is { } password)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(
|
||||
() =>
|
||||
{
|
||||
var walletFilePath = walletManager.WalletDirectories.GetWalletFilePaths(walletName!)
|
||||
.walletFilePath;
|
||||
{
|
||||
var walletFilePath = walletManager.WalletDirectories.GetWalletFilePaths(walletName!)
|
||||
.walletFilePath;
|
||||
|
||||
var keyManager = KeyManager.Recover(
|
||||
CurrentMnemonics!,
|
||||
password!,
|
||||
walletFilePath,
|
||||
AccountKeyPath,
|
||||
MinGapLimit);
|
||||
var keyManager = KeyManager.Recover(
|
||||
CurrentMnemonics!,
|
||||
password!,
|
||||
walletFilePath,
|
||||
AccountKeyPath,
|
||||
MinGapLimit);
|
||||
|
||||
keyManager.SetNetwork(network);
|
||||
keyManager.SetNetwork(network);
|
||||
|
||||
walletManager.AddWallet(keyManager);
|
||||
});
|
||||
walletManager.AddWallet(keyManager);
|
||||
});
|
||||
|
||||
Navigate().To(new AddedWalletPageViewModel(walletName!, WalletType.Normal));
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO navigate to error dialog.
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
IsBusy = false;
|
||||
|
||||
if (dialogResult.Kind == DialogResultKind.Cancel)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Navigate().To(new AddedWalletPageViewModel(walletName!, WalletType.Normal));
|
||||
Navigate().Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
|
|||
|
||||
public TermsAndConditionsViewModel(LegalDocuments legalDocuments, RoutableViewModel next)
|
||||
{
|
||||
Title = "Welcome to Wasabi Wallet";
|
||||
|
||||
ViewTermsCommand = ReactiveCommand.CreateFromTask(
|
||||
async () =>
|
||||
{
|
||||
|
|
|
@ -7,6 +7,11 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
{
|
||||
public class AboutAdvancedInfoViewModel : DialogViewModelBase<Unit>
|
||||
{
|
||||
public AboutAdvancedInfoViewModel()
|
||||
{
|
||||
NextCommand = CancelCommand;
|
||||
}
|
||||
|
||||
public Version BitcoinCoreVersion => Constants.BitcoinCoreVersion;
|
||||
|
||||
public Version HwiVersion => Constants.HwiVersion;
|
||||
|
|
|
@ -14,6 +14,8 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
|
||||
public AdvancedRecoveryOptionsViewModel((KeyPath keyPath, int minGapLimit) interactionInput)
|
||||
{
|
||||
Title = "Advanced Recovery Options";
|
||||
|
||||
this.ValidateProperty(x => x.AccountKeyPath, ValidateAccountKeyPath);
|
||||
this.ValidateProperty(x => x.MinGapLimit, ValidateMinGapLimit);
|
||||
|
||||
|
@ -41,7 +43,7 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
BackCommand = ReactiveCommand.Create(() => Navigate().Back(), backCommandCanExecute);
|
||||
|
||||
NextCommand = ReactiveCommand.Create(
|
||||
() => Close((KeyPath.Parse(AccountKeyPath), int.Parse(MinGapLimit))),
|
||||
() => Close(result: (KeyPath.Parse(AccountKeyPath), int.Parse(MinGapLimit))),
|
||||
nextCommandCanExecute);
|
||||
|
||||
CancelCommand = ReactiveCommand.Create(() => Close(), cancelCommandCanExecute);
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
||||
{
|
||||
public struct DialogResult<TResult>
|
||||
{
|
||||
public DialogResult(TResult? result, DialogResultKind kind)
|
||||
{
|
||||
Result = result;
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
public TResult? Result { get; }
|
||||
|
||||
public DialogResultKind Kind { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
||||
{
|
||||
public enum DialogResultKind
|
||||
{
|
||||
Normal,
|
||||
Cancel,
|
||||
Back
|
||||
}
|
||||
}
|
|
@ -14,23 +14,23 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
public abstract class DialogViewModelBase<TResult> : DialogViewModelBase
|
||||
{
|
||||
private readonly IDisposable _disposable;
|
||||
private readonly TaskCompletionSource<TResult> _currentTaskCompletionSource;
|
||||
private readonly TaskCompletionSource<DialogResult<TResult>> _currentTaskCompletionSource;
|
||||
|
||||
protected DialogViewModelBase()
|
||||
{
|
||||
_currentTaskCompletionSource = new TaskCompletionSource<TResult>();
|
||||
_currentTaskCompletionSource = new TaskCompletionSource<DialogResult<TResult>>();
|
||||
|
||||
_disposable = this.WhenAnyValue(x => x.IsDialogOpen)
|
||||
.Skip(1) // Skip the initial value change (which is false).
|
||||
.DistinctUntilChanged()
|
||||
.Subscribe(OnIsDialogOpenChanged);
|
||||
|
||||
CancelCommand = ReactiveCommand.Create(() => Close());
|
||||
CancelCommand = ReactiveCommand.Create(() => Close(DialogResultKind.Cancel));
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom()
|
||||
{
|
||||
Close();
|
||||
Close(DialogResultKind.Cancel);
|
||||
|
||||
base.OnNavigatedFrom();
|
||||
}
|
||||
|
@ -48,15 +48,15 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
/// Method to be called when the dialog intends to close
|
||||
/// and ready to pass a value back to the caller.
|
||||
/// </summary>
|
||||
/// <param name="value">The return value of the dialog</param>
|
||||
protected void Close(TResult value = default)
|
||||
/// <param name="result">The return value of the dialog</param>
|
||||
protected void Close(DialogResultKind kind = DialogResultKind.Normal, TResult result = default)
|
||||
{
|
||||
if (_currentTaskCompletionSource.Task.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentTaskCompletionSource.SetResult(value);
|
||||
_currentTaskCompletionSource.SetResult(new DialogResult<TResult>(result, kind));
|
||||
|
||||
_disposable.Dispose();
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
/// Shows the dialog.
|
||||
/// </summary>
|
||||
/// <returns>The value to be returned when the dialog is finished.</returns>
|
||||
public Task<TResult> ShowDialogAsync(IDialogHost? host = null)
|
||||
public Task<DialogResult<TResult>> ShowDialogAsync(IDialogHost? host = null)
|
||||
{
|
||||
if (host is null)
|
||||
{
|
||||
|
@ -90,7 +90,7 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
/// Gets the dialog result.
|
||||
/// </summary>
|
||||
/// <returns>The value to be returned when the dialog is finished.</returns>
|
||||
public Task<TResult> GetDialogResultAsync()
|
||||
public Task<DialogResult<TResult>> GetDialogResultAsync()
|
||||
{
|
||||
IsDialogOpen = true;
|
||||
|
||||
|
|
|
@ -11,9 +11,10 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
[AutoNotify] private string? _confirmPassword;
|
||||
[AutoNotify] private string? _password;
|
||||
|
||||
public EnterPasswordViewModel(string subtitle)
|
||||
public EnterPasswordViewModel(string caption)
|
||||
{
|
||||
Subtitle = subtitle;
|
||||
Title = "Enter a password";
|
||||
Caption = caption;
|
||||
|
||||
// This means pressing continue will make the password empty string.
|
||||
// pressing cancel will return null.
|
||||
|
@ -40,12 +41,12 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
|
||||
var cancelCommandCanExecute = this.WhenAnyValue(x => x.IsDialogOpen).ObserveOn(RxApp.MainThreadScheduler);
|
||||
|
||||
BackCommand = ReactiveCommand.Create(() => Close(), backCommandCanExecute);
|
||||
NextCommand = ReactiveCommand.Create(() => Close(Password), nextCommandCanExecute);
|
||||
CancelCommand = ReactiveCommand.Create(() => Close(), cancelCommandCanExecute);
|
||||
BackCommand = ReactiveCommand.Create(() => Close(DialogResultKind.Back), backCommandCanExecute);
|
||||
NextCommand = ReactiveCommand.Create(() => Close(result: Password), nextCommandCanExecute);
|
||||
CancelCommand = ReactiveCommand.Create(() => Close(DialogResultKind.Cancel), cancelCommandCanExecute);
|
||||
}
|
||||
|
||||
public string Subtitle { get; }
|
||||
public string Caption { get; }
|
||||
|
||||
protected override void OnDialogClosed()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
using ReactiveUI;
|
||||
|
||||
namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
||||
{
|
||||
public class ShowErrorDialogViewModel : DialogViewModelBase<bool>
|
||||
{
|
||||
public ShowErrorDialogViewModel(string message, string title, string caption)
|
||||
{
|
||||
Message = message;
|
||||
Title = title;
|
||||
Caption = caption;
|
||||
|
||||
NextCommand = ReactiveCommand.Create(() => Navigate().Back());
|
||||
}
|
||||
|
||||
public string Message { get; }
|
||||
public string Caption { get; }
|
||||
}
|
||||
}
|
|
@ -9,16 +9,17 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
|
||||
public TestDialogViewModel(string message)
|
||||
{
|
||||
Title = "Test Dialog";
|
||||
_message = message;
|
||||
|
||||
BackCommand = ReactiveCommand.Create(
|
||||
() => Navigate().Back(),
|
||||
this.WhenAnyValue(x => x.IsDialogOpen).ObserveOn(RxApp.MainThreadScheduler));
|
||||
CancelCommand = ReactiveCommand.Create(
|
||||
() => Close(false),
|
||||
() => Close(result: false),
|
||||
this.WhenAnyValue(x => x.IsDialogOpen).ObserveOn(RxApp.MainThreadScheduler));
|
||||
NextCommand = ReactiveCommand.Create(
|
||||
() => Close(true),
|
||||
() => Close(result: true),
|
||||
this.WhenAnyValue(x => x.IsDialogOpen).ObserveOn(RxApp.MainThreadScheduler));
|
||||
}
|
||||
|
||||
|
@ -28,7 +29,7 @@ namespace WalletWasabi.Fluent.ViewModels.Dialogs
|
|||
|
||||
public void Close()
|
||||
{
|
||||
Close(false);
|
||||
Close(result: false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,13 +4,14 @@ using System.Reactive;
|
|||
using System.Reactive.Linq;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
using WalletWasabi.Fluent.ViewModels.Dialogs;
|
||||
using WalletWasabi.Fluent.ViewModels.Navigation;
|
||||
using WalletWasabi.Helpers;
|
||||
using WalletWasabi.Logging;
|
||||
|
||||
namespace WalletWasabi.Fluent.ViewModels
|
||||
namespace WalletWasabi.Fluent.ViewModels.HelpAndSupport
|
||||
{
|
||||
[NavigationMetaData(
|
||||
Title = "About Wasabi",
|
||||
|
@ -34,19 +35,27 @@ namespace WalletWasabi.Fluent.ViewModels
|
|||
var interaction = new Interaction<Unit, Unit>();
|
||||
interaction.RegisterHandler(
|
||||
async x =>
|
||||
x.SetOutput(await new AboutAdvancedInfoViewModel().ShowDialogAsync()));
|
||||
x.SetOutput((await new AboutAdvancedInfoViewModel().ShowDialogAsync()).Result));
|
||||
|
||||
AboutAdvancedInfoDialogCommand = ReactiveCommand.CreateFromTask(
|
||||
execute: async () => await interaction.Handle(Unit.Default).ToTask());
|
||||
|
||||
OpenBrowserCommand.ThrownExceptions
|
||||
.ObserveOn(RxApp.TaskpoolScheduler)
|
||||
.Subscribe(ex => Logger.LogError(ex));
|
||||
OpenBrowserCommand = ReactiveCommand.CreateFromTask<string>(
|
||||
async (link) =>
|
||||
await IoHelpers.OpenBrowserAsync(link));
|
||||
|
||||
CopyLinkCommand = ReactiveCommand.CreateFromTask<string>(
|
||||
async (link) =>
|
||||
await Application.Current.Clipboard.SetTextAsync(link));
|
||||
|
||||
NextCommand = CancelCommand;
|
||||
}
|
||||
|
||||
public ICommand AboutAdvancedInfoDialogCommand { get; }
|
||||
|
||||
public ReactiveCommand<string, Unit> OpenBrowserCommand { get; }
|
||||
public ICommand OpenBrowserCommand { get; }
|
||||
|
||||
public ICommand CopyLinkCommand { get; }
|
||||
|
||||
public Version ClientVersion => Constants.ClientVersion;
|
||||
|
||||
|
@ -66,6 +75,6 @@ namespace WalletWasabi.Fluent.ViewModels
|
|||
|
||||
public static string DocsLink => "https://docs.wasabiwallet.io/";
|
||||
|
||||
public static string License => "https://github.com/zkSNACKs/WalletWasabi/blob/master/LICENSE.md";
|
||||
public static string LicenseLink => "https://github.com/zkSNACKs/WalletWasabi/blob/master/LICENSE.md";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -157,26 +157,22 @@ namespace WalletWasabi.Fluent.ViewModels
|
|||
BroadcastTransactionViewModel.RegisterAsyncLazy(
|
||||
async () =>
|
||||
{
|
||||
var result = await DialogScreen.NavigateDialog(new LoadTransactionViewModel(_global.Network));
|
||||
var dialogResult = await DialogScreen.NavigateDialog(new LoadTransactionViewModel(_global.Network));
|
||||
|
||||
if (result is { })
|
||||
if (dialogResult.Result is { })
|
||||
{
|
||||
while (_global.TransactionBroadcaster is null)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
DialogScreen.Back();
|
||||
|
||||
return new BroadcastTransactionViewModel(
|
||||
_global.BitcoinStore,
|
||||
_global.Network,
|
||||
_global.TransactionBroadcaster,
|
||||
result);
|
||||
dialogResult.Result);
|
||||
}
|
||||
|
||||
DialogScreen.Back();
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
|
|
|
@ -15,13 +15,10 @@ namespace WalletWasabi.Fluent.ViewModels.NavBar
|
|||
public abstract partial class NavBarItemViewModel : RoutableViewModel
|
||||
{
|
||||
private bool _isSelected;
|
||||
|
||||
[AutoNotify] private bool _isExpanded;
|
||||
[AutoNotify] private string _title;
|
||||
|
||||
protected NavBarItemViewModel()
|
||||
{
|
||||
_title = "";
|
||||
SelectionMode = NavBarItemSelectionMode.Selected;
|
||||
OpenCommand = ReactiveCommand.Create(
|
||||
() =>
|
||||
|
|
|
@ -11,6 +11,8 @@ namespace WalletWasabi.Fluent.ViewModels.Navigation
|
|||
public abstract partial class RoutableViewModel : ViewModelBase, INavigatable
|
||||
{
|
||||
[AutoNotify] private bool _isBusy;
|
||||
[AutoNotify] private string _title;
|
||||
|
||||
private CompositeDisposable? _currentDisposable;
|
||||
|
||||
public NavigationTarget CurrentTarget { get; internal set; }
|
||||
|
@ -19,8 +21,8 @@ namespace WalletWasabi.Fluent.ViewModels.Navigation
|
|||
|
||||
protected RoutableViewModel()
|
||||
{
|
||||
_title = "";
|
||||
BackCommand = ReactiveCommand.Create(() => Navigate().Back());
|
||||
|
||||
CancelCommand = ReactiveCommand.Create(() => Navigate().Clear());
|
||||
}
|
||||
|
||||
|
@ -91,10 +93,10 @@ namespace WalletWasabi.Fluent.ViewModels.Navigation
|
|||
{
|
||||
}
|
||||
|
||||
public async Task<TResult> NavigateDialog<TResult>(DialogViewModelBase<TResult> dialog)
|
||||
public async Task<DialogResult<TResult>> NavigateDialog<TResult>(DialogViewModelBase<TResult> dialog)
|
||||
=> await NavigateDialog(dialog, CurrentTarget);
|
||||
|
||||
public async Task<TResult> NavigateDialog<TResult>(DialogViewModelBase<TResult> dialog, NavigationTarget target)
|
||||
public async Task<DialogResult<TResult>> NavigateDialog<TResult>(DialogViewModelBase<TResult> dialog, NavigationTarget target)
|
||||
{
|
||||
Navigate(target).To(dialog);
|
||||
|
||||
|
@ -104,5 +106,11 @@ namespace WalletWasabi.Fluent.ViewModels.Navigation
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async Task ShowErrorAsync(string message, string caption)
|
||||
{
|
||||
var dialog = new ShowErrorDialogViewModel(message, Title, caption);
|
||||
await NavigateDialog(dialog, NavigationTarget.DialogScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace WalletWasabi.Fluent.ViewModels.Navigation
|
|||
{
|
||||
public static class NavigationExtensions
|
||||
{
|
||||
public static async Task<T> NavigateDialog<T>(
|
||||
public static async Task<DialogResult<T>> NavigateDialog<T>(
|
||||
this TargettedNavigationStack stack,
|
||||
DialogViewModelBase<T> dialog)
|
||||
{
|
||||
|
|
|
@ -29,23 +29,27 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
|
|||
[AutoNotify] private string _localBitcoinCoreDataDir;
|
||||
[AutoNotify] private bool _stopLocalBitcoinCoreOnShutdown;
|
||||
[AutoNotify] private string _bitcoinP2PEndPoint;
|
||||
[AutoNotify] private string _dustThreshold;
|
||||
|
||||
public BitcoinTabSettingsViewModel(Config config, UiConfig uiConfig) : base(config, uiConfig)
|
||||
{
|
||||
this.ValidateProperty(x => x.BitcoinP2PEndPoint, ValidateBitcoinP2PEndPoint);
|
||||
this.ValidateProperty(x => x.DustThreshold, ValidateDustThreshold);
|
||||
|
||||
_network = config.Network;
|
||||
_startLocalBitcoinCoreOnStartup = config.StartLocalBitcoinCoreOnStartup;
|
||||
_localBitcoinCoreDataDir = config.LocalBitcoinCoreDataDir;
|
||||
_stopLocalBitcoinCoreOnShutdown = config.StopLocalBitcoinCoreOnShutdown;
|
||||
_bitcoinP2PEndPoint = config.GetP2PEndpoint().ToString(defaultPort: -1);
|
||||
_dustThreshold = config.DustThreshold.ToString();
|
||||
|
||||
this.WhenAnyValue(
|
||||
x => x.Network,
|
||||
x => x.StartLocalBitcoinCoreOnStartup,
|
||||
x => x.StopLocalBitcoinCoreOnShutdown,
|
||||
x => x.BitcoinP2PEndPoint,
|
||||
x => x.LocalBitcoinCoreDataDir)
|
||||
x => x.LocalBitcoinCoreDataDir,
|
||||
x => x.DustThreshold)
|
||||
.ObserveOn(RxApp.TaskpoolScheduler)
|
||||
.Throttle(TimeSpan.FromMilliseconds(ThrottleTime))
|
||||
.Skip(1)
|
||||
|
@ -70,6 +74,27 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
|
|||
}
|
||||
}
|
||||
|
||||
private void ValidateDustThreshold(IValidationErrors errors) =>
|
||||
ValidateDustThreshold(errors, DustThreshold, whiteSpaceOk: true);
|
||||
|
||||
private void ValidateDustThreshold(IValidationErrors errors, string dustThreshold, bool whiteSpaceOk)
|
||||
{
|
||||
if (!whiteSpaceOk || !string.IsNullOrWhiteSpace(dustThreshold))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dustThreshold) && dustThreshold.Contains(
|
||||
',',
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
errors.Add(ErrorSeverity.Error, "Use decimal point instead of comma.");
|
||||
}
|
||||
|
||||
if (!decimal.TryParse(dustThreshold, out var dust) || dust < 0)
|
||||
{
|
||||
errors.Add(ErrorSeverity.Error, "Invalid dust threshold.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void EditConfigOnSave(Config config)
|
||||
{
|
||||
if (Network == config.Network)
|
||||
|
@ -82,6 +107,9 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
|
|||
config.StartLocalBitcoinCoreOnStartup = StartLocalBitcoinCoreOnStartup;
|
||||
config.StopLocalBitcoinCoreOnShutdown = StopLocalBitcoinCoreOnShutdown;
|
||||
config.LocalBitcoinCoreDataDir = Guard.Correct(LocalBitcoinCoreDataDir);
|
||||
config.DustThreshold = decimal.TryParse(DustThreshold, out var threshold)
|
||||
? Money.Coins(threshold)
|
||||
: Config.DefaultDustThreshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -29,12 +29,9 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
|
|||
[AutoNotify] private bool _customFee;
|
||||
[AutoNotify] private bool _customChangeAddress;
|
||||
[AutoNotify] private FeeDisplayFormat _selectedFeeDisplayFormat;
|
||||
[AutoNotify] private string _dustThreshold;
|
||||
|
||||
public GeneralSettingsTabViewModel(Config config, UiConfig uiConfig) : base(config, uiConfig)
|
||||
{
|
||||
this.ValidateProperty(x => x.DustThreshold, ValidateDustThreshold);
|
||||
|
||||
_darkModeEnabled = uiConfig.DarkModeEnabled;
|
||||
_autoCopy = uiConfig.Autocopy;
|
||||
_customFee = uiConfig.IsCustomFee;
|
||||
|
@ -42,13 +39,6 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
|
|||
_selectedFeeDisplayFormat = Enum.IsDefined(typeof(FeeDisplayFormat), uiConfig.FeeDisplayFormat)
|
||||
? (FeeDisplayFormat) uiConfig.FeeDisplayFormat
|
||||
: FeeDisplayFormat.SatoshiPerByte;
|
||||
_dustThreshold = config.DustThreshold.ToString();
|
||||
|
||||
this.WhenAnyValue(x => x.DustThreshold)
|
||||
.ObserveOn(RxApp.TaskpoolScheduler)
|
||||
.Throttle(TimeSpan.FromMilliseconds(ThrottleTime))
|
||||
.Skip(1)
|
||||
.Subscribe(_ => Save());
|
||||
|
||||
this.WhenAnyValue(x => x.DarkModeEnabled)
|
||||
.Skip(1)
|
||||
|
@ -83,32 +73,8 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
|
|||
public IEnumerable<FeeDisplayFormat> FeeDisplayFormats =>
|
||||
Enum.GetValues(typeof(FeeDisplayFormat)).Cast<FeeDisplayFormat>();
|
||||
|
||||
private void ValidateDustThreshold(IValidationErrors errors) =>
|
||||
ValidateDustThreshold(errors, DustThreshold, whiteSpaceOk: true);
|
||||
|
||||
private void ValidateDustThreshold(IValidationErrors errors, string dustThreshold, bool whiteSpaceOk)
|
||||
{
|
||||
if (!whiteSpaceOk || !string.IsNullOrWhiteSpace(dustThreshold))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dustThreshold) && dustThreshold.Contains(
|
||||
',',
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
errors.Add(ErrorSeverity.Error, "Use decimal point instead of comma.");
|
||||
}
|
||||
|
||||
if (!decimal.TryParse(dustThreshold, out var dust) || dust < 0)
|
||||
{
|
||||
errors.Add(ErrorSeverity.Error, "Invalid dust threshold.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void EditConfigOnSave(Config config)
|
||||
{
|
||||
config.DustThreshold = decimal.TryParse(DustThreshold, out var threshold)
|
||||
? Money.Coins(threshold)
|
||||
: Config.DefaultDustThreshold;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
|
|||
|
||||
this.WhenAnyValue(
|
||||
x => x.UseTor,
|
||||
x => x.UseTor,
|
||||
x => x.TerminateTorOnExit,
|
||||
x => x.TorSocks5EndPoint)
|
||||
.ObserveOn(RxApp.TaskpoolScheduler)
|
||||
.Throttle(TimeSpan.FromMilliseconds(ThrottleTime))
|
||||
|
|
|
@ -37,6 +37,8 @@ namespace WalletWasabi.Fluent.ViewModels.TransactionBroadcasting
|
|||
TransactionBroadcaster broadcaster,
|
||||
SmartTransaction transaction)
|
||||
{
|
||||
Title = "Broadcast Transaction";
|
||||
|
||||
var nullMoney = new Money(-1L);
|
||||
var nullOutput = new TxOut(nullMoney, Script.Empty);
|
||||
|
||||
|
@ -84,15 +86,14 @@ namespace WalletWasabi.Fluent.ViewModels.TransactionBroadcasting
|
|||
try
|
||||
{
|
||||
await broadcaster.SendTransactionAsync(transaction);
|
||||
Navigate().To(new SuccessBroadcastTransactionViewModel());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: Notify the user
|
||||
Logger.LogError(ex);
|
||||
await ShowErrorAsync(ex.ToUserFriendlyString(), "It was not possible to broadcast the transaction.");
|
||||
}
|
||||
|
||||
Navigate().Back();
|
||||
|
||||
IsBusy = false;
|
||||
},
|
||||
nextCommandCanExecute);
|
||||
|
|
|
@ -19,19 +19,20 @@ namespace WalletWasabi.Fluent.ViewModels.TransactionBroadcasting
|
|||
|
||||
public LoadTransactionViewModel(Network network)
|
||||
{
|
||||
Title = "Transaction Broadcaster";
|
||||
Network = network;
|
||||
|
||||
this.WhenAnyValue(x => x.FinalTransaction)
|
||||
.Where(x => x is { })
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(finalTransaction => Close(finalTransaction));
|
||||
.Subscribe(finalTransaction => Close(result: finalTransaction));
|
||||
|
||||
ImportTransactionCommand = ReactiveCommand.CreateFromTask(
|
||||
async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = await FileDialogHelper.ShowOpenFileDialogAsync("Import Transaction", filterExtTypes: new[] {"psbt", "*"});
|
||||
var path = await FileDialogHelper.ShowOpenFileDialogAsync("Import Transaction", new[] {"psbt", "*"});
|
||||
if (path is { })
|
||||
{
|
||||
FinalTransaction = await ParseTransactionAsync(path);
|
||||
|
@ -40,6 +41,7 @@ namespace WalletWasabi.Fluent.ViewModels.TransactionBroadcasting
|
|||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
await ShowErrorAsync(ex.ToUserFriendlyString(), "It was not possible to load the transaction.");
|
||||
}
|
||||
},
|
||||
outputScheduler: RxApp.MainThreadScheduler);
|
||||
|
@ -71,8 +73,8 @@ namespace WalletWasabi.Fluent.ViewModels.TransactionBroadcasting
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: Notify the user
|
||||
Logger.LogError(ex);
|
||||
await ShowErrorAsync(ex.ToUserFriendlyString(), "It was not possible to paste the transaction.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using ReactiveUI;
|
||||
using WalletWasabi.Fluent.ViewModels.Navigation;
|
||||
|
||||
namespace WalletWasabi.Fluent.ViewModels.TransactionBroadcasting
|
||||
{
|
||||
public class SuccessBroadcastTransactionViewModel : RoutableViewModel
|
||||
{
|
||||
public SuccessBroadcastTransactionViewModel()
|
||||
{
|
||||
Title = "Success";
|
||||
|
||||
NextCommand = ReactiveCommand.Create(() => Navigate().Clear());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:WalletWasabi.Fluent.ViewModels"
|
||||
xmlns:controls="clr-namespace:WalletWasabi.Fluent.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="vm:AboutViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.AboutView">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="TextBlock#LogoText1">
|
||||
<Style.Animations>
|
||||
<Animation Delay="0.3" Duration="0.37" Easing="{StaticResource FluentEasing}">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="12" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="TextBlock#LogoText2">
|
||||
<Style.Animations>
|
||||
<Animation Delay="0.32" Duration="0.37" Easing="{StaticResource FluentEasing}">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="-12" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="TextBlock#LogoText3">
|
||||
<Setter Property="Foreground" Value="{StaticResource VersionHighlightRadialBrush}" />
|
||||
<Setter Property="RenderTransformOrigin" Value="50%,50%" />
|
||||
<Style.Animations>
|
||||
<Animation Delay="0.34" Duration="0.37" Easing="{StaticResource FluentEasing}">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="(ScaleTransform.ScaleX)" Value="0.80" />
|
||||
<Setter Property="(ScaleTransform.ScaleY)" Value="0.80" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="(ScaleTransform.ScaleX)" Value="1" />
|
||||
<Setter Property="(ScaleTransform.ScaleY)" Value="1" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.LinkSeparatorPadding">
|
||||
<Setter Property="Margin" Value="5 -0.5" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.Header">
|
||||
<Setter Property="Margin" Value="0 4 0 0" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.Hyperlink">
|
||||
<Setter Property="TextDecorations" Value="Underline" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColor}" />
|
||||
</Style>
|
||||
<Style Selector="TextBox.Content">
|
||||
<Setter Property="Margin" Value="0 4 0 0" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Panel>
|
||||
<controls:ContentArea>
|
||||
<controls:ContentArea.Title>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Name="LogoText1" Classes="h1 semibold">Wasabi</TextBlock>
|
||||
<Panel Width="5" Height="5" />
|
||||
<TextBlock Name="LogoText2" Classes="h1 light">Wallet</TextBlock>
|
||||
<Panel Width="10" Height="10" />
|
||||
<TextBlock Name="LogoText3" Classes="h1 ultrabold">2.0</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:ContentArea.Title>
|
||||
<controls:ContentArea.Caption>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Classes="Header" Text="Current Version: " />
|
||||
<TextBox Classes="Content selectable_text_block"
|
||||
Text="{Binding ClientVersion}" />
|
||||
</StackPanel>
|
||||
</controls:ContentArea.Caption>
|
||||
|
||||
<DockPanel>
|
||||
<StackPanel Spacing="10" DockPanel.Dock="Bottom" HorizontalAlignment="Left" Orientation="Vertical">
|
||||
<WrapPanel Margin="0 0 0 25">
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{Binding DocsLink}">
|
||||
<TextBlock Text="Documentation" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{Binding SourceCodeLink}">
|
||||
<TextBlock Text="Source Code (Github)" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{Binding ClearnetLink}">
|
||||
<TextBlock Text="Website (Clearnet)" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}" CommandParameter="{Binding TorLink}">
|
||||
<TextBlock Text="Website (Tor)" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{Binding StatusPageLink}">
|
||||
<TextBlock Text="Coordinator Status Page" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{Binding UserSupportLink}">
|
||||
<TextBlock Text="User Support" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{Binding BugReportLink}">
|
||||
<TextBlock Text="Bug Reporting" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}" CommandParameter="{Binding FAQLink}">
|
||||
<TextBlock Text="FAQs" Classes="Hyperlink" />
|
||||
</Button>
|
||||
</WrapPanel>
|
||||
|
||||
<Button HorizontalAlignment="Center" Classes="plain obscured"
|
||||
Command="{Binding AboutAdvancedInfoDialogCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<PathIcon Data="{StaticResource info_regular}" />
|
||||
<TextBlock Text="Advanced Information..." Classes="Hyperlink" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<WrapPanel HorizontalAlignment="Center">
|
||||
<TextBlock Margin=" 0 0 0 -0.5" Text="This open source software is licensed with " />
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{Binding License}">
|
||||
<TextBlock Text="MIT License" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<TextBlock Margin=" 0 0 0 -0.5" Text="." />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
<Panel Height="150" />
|
||||
</DockPanel>
|
||||
</controls:ContentArea>
|
||||
</Panel>
|
||||
</UserControl>
|
|
@ -10,13 +10,13 @@
|
|||
x:DataType="vm:AddWalletPageViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.AddWallet.AddWalletPageView">
|
||||
<c:ContentArea Title="Add Wallet" Caption="Type in your wallet's name and select an option:"
|
||||
<c:ContentArea Title="{Binding Title}" Caption="Type in your wallet's name and select an option:"
|
||||
EnableBack="{Binding EnableBack}"
|
||||
IsBusy="{Binding IsBusy}">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Margin="0 0 0 20">
|
||||
<Label Content="Wallet _Name:" Target="walletNameTb" />
|
||||
<TextBox x:Name="walletNameTb" Text="{Binding WalletName}" Watermark="(e.g. My Wallet)">
|
||||
<TextBox x:Name="walletNameTb" MaxLength="250" Text="{Binding WalletName}" Watermark="(e.g. My Wallet)">
|
||||
<i:Interaction.Behaviors>
|
||||
<behaviors:FocusOnAttachedBehavior />
|
||||
</i:Interaction.Behaviors>
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
x:DataType="vm:AddedWalletPageViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.AddWallet.AddedWalletPageView">
|
||||
<c:ContentArea Title="Success" NextContent="Done" EnableNext="True" EnableCancel="False" EnableBack="False"
|
||||
<c:ContentArea Title="{Binding Title}" NextContent="Done" EnableNext="True" EnableCancel="False" EnableBack="False"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Bottom" Margin="0 40 0 0">
|
||||
<TextBlock Text="Your wallet " />
|
||||
<TextBlock Text="{Binding WalletName}" FontWeight="Bold"/>
|
||||
|
@ -19,7 +19,7 @@
|
|||
</StackPanel>
|
||||
<Viewbox MaxHeight="200">
|
||||
<Image Source="{Binding Path='Type', Converter={x:Static conv:WalletTypeImageConverter.Instance}}" />
|
||||
</Viewbox>
|
||||
</Viewbox>
|
||||
</DockPanel>
|
||||
</c:ContentArea>
|
||||
</UserControl>
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
<UserControl.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding NextCommand}" />
|
||||
</UserControl.KeyBindings>
|
||||
<c:ContentArea Title="Confirm recovery words" Caption="Type the required recovery words"
|
||||
<c:ContentArea Title="{Binding Title}" Caption="Type the required recovery words"
|
||||
EnableBack="True"
|
||||
EnableCancel="True" CancelContent="Cancel"
|
||||
EnableCancel="False"
|
||||
EnableNext="True" NextContent="Continue">
|
||||
<ScrollViewer>
|
||||
<ItemsControl HorizontalAlignment="Center" Items="{Binding ConfirmationWords}">
|
||||
<ItemsControl HorizontalAlignment="Center" VerticalAlignment="Center" Items="{Binding ConfirmationWords}">
|
||||
<i:Interaction.Behaviors>
|
||||
<behaviors:FocusFirstTextBoxInItemsControlBehavior />
|
||||
</i:Interaction.Behaviors>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
x:CompileBindings="True"
|
||||
x:DataType="create:RecoveryWordsViewModel"
|
||||
x:Class="WalletWasabi.Fluent.Views.AddWallet.Create.RecoveryWordsView">
|
||||
<c:ContentArea Title="Recovery words"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="Write down the following numbered recovery words in order. These can be used to recover your wallet in the future. It is very important you keep them in a safe place."
|
||||
EnableCancel="True" CancelContent="Cancel"
|
||||
EnableBack="True"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.AddWallet.HardwareWallet.ConnectHardwareWalletView">
|
||||
|
||||
<c:ContentArea Title="Hardware Wallet"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="Connect your hardware wallet to the PC"
|
||||
EnableCancel="True" CancelContent="Cancel"
|
||||
EnableBack="True"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
x:DataType="hardwareWallet:DetectedHardwareWalletViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.AddWallet.HardwareWallet.DetectedHardwareWalletView">
|
||||
<c:ContentArea Title="Hardware Wallet"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
IsBusy="{Binding IsBusy}"
|
||||
EnableCancel="False"
|
||||
EnableBack="False"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
x:CompileBindings="True"
|
||||
x:DataType="vm:LegalDocumentsViewModel"
|
||||
x:Class="WalletWasabi.Fluent.Views.AddWallet.LegalDocumentsView">
|
||||
<c:ContentArea Title="Terms and Conditions"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="Please read the small print."
|
||||
EnableNext="True" NextContent="Continue" FocusNext="True">
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Content}" />
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.AddWallet.RecoverWalletView">
|
||||
<c:ContentArea x:Name="RecoveryPageRoot"
|
||||
Title="Enter recovery words" Caption="Type in your 12 recovery words and click Continue."
|
||||
Title="{Binding Title}" Caption="Type in your 12 recovery words and click Continue."
|
||||
EnableBack="True"
|
||||
EnableCancel="True" CancelContent="Cancel"
|
||||
EnableNext="True" NextContent="Finish"
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<UserControl.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding NextCommand}" />
|
||||
</UserControl.KeyBindings>
|
||||
<c:ContentArea Title="Welcome to Wasabi Wallet"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
EnableCancel="False"
|
||||
EnableNext="True" NextContent="Continue" FocusNext="True"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</UserControl.Styles>
|
||||
<controls:ContentArea Title="Advanced Information"
|
||||
EnableCancel="False"
|
||||
EnableNext="False">
|
||||
EnableNext="True" NextContent="OK">
|
||||
<StackPanel HorizontalAlignment="Center" Spacing="20" MinWidth="380" Margin="30 0 0 0">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="Header" Text="Compatible Coordinator Versions" />
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<Setter Property="MinHeight" Value="25" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<c:ContentArea Title="Advanced Recovery Options"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="Type your wallet-specific recovery options and click Continue."
|
||||
EnableCancel="True" CancelContent="Cancel"
|
||||
EnableNext="True" NextContent="Continue">
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
<UserControl.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding NextCommand}" />
|
||||
</UserControl.KeyBindings>
|
||||
<c:ContentArea Title="Enter a password"
|
||||
Caption="{Binding Subtitle}"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="{Binding Caption}"
|
||||
EnableBack="True"
|
||||
EnableCancel="True" CancelContent="Cancel"
|
||||
EnableNext="True" NextContent="Continue">
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:c="using:WalletWasabi.Fluent.Controls"
|
||||
xmlns:dialog="using:WalletWasabi.Fluent.ViewModels.Dialogs"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="dialog:ShowErrorDialogViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.Dialogs.ShowErrorDialogView">
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="{Binding Caption}"
|
||||
EnableNext="True" NextContent="Ok"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Message}" DockPanel.Dock="Bottom" TextAlignment="Center" TextWrapping="Wrap" MaxWidth="500" Margin="20 0" />
|
||||
<Viewbox MaxHeight="150" Margin="40">
|
||||
<PathIcon Data="{StaticResource warning_regular}" />
|
||||
</Viewbox>
|
||||
</DockPanel>
|
||||
</c:ContentArea>
|
||||
</UserControl>
|
|
@ -0,0 +1,18 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace WalletWasabi.Fluent.Views.Dialogs
|
||||
{
|
||||
public class ShowErrorDialogView : UserControl
|
||||
{
|
||||
public ShowErrorDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
x:DataType="dialog:TestDialogViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.Dialogs.TestDialogView">
|
||||
<c:ContentArea Title="Test Dialog" Caption="A quick example of a dialog:" EnableCancel="False" EnableNext="True" NextContent="Confirm">
|
||||
<c:ContentArea Title="{Binding Title}" Caption="A quick example of a dialog:" EnableCancel="False" EnableNext="True" NextContent="Confirm">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Message}" />
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:WalletWasabi.Fluent.ViewModels.HelpAndSupport"
|
||||
xmlns:controls="clr-namespace:WalletWasabi.Fluent.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="vm:AboutViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.HelpAndSupport.AboutView">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="TextBlock#LogoText1">
|
||||
<Style.Animations>
|
||||
<Animation Delay="0.3" Duration="0.37" Easing="{StaticResource FluentEasing}">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="12" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="TextBlock#LogoText2">
|
||||
<Style.Animations>
|
||||
<Animation Delay="0.32" Duration="0.37" Easing="{StaticResource FluentEasing}">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="-12" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="(TranslateTransform.Y)" Value="0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="TextBlock#LogoText3">
|
||||
<Setter Property="Foreground" Value="{StaticResource VersionHighlightRadialBrush}" />
|
||||
<Setter Property="RenderTransformOrigin" Value="50%,50%" />
|
||||
<Style.Animations>
|
||||
<Animation Delay="0.34" Duration="0.37" Easing="{StaticResource FluentEasing}">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="(ScaleTransform.ScaleX)" Value="0.80" />
|
||||
<Setter Property="(ScaleTransform.ScaleY)" Value="0.80" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Setter Property="(ScaleTransform.ScaleX)" Value="1" />
|
||||
<Setter Property="(ScaleTransform.ScaleY)" Value="1" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.LinkSeparatorPadding">
|
||||
<Setter Property="Margin" Value="5 -0.5" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.Header">
|
||||
<Setter Property="Margin" Value="0 4 0 0" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.Hyperlink">
|
||||
<Setter Property="TextDecorations" Value="Underline" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColor}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="TextBox.Content">
|
||||
<Setter Property="Margin" Value="0 4 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Button > PathIcon">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</Style>
|
||||
<Style Selector="Button:pointerover > PathIcon">
|
||||
<Setter Property="Opacity" Value="0.75" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Panel>
|
||||
<controls:ContentArea EnableNext="True" NextContent="Close">
|
||||
<controls:ContentArea.Title>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Name="LogoText1" Classes="h1 semibold">Wasabi</TextBlock>
|
||||
<Panel Width="5" Height="5" />
|
||||
<TextBlock Name="LogoText2" Classes="h1 light">Wallet</TextBlock>
|
||||
<Panel Width="10" Height="10" />
|
||||
<TextBlock Name="LogoText3" Classes="h1 ultrabold">2.0</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:ContentArea.Title>
|
||||
<controls:ContentArea.Caption>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Classes="Header" Text="Current Version: " />
|
||||
<TextBox Classes="Content selectable_text_block"
|
||||
Text="{Binding ClientVersion}" />
|
||||
</StackPanel>
|
||||
</controls:ContentArea.Caption>
|
||||
<DockPanel>
|
||||
<StackPanel Spacing="10" DockPanel.Dock="Bottom" HorizontalAlignment="Center" Orientation="Vertical">
|
||||
<WrapPanel Margin="0 0 0 25" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain"
|
||||
Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.DocsLink}">
|
||||
<TextBlock Text="Documentation" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.DocsLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.SourceCodeLink}">
|
||||
<TextBlock Text="Source Code (Github)" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.SourceCodeLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain"
|
||||
Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.ClearnetLink}">
|
||||
<TextBlock Text="Website (Clearnet)" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.ClearnetLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<TextBlock Text="Website (Tor)" />
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.TorLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain"
|
||||
Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.StatusPageLink}">
|
||||
<TextBlock Text="Coordinator Status Page" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.StatusPageLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.UserSupportLink}">
|
||||
<TextBlock Text="User Support" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.UserSupportLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain" Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.BugReportLink}">
|
||||
<TextBlock Text="Bug Reporting" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.BugReportLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="LinkSeparatorPadding" Text="-" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain"
|
||||
Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.FAQLink}">
|
||||
<TextBlock Text="FAQs" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.FAQLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
<Button HorizontalAlignment="Center" Classes="plain obscured"
|
||||
Command="{Binding AboutAdvancedInfoDialogCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<PathIcon Data="{StaticResource info_regular}" />
|
||||
<TextBlock Text="Advanced Information..." Classes="Hyperlink" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<WrapPanel HorizontalAlignment="Center">
|
||||
<TextBlock Margin=" 0 0 0 -0.5"
|
||||
VerticalAlignment="Center"
|
||||
Text="This open source software is licensed with " />
|
||||
<StackPanel Orientation="Horizontal" Spacing="5" Margin="2">
|
||||
<Button Classes="plain"
|
||||
Command="{Binding OpenBrowserCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.LicenseLink}">
|
||||
<TextBlock Text="MIT License" Classes="Hyperlink" />
|
||||
</Button>
|
||||
<Button Classes="plain"
|
||||
Command="{Binding CopyLinkCommand}"
|
||||
CommandParameter="{x:Static vm:AboutViewModel.LicenseLink}"
|
||||
ToolTip.Tip="Copy link">
|
||||
<PathIcon Data="{StaticResource copy_regular}" Width="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
<Panel Height="150" />
|
||||
</DockPanel>
|
||||
</controls:ContentArea>
|
||||
</Panel>
|
||||
</UserControl>
|
|
@ -1,7 +1,7 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace WalletWasabi.Fluent.Views
|
||||
namespace WalletWasabi.Fluent.Views.HelpAndSupport
|
||||
{
|
||||
public class AboutView : UserControl
|
||||
{
|
|
@ -50,6 +50,8 @@
|
|||
<c:Dialog x:CompileBindings="False"
|
||||
DataContext="{Binding DialogScreen}"
|
||||
IsDialogOpen="{Binding IsDialogOpen, Mode=TwoWay}"
|
||||
EnableCancelOnPressed="False"
|
||||
EnableCancelOnEscape="True"
|
||||
IsEnabled="{Binding $parent.DataContext.IsDialogScreenEnabled}"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
MaxContentWidth="800" MaxContentHeight="700">
|
||||
|
@ -57,6 +59,8 @@
|
|||
</c:Dialog>
|
||||
<c:Dialog DataContext="{Binding CurrentDialog}"
|
||||
IsDialogOpen="{Binding IsDialogOpen, Mode=TwoWay}"
|
||||
EnableCancelOnPressed="False"
|
||||
EnableCancelOnEscape="True"
|
||||
Content="{Binding }"
|
||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center" />
|
||||
</Panel>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
x:DataType="vm:SearchPageViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.Search.SearchPageView">
|
||||
<c:ContentArea Title="Action Center" IsBusy="{Binding IsBusy}">
|
||||
<c:ContentArea Title="{Binding Title}" IsBusy="{Binding IsBusy}">
|
||||
<c:ContentArea.Caption>
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Left" Text="Locate and discover features in Wasabi" />
|
||||
|
|
|
@ -59,5 +59,10 @@
|
|||
<TextBox Text="{Binding BitcoinP2PEndPoint}" />
|
||||
</DockPanel>
|
||||
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock ToolTip.Tip="Coins under the dust threshold won't appear in the coin lists." Text= "Dust Threshold (BTC)" />
|
||||
<TextBox Text="{Binding DustThreshold}" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</UserControl>
|
|
@ -54,9 +54,6 @@
|
|||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock ToolTip.Tip="Coins under the dust threshold won't appear in the coin lists." Text= "Dust Threshold (BTC)" />
|
||||
<TextBox Text="{Binding DustThreshold}" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</UserControl>
|
|
@ -29,7 +29,8 @@
|
|||
Opacity="{Binding IsModified, Converter={x:Static conv:BoolOpacityConverters.BoolToOpacity}}"
|
||||
DockPanel.Dock="Bottom" />
|
||||
|
||||
<c:ContentArea Title="Settings" Caption="Manage appearance, privacy and other settings"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="Manage appearance, privacy and other settings"
|
||||
EnableBack="True">
|
||||
<DockPanel LastChildFill="True">
|
||||
|
||||
|
|
|
@ -9,16 +9,17 @@
|
|||
x:DataType="transactionBroadcasting:BroadcastTransactionViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.TransactionBroadcasting.BroadcastTransactionView">
|
||||
<c:ContentArea Title="Broadcast Transaction"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="{Binding TransactionId}"
|
||||
EnableCancel="{Binding !IsBusy}"
|
||||
EnableNext="True" NextContent="Broadcast">
|
||||
EnableNext="True" NextContent="Broadcast"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
|
||||
<DockPanel LastChildFill="True">
|
||||
<DockPanel.Styles>
|
||||
<Style Selector="TextBlock.unit">
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
<Setter Property="Margin" Value="3 0 0 6"/>
|
||||
<Setter Property="Margin" Value="3 0 0 5"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
</Style>
|
||||
|
||||
|
@ -31,7 +32,7 @@
|
|||
|
||||
<Style Selector="StackPanel.amountValue">
|
||||
<Setter Property="Orientation" Value="Horizontal"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="TextBlock.FontSize" Value="35"/>
|
||||
<Setter Property="TextBlock.FontWeight" Value="UltraLight"/>
|
||||
</Style>
|
||||
|
@ -56,39 +57,38 @@
|
|||
<TextBlock Text="{Binding OutputCountString}" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid ColumnDefinitions="*,*,*" DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" Margin="20">
|
||||
<StackPanel Grid.Column="0" HorizontalAlignment="Left">
|
||||
<TextBlock Text="Input" Classes="amountTitle"/>
|
||||
<StackPanel Classes="amountValue">
|
||||
<TextBlock Text="{Binding TotalInputValue, Converter={x:Static converters:MoneyConverters.MoneyToString}}" />
|
||||
<TextBlock Text="BTC" Classes="unit" IsVisible="{Binding !!TotalInputValue}"/>
|
||||
<Viewbox DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" Margin="20 0 20 20">
|
||||
<Grid ColumnDefinitions="230,*,230">
|
||||
<StackPanel Grid.Column="0" HorizontalAlignment="Left">
|
||||
<TextBlock Text="Input" Classes="amountTitle"/>
|
||||
<StackPanel Classes="amountValue">
|
||||
<TextBlock Text="{Binding TotalInputValue, Converter={x:Static converters:MoneyConverters.MoneyToString}}" />
|
||||
<TextBlock Text="BTC" Classes="unit" IsVisible="{Binding !!TotalInputValue}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Output" Classes="amountTitle" />
|
||||
<StackPanel Classes="amountValue">
|
||||
<TextBlock Text="{Binding TotalOutputValue, Converter={x:Static converters:MoneyConverters.MoneyToString}}" />
|
||||
<TextBlock Text="BTC" Classes="unit" IsVisible="{Binding !!TotalOutputValue}" />
|
||||
<StackPanel Grid.Column="1" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Output" Classes="amountTitle" />
|
||||
<StackPanel Classes="amountValue">
|
||||
<TextBlock Text="{Binding TotalOutputValue, Converter={x:Static converters:MoneyConverters.MoneyToString}}" />
|
||||
<TextBlock Text="BTC" Classes="unit" IsVisible="{Binding !!TotalOutputValue}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Fee" Classes="amountTitle"/>
|
||||
<StackPanel Classes="amountValue">
|
||||
<TextBlock Text="{Binding NetworkFee, Converter={x:Static converters:MoneyConverters.MoneyToString}}" />
|
||||
<TextBlock Text="BTC" Classes="unit" IsVisible="{Binding !!NetworkFee}" />
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Fee" Classes="amountTitle"/>
|
||||
<StackPanel Classes="amountValue">
|
||||
<TextBlock Text="{Binding NetworkFee, Converter={x:Static converters:MoneyConverters.MoneyToString}}" />
|
||||
<TextBlock Text="BTC" Classes="unit" IsVisible="{Binding !!NetworkFee}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
|
||||
<Viewbox Width="300" DockPanel.Dock="Top" HorizontalAlignment="Center">
|
||||
<Viewbox.Resources>
|
||||
<SolidColorBrush x:Key="SystemAccentSolidColorBrush" Color="{StaticResource SystemAccentColor}"/>
|
||||
</Viewbox.Resources>
|
||||
<Viewbox MaxHeight="200" DockPanel.Dock="Top" HorizontalAlignment="Center">
|
||||
<Viewbox.Styles>
|
||||
<Style Selector="PathIcon">
|
||||
<Setter Property="Foreground" Value="{StaticResource SystemAccentSolidColorBrush}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColor}"/>
|
||||
<Setter Property="Opacity" Value="0.6"/>
|
||||
</Style>
|
||||
<Style Selector="PathIcon.broadcasting">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
x:DataType="transactionBroadcasting:LoadTransactionViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.TransactionBroadcasting.LoadTransactionView">
|
||||
<c:ContentArea Title="Transaction Broadcaster"
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
Caption="Paste or import the transaction and broadcast it to the network."
|
||||
EnableCancel="True">
|
||||
<StackPanel Orientation="Horizontal" Spacing="50" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:c="clr-namespace:WalletWasabi.Fluent.Controls"
|
||||
xmlns:transactionBroadcasting="clr-namespace:WalletWasabi.Fluent.ViewModels.TransactionBroadcasting"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:DataType="transactionBroadcasting:SuccessBroadcastTransactionViewModel"
|
||||
x:CompileBindings="True"
|
||||
x:Class="WalletWasabi.Fluent.Views.TransactionBroadcasting.SuccessBroadcastTransactionView">
|
||||
<c:ContentArea Title="{Binding Title}"
|
||||
EnableNext="True" NextContent="Done"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
<DockPanel>
|
||||
<TextBlock Text="Your transaction was successfully broadcasted." DockPanel.Dock="Bottom" TextAlignment="Center" TextWrapping="Wrap" Margin="0 40"/>
|
||||
<Viewbox MaxHeight="200" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Viewbox.Styles>
|
||||
<Style Selector=":is(PathIcon).fadeIn">
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:1">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0.0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1.0"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</Viewbox.Styles>
|
||||
<PathIcon Data="{StaticResource checkmark_filled}" Foreground="{DynamicResource SystemControlBackgroundAccentBrush}" Classes="fadeIn" />
|
||||
</Viewbox>
|
||||
</DockPanel>
|
||||
</c:ContentArea>
|
||||
</UserControl>
|
|
@ -0,0 +1,18 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace WalletWasabi.Fluent.Views.TransactionBroadcasting
|
||||
{
|
||||
public class SuccessBroadcastTransactionView : UserControl
|
||||
{
|
||||
public SuccessBroadcastTransactionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using WalletWasabi.Fluent.Helpers;
|
||||
|
||||
namespace WalletWasabi.Fluent.Views.Wallets
|
||||
{
|
||||
|
|
|
@ -7,7 +7,6 @@ using NBitcoin;
|
|||
using NBitcoin.Protocol;
|
||||
using NBitcoin.Protocol.Behaviors;
|
||||
using NBitcoin.Protocol.Connectors;
|
||||
using Nito.AsyncEx;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -105,8 +104,6 @@ namespace WalletWasabi.Gui
|
|||
|
||||
BitcoinStore = new BitcoinStore(indexStore, transactionStore, mempoolService, blocks);
|
||||
|
||||
SingleInstanceChecker = new SingleInstanceChecker(Network);
|
||||
|
||||
WasabiClientFactory wasabiClientFactory = Config.UseTor
|
||||
? new WasabiClientFactory(Config.TorSocks5EndPoint, backendUriGetter: () => Config.GetCurrentBackendUri())
|
||||
: new WasabiClientFactory(torEndPoint: null, backendUriGetter: () => Config.GetFallbackBackendUri());
|
||||
|
@ -121,8 +118,6 @@ namespace WalletWasabi.Gui
|
|||
|
||||
private CancellationTokenSource StoppingCts { get; }
|
||||
|
||||
private SingleInstanceChecker SingleInstanceChecker { get; }
|
||||
|
||||
public async Task InitializeNoWalletAsync(TerminateService terminateService)
|
||||
{
|
||||
InitializationStarted = true;
|
||||
|
@ -130,8 +125,6 @@ namespace WalletWasabi.Gui
|
|||
|
||||
try
|
||||
{
|
||||
await SingleInstanceChecker.EnsureSingleOrThrowAsync().ConfigureAwait(false);
|
||||
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
|
||||
Cache = new MemoryCache(new MemoryCacheOptions
|
||||
|
@ -753,15 +746,6 @@ namespace WalletWasabi.Gui
|
|||
}
|
||||
|
||||
Logger.LogDebug($"Step: {nameof(SingleInstanceChecker)}.", nameof(Global));
|
||||
|
||||
try
|
||||
{
|
||||
await SingleInstanceChecker.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error during the disposal of {nameof(SingleInstanceChecker)}: {ex}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -320,7 +320,7 @@ namespace WalletWasabi.Tests.RegressionTests
|
|||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
throw ex;
|
||||
throw;
|
||||
}
|
||||
return; // Occassionally this fails on Linux or OSX, I have no idea why.
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using WalletWasabi.Blockchain.Keys;
|
||||
using WalletWasabi.Wallets;
|
||||
|
||||
namespace WalletWasabi.Helpers
|
||||
{
|
||||
public static class ImportWalletHelper
|
||||
{
|
||||
private const string WalletExistsErrorMessage = "Wallet with the same fingerprint already exists!";
|
||||
|
||||
public static async Task<bool> ImportWalletAsync(WalletManager walletManager, string walletName, string filePath)
|
||||
{
|
||||
var walletFullPath = walletManager.WalletDirectories.GetWalletFilePaths(walletName).walletFilePath;
|
||||
|
||||
string jsonString = await File.ReadAllTextAsync(filePath);
|
||||
var jsonWallet = JObject.Parse(jsonString);
|
||||
|
||||
// TODO: Better logic to distinguish wallets.
|
||||
// If Count <= 3 then it is a possible Coldcard json otherwise possible Wasabi json
|
||||
var isColdcardJson = jsonWallet.Count <= 3;
|
||||
|
||||
KeyManager km = isColdcardJson
|
||||
? GetKeyManagerByColdcardJson(walletManager, jsonWallet, walletFullPath)
|
||||
: GetKeyManagerByWasabiJson(walletManager, filePath, walletFullPath);
|
||||
|
||||
walletManager.AddWallet(km);
|
||||
|
||||
return isColdcardJson;
|
||||
}
|
||||
|
||||
private static KeyManager GetKeyManagerByWasabiJson(WalletManager manager, string filePath, string walletFullPath)
|
||||
{
|
||||
var km = KeyManager.FromFile(filePath);
|
||||
|
||||
if (manager.WalletExists(km.MasterFingerprint))
|
||||
{
|
||||
throw new InvalidOperationException(WalletExistsErrorMessage);
|
||||
}
|
||||
|
||||
km.SetFilePath(walletFullPath);
|
||||
|
||||
return km;
|
||||
}
|
||||
|
||||
private static KeyManager GetKeyManagerByColdcardJson(WalletManager manager, JObject jsonWallet, string walletFullPath)
|
||||
{
|
||||
var xpubString = jsonWallet["ExtPubKey"].ToString();
|
||||
var mfpString = jsonWallet["MasterFingerprint"].ToString();
|
||||
|
||||
// https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066
|
||||
// Coldcard 2.1.0 improperly implemented Wasabi skeleton fingerprint at first, so we must reverse byte order.
|
||||
// The solution was to add a ColdCardFirmwareVersion json field from 2.1.1 and correct the one generated by 2.1.0.
|
||||
var coldCardVersionString = jsonWallet["ColdCardFirmwareVersion"]?.ToString();
|
||||
var reverseByteOrder = false;
|
||||
if (coldCardVersionString is null)
|
||||
{
|
||||
reverseByteOrder = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Version coldCardVersion = new (coldCardVersionString);
|
||||
|
||||
if (coldCardVersion == new Version("2.1.0")) // Should never happen though.
|
||||
{
|
||||
reverseByteOrder = true;
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = ByteHelpers.FromHex(Guard.NotNullOrEmptyOrWhitespace(nameof(mfpString), mfpString, trim: true));
|
||||
HDFingerprint mfp = reverseByteOrder ? new HDFingerprint(bytes.Reverse().ToArray()) : new HDFingerprint(bytes);
|
||||
|
||||
if (manager.WalletExists(mfp))
|
||||
{
|
||||
throw new InvalidOperationException(WalletExistsErrorMessage);
|
||||
}
|
||||
|
||||
ExtPubKey extPubKey = NBitcoinHelpers.BetterParseExtPubKey(xpubString);
|
||||
|
||||
return KeyManager.CreateNewHardwareWalletWatchOnly(mfp, extPubKey, walletFullPath);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -219,7 +219,7 @@ namespace WalletWasabi.Services
|
|||
}
|
||||
else
|
||||
{
|
||||
throw ex;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
|
|
|
@ -275,6 +275,8 @@ namespace WalletWasabi.Wallets
|
|||
}
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public bool WalletExists(HDFingerprint? fingerprint) => GetWallets().Any(x => fingerprint is { } && x.KeyManager.MasterFingerprint == fingerprint);
|
||||
|
||||
private void ChaumianClient_OnDequeue(object? sender, DequeueResult e)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче