private volatile bool _disposedValue = false; // To detect redundant calls public AddressViewModel(HdPubKey model, KeyManager km, ReceiveTabViewModel owner) { Global = Locator.Current.GetService <Global>(); KeyManager = km; Owner = owner; IsHardwareWallet = km.IsHardwareWallet; Model = model; ClipboardNotificationVisible = false; ClipboardNotificationOpacity = 0; _label = model.Label; this.WhenAnyValue(x => x.IsExpanded) .ObserveOn(RxApp.TaskpoolScheduler) .Where(x => x) .Take(1) .Select(x => { var encoder = new QrEncoder(); encoder.TryEncode(Address, out var qrCode); return(qrCode.Matrix.InternalArray); }) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(qr => QrCode = qr, onError: ex => Logger.LogError(ex)); // Catch the exceptions everywhere (e.g.: Select) except in Subscribe. Global.UiConfig .WhenAnyValue(x => x.LurkingWifeMode) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { this.RaisePropertyChanged(nameof(IsLurkingWifeMode)); this.RaisePropertyChanged(nameof(Address)); this.RaisePropertyChanged(nameof(Label)); }).DisposeWith(Disposables); this.WhenAnyValue(x => x.Label) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(newLabel => { if (InEditMode) { KeyManager keyManager = KeyManager; HdPubKey hdPubKey = keyManager.GetKeys(x => Model == x).FirstOrDefault(); if (hdPubKey != default) { hdPubKey.SetLabel(newLabel, kmToFile: keyManager); } } }); _expandMenuCaption = this .WhenAnyValue(x => x.IsExpanded) .Select(x => (x ? "Hide " : "Show ") + "QR Code") .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.ExpandMenuCaption) .DisposeWith(Disposables); ToggleQrCode = ReactiveCommand.Create(() => IsExpanded = !IsExpanded); SaveQRCode = ReactiveCommand.CreateFromTask(SaveQRCodeAsync); CopyAddress = ReactiveCommand.CreateFromTask(TryCopyToClipboardAsync); CopyLabel = ReactiveCommand.CreateFromTask(async() => await Application.Current.Clipboard.SetTextAsync(Label ?? string.Empty)); ChangeLabel = ReactiveCommand.Create(() => InEditMode = true); DisplayAddressOnHw = ReactiveCommand.CreateFromTask(async() => { var client = new HwiClient(Global.Network); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); try { await client.DisplayAddressAsync(KeyManager.MasterFingerprint.Value, Model.FullKeyPath, cts.Token); } catch (HwiException) { await PinPadViewModel.UnlockAsync(); await client.DisplayAddressAsync(KeyManager.MasterFingerprint.Value, Model.FullKeyPath, cts.Token); } }); LockAddress = ReactiveCommand.CreateFromTask(async() => { Model.SetKeyState(KeyState.Locked, km); owner.InitializeAddresses(); bool isAddressCopied = await Application.Current.Clipboard.GetTextAsync() == Address; if (isAddressCopied) { await Application.Current.Clipboard.ClearAsync(); } }); Observable .Merge(ToggleQrCode.ThrownExceptions) .Merge(SaveQRCode.ThrownExceptions) .Merge(CopyAddress.ThrownExceptions) .Merge(CopyLabel.ThrownExceptions) .Merge(ChangeLabel.ThrownExceptions) .Merge(DisplayAddressOnHw.ThrownExceptions) .Merge(LockAddress.ThrownExceptions) .Subscribe(ex => { Logger.LogError(ex); NotificationHelpers.Error(ex.ToUserFriendlyString()); }); }
public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info, TransactionBroadcaster broadcaster, BuildTransactionResult transaction) { var destinationAmount = transaction.CalculateDestinationAmount().ToDecimal(MoneyUnit.BTC); var fee = transaction.Fee; var labels = ""; if (info.Labels.Count() == 1) { labels = info.Labels.First() + " "; } else if (info.Labels.Count() > 1) { labels = string.Join(", ", info.Labels.Take(info.Labels.Count() - 1)); labels += $" and {info.Labels.Last()} "; } BtcAmountText = $"{destinationAmount} bitcoins "; FiatAmountText = $"(≈{(destinationAmount * wallet.Synchronizer.UsdExchangeRate).FormattedFiat()} USD) "; LabelsText = labels; AddressText = info.Address.ToString(); ConfirmationTimeText = "~20 minutes "; BtcFeeText = $"{fee.ToDecimal(MoneyUnit.Satoshi)} satoshis "; FiatFeeText = $"(≈{(fee.ToDecimal(MoneyUnit.BTC) * wallet.Synchronizer.UsdExchangeRate).FormattedFiat()} USD)"; PercentFeeText = $"{transaction.FeePercentOfSent:F2}%"; NextCommand = ReactiveCommand.CreateFromTask(async() => { var dialogResult = await NavigateDialog(new EnterPasswordDialogViewModel(""), NavigationTarget.DialogScreen); if (dialogResult.Kind == DialogResultKind.Normal) { IsBusy = true; var passwordValid = await Task.Run( () => PasswordHelper.TryPassword( wallet.KeyManager, dialogResult.Result, out string?compatibilityPasswordUsed)); if (passwordValid) { // Dequeue any coin-joining coins. await wallet.ChaumianClient.DequeueAllCoinsFromMixAsync(DequeueReason.TransactionBuilding); var signedTransaction = transaction.Transaction; // If it's a hardware wallet and still has a private key then it's password. if (wallet.KeyManager.IsHardwareWallet && !transaction.Signed) { try { var client = new HwiClient(wallet.Network); using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); PSBT?signedPsbt = null; try { signedPsbt = await client.SignTxAsync( wallet.KeyManager.MasterFingerprint !.Value, transaction.Psbt, cts.Token); } catch (HwiException ex) when(ex.ErrorCode is not HwiErrorCode.ActionCanceled) { await PinPadViewModel.UnlockAsync(); signedPsbt = await client.SignTxAsync( wallet.KeyManager.MasterFingerprint !.Value, transaction.Psbt, cts.Token); } signedTransaction = signedPsbt.ExtractSmartTransaction(transaction.Transaction); } catch (Exception _) { // probably throw something here? } } await broadcaster.SendTransactionAsync(signedTransaction); Navigate().Clear(); IsBusy = false; } else { IsBusy = false; await ShowErrorAsync("Password was incorrect.", "Please try again.", ""); } } }); }