Merge branch 'master' into ca1835

This commit is contained in:
Lucas Ontivero 2020-12-14 11:58:14 -03:00 коммит произвёл GitHub
Родитель b939c8d282 579ce1fadc
Коммит af48d9c0e7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
69 изменённых файлов: 797 добавлений и 479 удалений

Просмотреть файл

@ -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,13 +81,13 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
{
IsBusy = true;
try
{
var result = await NavigateDialog(
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(
() =>
@ -105,15 +106,23 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet
walletManager.AddWallet(keyManager);
});
}
Navigate().To(new AddedWalletPageViewModel(walletName!, WalletType.Normal));
return;
}
catch (Exception ex)
{
// TODO navigate to error dialog.
Logger.LogError(ex);
}
finally
}
IsBusy = false;
if (dialogResult.Kind == DialogResultKind.Cancel)
{
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,7 +9,7 @@
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">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Bottom" Margin="0 40 0 0">

Просмотреть файл

@ -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,7 +57,8 @@
<TextBlock Text="{Binding OutputCountString}" />
</StackPanel>
<Grid ColumnDefinitions="*,*,*" DockPanel.Dock="Bottom" HorizontalAlignment="Stretch" Margin="20">
<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">
@ -81,14 +83,12 @@
</StackPanel>
</StackPanel>
</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)

Просмотреть файл

@ -276,6 +276,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)
{
OnDequeue?.Invoke(sender, e);