private async void OnInit(object sender, EventArgs args) { Initializing -= OnInit; while (Global.Wallet.State < WalletState.Initialized) { await Task.Delay(200); } Device.BeginInvokeOnMainThread(() => { CoinList = new CoinListViewModel(); Observable.FromEventPattern(Global.Wallet.TransactionProcessor, nameof(Global.Wallet.TransactionProcessor.WalletRelevantTransactionProcessed)) .Throttle(TimeSpan.FromSeconds(0.1)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { Balance = Global.Wallet.Coins.TotalAmount().ToString(); HasPrivateCoins = Enumerable.Where( Global.Wallet.Coins, c => c.Unspent && !c.SpentAccordingToBackend && c.AnonymitySet > 1 ).Sum(c => (long?)c.Amount) > 0; }); Observable.FromEventPattern(Global.Wallet, nameof(Global.Wallet.NewBlockProcessed)) .Merge(Observable.FromEventPattern(Global.Wallet.TransactionProcessor, nameof(Global.Wallet.TransactionProcessor.WalletRelevantTransactionProcessed))) .Throttle(TimeSpan.FromSeconds(3)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(async _ => await TryRewriteTableAsync()); CoinJoinViewModel = new CoinJoinViewModel(CoinList); SendAmountViewModel = new SendAmountViewModel(CoinList); }); }
public SendViewModel(IScreen hostScreen) : base(hostScreen) { CoinList = new CoinListViewModel(hostScreen); AmountText = "0.0"; this.WhenAnyValue(x => x.AmountText) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(amount => { // Correct amount Regex digitsOnly = new Regex(@"[^\d,.]"); string betterAmount = digitsOnly.Replace(amount, ""); // Make it digits , and . only. betterAmount = betterAmount.Replace(',', '.'); int countBetterAmount = betterAmount.Count(x => x == '.'); if (countBetterAmount > 1) // Do not enable typing two dots. { var index = betterAmount.IndexOf('.', betterAmount.IndexOf('.') + 1); if (index > 0) { betterAmount = betterAmount.Substring(0, index); } } var dotIndex = betterAmount.IndexOf('.'); if (dotIndex != -1 && betterAmount.Length - dotIndex > 8) // Enable max 8 decimals. { betterAmount = betterAmount.Substring(0, dotIndex + 1 + 8); } if (betterAmount != amount) { AmountText = betterAmount; } }); BuildTransactionCommand = ReactiveCommand.CreateFromTask(async() => { try { IsBusy = true; Password = Guard.Correct(Password); Memo = Memo.Trim(',', ' ').Trim(); var selectedCoinViewModels = CoinList.Coins.Where(cvm => cvm.IsSelected); var selectedCoinReferences = selectedCoinViewModels.Select(cvm => new TxoRef(cvm.Model.TransactionId, cvm.Model.Index)).ToList(); if (!selectedCoinReferences.Any()) { //SetWarningMessage("No coins are selected to spend."); return; } BitcoinAddress address; try { address = BitcoinAddress.Create(Address.Trim(), Global.Network); } catch (FormatException) { // SetWarningMessage("Invalid address."); return; } var script = address.ScriptPubKey; var amount = Money.Zero; if (!Money.TryParse(AmountText, out amount) || amount == Money.Zero) { // SetWarningMessage($"Invalid amount."); return; } if (amount == selectedCoinViewModels.Sum(x => x.Amount)) { // SetWarningMessage("Looks like you want to spend a whole coin. Try Max button instead."); return; } var memo = Memo; var operation = new WalletService.Operation(script, amount, memo); var feeTarget = 500; var result = await Task.Run(() => Global.WalletService.BuildTransaction(Password, new[] { operation }, feeTarget, allowUnconfirmed: true, allowedInputs: selectedCoinReferences)); SmartTransaction signedTransaction = result.Transaction; await Task.Run(async() => await Global.WalletService.SendTransactionAsync(signedTransaction)); } catch (InsufficientBalanceException ex) { Money needed = ex.Minimum - ex.Actual; Logger.LogDebug <SendViewModel>(ex); //SetWarningMessage($"Not enough coins selected. You need an estimated {needed.ToString(false, true)} BTC more to make this transaction."); } catch (Exception ex) { Logger.LogDebug <SendViewModel>(ex); //SetWarningMessage(ex.ToTypeMessageString()); } finally { IsBusy = false; } }, this.WhenAny(x => x.AmountText, x => x.Address, x => x.IsBusy, (amountText, address, busy) => !string.IsNullOrWhiteSpace(amountText.Value) && !string.IsNullOrWhiteSpace(address.Value) && !busy.Value) .ObserveOn(RxApp.MainThreadScheduler)); }
public CoinViewModel(CoinListViewModel owner, SmartCoin model, IScreen hostScreen) : base(hostScreen) { Model = model; _owner = owner; }
public SendAmountViewModel(CoinListViewModel coinList) : base(Locator.Current.GetService <IViewStackService>()) { Global = Locator.Current.GetService <Global>(); CoinList = coinList; AmountText = "0.0"; AllSelectedAmount = Money.Zero; EstimatedBtcFee = Money.Zero; this.WhenAnyValue(x => x.AmountText) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(amount => { // Correct amount if (IsMax) { SetAmountIfMax(); } else { Regex digitsOnly = new Regex(@"[^\d,.]"); string betterAmount = digitsOnly.Replace(amount, ""); // Make it digits , and . only. betterAmount = betterAmount.Replace(',', '.'); int countBetterAmount = betterAmount.Count(x => x == '.'); if (countBetterAmount > 1) // Do not enable typing two dots. { var index = betterAmount.IndexOf('.', betterAmount.IndexOf('.') + 1); if (index > 0) { betterAmount = betterAmount.Substring(0, index); } } var dotIndex = betterAmount.IndexOf('.'); if (dotIndex != -1 && betterAmount.Length - dotIndex > 8) // Enable max 8 decimals. { betterAmount = betterAmount.Substring(0, dotIndex + 1 + 8); } if (betterAmount != amount) { AmountText = betterAmount; } } }); _sendFromText = this .WhenAnyValue(x => x.CoinList.SelectPrivateSwitchState, x => x.CoinList.SelectedCount) .Select(tup => { var coinGrammaticalNumber = tup.Item2 == 1 ? " Coin ▾" : " Coins ▾"; return(tup.Item1 ? "Auto-Select Private ▾" : (tup.Item2.ToString() + coinGrammaticalNumber)); }) .ToProperty(this, nameof(SendFromText)); this.WhenAnyValue(x => x.IsMax) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(isMax => { if (isMax) { SetAmountIfMax(); } }); Observable.FromEventPattern(CoinList, nameof(CoinList.SelectionChanged)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => SetFees()); _minMaxFeeTargetsEqual = this.WhenAnyValue(x => x.MinimumFeeTarget, x => x.MaximumFeeTarget, (x, y) => x == y) .ToProperty(this, x => x.MinMaxFeeTargetsEqual, scheduler: RxApp.MainThreadScheduler); SetFeeTargetLimits(); FeeTarget = Global.UiConfig.FeeTarget; FeeRate = new FeeRate((decimal)50); //50 sat/vByte placeholder til loads SetFees(); Observable .FromEventPattern <AllFeeEstimate>(Global.FeeProviders, nameof(Global.FeeProviders.AllFeeEstimateChanged)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { SetFeeTargetLimits(); if (FeeTarget < MinimumFeeTarget) // Should never happen. { FeeTarget = MinimumFeeTarget; } else if (FeeTarget > MaximumFeeTarget) { FeeTarget = MaximumFeeTarget; } SetFees(); }) .DisposeWith(Disposables); GoNext = ReactiveCommand.CreateFromObservable(() => { ViewStackService.PushPage(new SendWhoViewModel(this)).Subscribe(); return(Observable.Return(Unit.Default)); }, this.WhenAnyValue( x => x.AmountText, x => x.CoinList.SelectedAmount, (amountToSpend, selectedAmount) => { return(AmountTextPositive(amountToSpend) && Money.Parse(amountToSpend.TrimStart('~', ' ')) + EstimatedBtcFee <= selectedAmount); })); SelectCoins = ReactiveCommand.CreateFromObservable(() => { ViewStackService.PushModal(CoinList).Subscribe(); return(Observable.Return(Unit.Default)); }); SelectFee = ReactiveCommand.CreateFromObservable(() => { ViewStackService.PushModal(new FeeViewModel(this)).Subscribe(); return(Observable.Return(Unit.Default)); }); this.WhenAnyValue(x => x.FeeTarget) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { SetFees(); }); }
public CoinJoinViewModel(CoinListViewModel coinList) : base(Locator.Current.GetService <IViewStackService>()) { Global = Locator.Current.GetService <Global>(); SetBalance(); notificationManager = Global.NotificationManager; if (Disposables != null) { throw new Exception("Wallet opened before it was closed."); } Disposables = new CompositeDisposable(); CoinList = coinList; Observable .FromEventPattern <SmartCoin>(CoinList, nameof(CoinList.DequeueCoinsPressed)) .Subscribe(async x => await DoDequeueAsync(x.EventArgs)); this.WhenAnyValue(x => x.RoundTimesout) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { TimeSpan left = RoundTimesout - DateTimeOffset.UtcNow; TimeLeftTillRoundTimeout = left > TimeSpan.Zero ? left : TimeSpan.Zero; // Make sure cannot be less than zero. }); AmountQueued = Money.Zero; var registrableRound = Global.Wallet.ChaumianClient.State.GetRegistrableRoundOrDefault(); CoordinatorFeePercent = registrableRound?.State?.CoordinatorFeePercent.ToString() ?? "0.003"; Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.ChaumianClient.CoinQueued)) .Merge(Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.ChaumianClient.OnDequeue))) .Merge(Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.ChaumianClient.StateUpdated))) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => UpdateStates()) .DisposeWith(Disposables); Observable.FromEventPattern(Global.Wallet.ChaumianClient, nameof(Global.Wallet.ChaumianClient.OnDequeue)) .Subscribe(pattern => { var e = (DequeueResult)pattern.EventArgs; try { foreach (var success in e.Successful.Where(x => x.Value.Any())) { DequeueReason reason = success.Key; if (reason == DequeueReason.UserRequested) { notificationManager.RemoveAllPendingNotifications(); } } } catch (Exception ex) { Logger.LogWarning(ex); } }) .DisposeWith(Disposables); ClientRound mostAdvancedRound = Global.Wallet.ChaumianClient?.State?.GetMostAdvancedRoundOrDefault(); if (mostAdvancedRound != default) { RoundPhaseState = new RoundPhaseState(mostAdvancedRound.State.Phase, Global.Wallet.ChaumianClient?.State.IsInErrorState ?? false); RoundTimesout = mostAdvancedRound.State.Phase == RoundPhase.InputRegistration ? mostAdvancedRound.State.InputRegistrationTimesout : DateTimeOffset.UtcNow; PeersRegistered = mostAdvancedRound.State.RegisteredPeerCount; PeersNeeded = mostAdvancedRound.State.RequiredPeerCount; } else { RoundPhaseState = new RoundPhaseState(RoundPhase.InputRegistration, false); RoundTimesout = DateTimeOffset.UtcNow; PeersRegistered = 0; PeersNeeded = 100; } Observable.Interval(TimeSpan.FromSeconds(1)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { TimeSpan left = RoundTimesout - DateTimeOffset.UtcNow; TimeLeftTillRoundTimeout = left > TimeSpan.Zero ? left : TimeSpan.Zero; // Make sure cannot be less than zero. }).DisposeWith(Disposables); CoinJoinCommand = ReactiveCommand.CreateFromTask <string, bool>(DoEnqueueAsync); ExitCoinJoinCommand = ReactiveCommand.CreateFromTask(ExitCoinJoinAsync); var canPromptPassword = this.WhenAnyValue( x => x.CoinList.SelectedAmount, x => x.RequiredBTC, (amnt, rBTC) => { return(!(rBTC is null) && !(amnt is null) && amnt >= rBTC); });