public HelpCommands(CommandIconService commandIconService, AvaloniaGlobalComponent global) { Global = global.Global; AboutCommand = new CommandDefinition( "About", commandIconService.GetCompletionKindImage("About"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); })); CustomerSupportCommand = new CommandDefinition( "Customer Support", commandIconService.GetCompletionKindImage("CustomerSupport"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://www.reddit.com/r/WasabiWallet/"); } catch (Exception ex) { Logging.Logger.LogWarning <HelpCommands>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); ReportBugCommand = new CommandDefinition( "Report Bug", commandIconService.GetCompletionKindImage("ReportBug"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://github.com/zkSNACKs/WalletWasabi/issues"); } catch (Exception ex) { Logging.Logger.LogWarning <HelpCommands>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); DocsCommand = new CommandDefinition( "Documentation", commandIconService.GetCompletionKindImage("Documentation"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://docs.wasabiwallet.io/"); } catch (Exception ex) { Logging.Logger.LogWarning <HelpCommands>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); PrivacyPolicyCommand = new CommandDefinition( "Privacy Policy", commandIconService.GetCompletionKindImage("PrivacyPolicy"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new PrivacyPolicyViewModel(Global)); })); TermsAndConditionsCommand = new CommandDefinition( "Terms and Conditions", commandIconService.GetCompletionKindImage("TermsAndConditions"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new TermsAndConditionsViewModel(Global)); })); LegalIssuesCommand = new CommandDefinition( "Legal Issues", commandIconService.GetCompletionKindImage("LegalIssues"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new LegalIssuesViewModel(Global)); })); #if DEBUG DevToolsCommand = new CommandDefinition( "Dev Tools", commandIconService.GetCompletionKindImage("DevTools"), ReactiveCommand.Create(() => { var devTools = new DevTools(Application.Current.Windows.FirstOrDefault()); var devToolsWindow = new Window { Width = 1024, Height = 512, Content = devTools, DataTemplates = { new ViewLocator <Avalonia.Diagnostics.ViewModels.ViewModelBase>() } }; devToolsWindow.Show(); })); #endif }
public void Initialize(NodesCollection nodes, WasabiSynchronizer synchronizer, UpdateChecker updateChecker) { Nodes = nodes; Synchronizer = synchronizer; HashChain = synchronizer.BitcoinStore.HashChain; UseTor = Global.Config.UseTor; // Do not make it dynamic, because if you change this config settings only next time will it activate. _status = ActiveStatuses.WhenAnyValue(x => x.CurrentStatus) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.Status) .DisposeWith(Disposables); Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Added)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { SetPeers(Nodes.Count); }).DisposeWith(Disposables); Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Removed)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { SetPeers(Nodes.Count); }).DisposeWith(Disposables); SetPeers(Nodes.Count); Observable.FromEventPattern <bool>(typeof(WalletService), nameof(WalletService.DownloadingBlockChanged)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => DownloadingBlock = x.EventArgs) .DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.TorStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(status => { SetTor(status); SetPeers(Nodes.Count); }).DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.BackendStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { Backend = Synchronizer.BackendStatus; }).DisposeWith(Disposables); _filtersLeft = HashChain.WhenAnyValue(x => x.HashesLeft) .Throttle(TimeSpan.FromMilliseconds(100)) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.FiltersLeft) .DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.UsdExchangeRate) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(usd => { BtcPrice = $"${(long)usd}"; }).DisposeWith(Disposables); Observable.FromEventPattern <bool>(Synchronizer, nameof(Synchronizer.ResponseArrivedIsGenSocksServFail)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(e => { OnResponseArrivedIsGenSocksServFail(e.EventArgs); }).DisposeWith(Disposables); this.WhenAnyValue(x => x.FiltersLeft, x => x.DownloadingBlock) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(tup => { (int filtersLeft, bool downloadingBlock) = tup.ToValueTuple(); if (filtersLeft == 0 && !downloadingBlock) { TryRemoveStatus(StatusBarStatus.Synchronizing); } else { TryAddStatus(StatusBarStatus.Synchronizing); } }); this.WhenAnyValue(x => x.Tor, x => x.Backend, x => x.Peers) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(tup => { (TorStatus tor, BackendStatus backend, int peers) = tup.ToValueTuple(); if (tor == TorStatus.NotRunning || backend != BackendStatus.Connected || peers < 1) { TryAddStatus(StatusBarStatus.Connecting); } else { TryRemoveStatus(StatusBarStatus.Connecting); } }); this.WhenAnyValue(x => x.UpdateStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => { if (x == UpdateStatus.Critical) { TryAddStatus(StatusBarStatus.CriticalUpdate); } else { TryRemoveStatus(StatusBarStatus.CriticalUpdate); } if (x == UpdateStatus.Optional) { TryAddStatus(StatusBarStatus.OptionalUpdate); } else { TryRemoveStatus(StatusBarStatus.OptionalUpdate); } }); UpdateCommand = ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://wasabiwallet.io/#download"); } catch (Exception ex) { Logging.Logger.LogWarning <StatusBarViewModel>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } }, this.WhenAnyValue(x => x.UpdateStatus) .ObserveOn(RxApp.MainThreadScheduler) .Select(x => x != UpdateStatus.Latest)); this.RaisePropertyChanged(nameof(UpdateCommand)); // The binding happens after the constructor. So, if the command is not in constructor, then we need this line. updateChecker.Start(TimeSpan.FromMinutes(7), () => { UpdateStatus = UpdateStatus.Critical; return(Task.CompletedTask); }, () => { if (UpdateStatus != UpdateStatus.Critical) { UpdateStatus = UpdateStatus.Optional; } return(Task.CompletedTask); }); }
public HelpCommands(CommandIconService commandIconService, AvaloniaGlobalComponent global) { Global = global.Global; AboutCommand = new CommandDefinition( "About", commandIconService.GetCompletionKindImage("About"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); })); CustomerSupportCommand = new CommandDefinition( "Customer Support", commandIconService.GetCompletionKindImage("CustomerSupport"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://www.reddit.com/r/WasabiWallet/"); } catch (Exception ex) { Logging.Logger.LogWarning <HelpCommands>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); ReportBugCommand = new CommandDefinition( "Report Bug", commandIconService.GetCompletionKindImage("ReportBug"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://github.com/zkSNACKs/WalletWasabi/issues"); } catch (Exception ex) { Logging.Logger.LogWarning <HelpCommands>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); DocsCommand = new CommandDefinition( "Documentation", commandIconService.GetCompletionKindImage("Documentation"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://docs.wasabiwallet.io/"); } catch (Exception ex) { Logging.Logger.LogWarning <HelpCommands>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); PrivacyPolicyCommand = new CommandDefinition( "Privacy Policy", commandIconService.GetCompletionKindImage("PrivacyPolicy"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new PrivacyPolicyViewModel(Global)); })); TermsAndConditionsCommand = new CommandDefinition( "Terms and Conditions", commandIconService.GetCompletionKindImage("TermsAndConditions"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new TermsAndConditionsViewModel(Global)); })); LegalIssuesCommand = new CommandDefinition( "Legal Issues", commandIconService.GetCompletionKindImage("LegalIssues"), ReactiveCommand.Create(() => { IoC.Get <IShell>().AddOrSelectDocument(() => new LegalIssuesViewModel(Global)); })); }
public LoadWalletViewModel(WalletManagerViewModel owner, LoadWalletType loadWalletType) : base(loadWalletType == LoadWalletType.Password ? "Test Password" : (loadWalletType == LoadWalletType.Desktop ? "Load Wallet" : "Hardware Wallet")) { Owner = owner; Password = ""; LoadWalletType = loadWalletType; Wallets = new ObservableCollection <LoadWalletEntry>(); IsHwWalletSearchTextVisible = false; this.WhenAnyValue(x => x.SelectedWallet) .Subscribe(_ => TrySetWalletStates()); this.WhenAnyValue(x => x.IsWalletOpened) .Subscribe(_ => TrySetWalletStates()); this.WhenAnyValue(x => x.IsBusy) .Subscribe(_ => TrySetWalletStates()); LoadCommand = ReactiveCommand.CreateFromTask(async() => await LoadWalletAsync(), this.WhenAnyValue(x => x.CanLoadWallet)); TestPasswordCommand = ReactiveCommand.CreateFromTask(async() => await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false), this.WhenAnyValue(x => x.CanTestPassword)); OpenFolderCommand = ReactiveCommand.Create(OpenWalletsFolder); ImportColdcardCommand = ReactiveCommand.CreateFromTask(async() => { var ofd = new OpenFileDialog { AllowMultiple = false, Title = "Import Coldcard" }; if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal); } var selected = await ofd.ShowAsync(Application.Current.MainWindow, fallBack: true); if (selected != null && selected.Any()) { var path = selected.First(); var jsonString = await File.ReadAllTextAsync(path); var json = JObject.Parse(jsonString); var xpubString = json["ExtPubKey"].ToString(); var mfpString = json["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 = json["ColdCardFirmwareVersion"]?.ToString(); var reverseByteOrder = false; if (coldCardVersionString is null) { reverseByteOrder = true; } else { Version coldCardVersion = new Version(coldCardVersionString); if (coldCardVersion == new Version("2.1.0")) // Should never happen though. { reverseByteOrder = true; } } HDFingerprint mfp = NBitcoinHelpers.BetterParseHDFingerprint(mfpString, reverseByteOrder: reverseByteOrder); ExtPubKey extPubKey = NBitcoinHelpers.BetterParseExtPubKey(xpubString); Logger.LogInfo("Creating a new wallet file."); var walletName = Global.GetNextHardwareWalletName(customPrefix: "Coldcard"); var walletFullPath = Global.GetWalletFullPath(walletName); KeyManager.CreateNewHardwareWalletWatchOnly(mfp, extPubKey, walletFullPath); owner.SelectLoadWallet(); } }); EnumerateHardwareWalletsCommand = ReactiveCommand.CreateFromTask(async() => await EnumerateHardwareWalletsAsync()); OpenBrowserCommand = ReactiveCommand.Create <string>(x => IoHelpers.OpenBrowser(x)); Observable.Merge(OpenBrowserCommand.ThrownExceptions) .Merge(LoadCommand.ThrownExceptions) .Merge(TestPasswordCommand.ThrownExceptions) .Merge(OpenFolderCommand.ThrownExceptions) .Merge(ImportColdcardCommand.ThrownExceptions) .Merge(EnumerateHardwareWalletsCommand.ThrownExceptions) .Subscribe(ex => { SetWarningMessage(ex.ToTypeMessageString()); Logger.LogError(ex); }); SetLoadButtonText(); }
public StatusBarViewModel(NodesCollection nodes, WasabiSynchronizer synchronizer, UpdateChecker updateChecker) { UpdateStatus = UpdateStatus.Latest; Nodes = nodes; Synchronizer = synchronizer; BlocksLeft = 0; FiltersLeft = synchronizer.GetFiltersLeft(); UseTor = Global.Config.UseTor.Value; // Don't make it dynamic, because if you change this config settings only next time will it activate. Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Added)) .Subscribe(x => { SetPeers(Nodes.Count); }).DisposeWith(Disposables); Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Removed)) .Subscribe(x => { SetPeers(Nodes.Count); }).DisposeWith(Disposables); SetPeers(Nodes.Count); Observable.FromEventPattern <int>(typeof(WalletService), nameof(WalletService.ConcurrentBlockDownloadNumberChanged)) .Subscribe(x => { BlocksLeft = x.EventArgs; }).DisposeWith(Disposables); Observable.FromEventPattern(synchronizer, nameof(synchronizer.NewFilter)).Subscribe(x => { FiltersLeft = Synchronizer.GetFiltersLeft(); }).DisposeWith(Disposables); synchronizer.WhenAnyValue(x => x.TorStatus).Subscribe(status => { SetTor(status); SetPeers(Nodes.Count); }).DisposeWith(Disposables); synchronizer.WhenAnyValue(x => x.BackendStatus).Subscribe(_ => { Backend = Synchronizer.BackendStatus; }).DisposeWith(Disposables); synchronizer.WhenAnyValue(x => x.BestBlockchainHeight).Subscribe(_ => { FiltersLeft = Synchronizer.GetFiltersLeft(); }).DisposeWith(Disposables); synchronizer.WhenAnyValue(x => x.UsdExchangeRate).Subscribe(usd => { BtcPrice = $"${(long)usd}"; }).DisposeWith(Disposables); Observable.FromEventPattern <bool>(synchronizer, nameof(synchronizer.ResponseArrivedIsGenSocksServFail)) .Subscribe(e => { OnResponseArrivedIsGenSocksServFail(e.EventArgs); }).DisposeWith(Disposables); this.WhenAnyValue(x => x.BlocksLeft).Subscribe(blocks => { RefreshStatus(); }); this.WhenAnyValue(x => x.FiltersLeft).Subscribe(filters => { RefreshStatus(); }); this.WhenAnyValue(x => x.Tor).Subscribe(tor => { RefreshStatus(); }); this.WhenAnyValue(x => x.Backend).Subscribe(backend => { RefreshStatus(); }); this.WhenAnyValue(x => x.Peers).Subscribe(peers => { RefreshStatus(); }); this.WhenAnyValue(x => x.UpdateStatus).Subscribe(_ => { RefreshStatus(); }); this.WhenAnyValue(x => x.Status).Subscribe(async status => { if (status.EndsWith(".")) // Then do animation. { string nextAnimation = null; if (status.EndsWith("...")) { nextAnimation = status.TrimEnd("..", StringComparison.Ordinal); } else if (status.EndsWith("..") || status.EndsWith(".")) { nextAnimation = $"{status}."; } if (nextAnimation != null) { await Task.Delay(1000); if (Status == status) // If still the same. { Status = nextAnimation; } } } }); UpdateCommand = ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://wasabiwallet.io/#download"); } catch (Exception ex) { Logging.Logger.LogWarning <StatusBarViewModel>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel()); } }, this.WhenAnyValue(x => x.UpdateStatus).Select(x => x != UpdateStatus.Latest)); updateChecker.Start(TimeSpan.FromMinutes(7), () => { UpdateStatus = UpdateStatus.Critical; return(Task.CompletedTask); }, () => { if (UpdateStatus != UpdateStatus.Critical) { UpdateStatus = UpdateStatus.Optional; } return(Task.CompletedTask); }); }
public void Initialize(NodesCollection nodes, WasabiSynchronizer synchronizer, UpdateChecker updateChecker) { Nodes = nodes; Synchronizer = synchronizer; HashChain = synchronizer.BitcoinStore.HashChain; UseTor = Global.Config.UseTor.Value; // Don't make it dynamic, because if you change this config settings only next time will it activate. Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Added)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => { SetPeers(Nodes.Count); }).DisposeWith(Disposables); Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Removed)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => { SetPeers(Nodes.Count); }).DisposeWith(Disposables); SetPeers(Nodes.Count); Observable.FromEventPattern <int>(typeof(WalletService), nameof(WalletService.ConcurrentBlockDownloadNumberChanged)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => { BlocksLeft = x.EventArgs; }).DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.TorStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(status => { SetTor(status); SetPeers(Nodes.Count); }).DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.BackendStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { Backend = Synchronizer.BackendStatus; }).DisposeWith(Disposables); HashChain.WhenAnyValue(x => x.HashesLeft) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => { FiltersLeft = x; }).DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.UsdExchangeRate) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(usd => { BtcPrice = $"${(long)usd}"; }).DisposeWith(Disposables); Observable.FromEventPattern <bool>(Synchronizer, nameof(Synchronizer.ResponseArrivedIsGenSocksServFail)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(e => { OnResponseArrivedIsGenSocksServFail(e.EventArgs); }).DisposeWith(Disposables); this.WhenAnyValue(x => x.BlocksLeft) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(blocks => { RefreshStatus(); }); this.WhenAnyValue(x => x.FiltersLeft) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(filters => { RefreshStatus(); }); this.WhenAnyValue(x => x.Tor) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(tor => { RefreshStatus(); }); this.WhenAnyValue(x => x.Backend) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(backend => { RefreshStatus(); }); this.WhenAnyValue(x => x.Peers) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(peers => { RefreshStatus(); }); this.WhenAnyValue(x => x.UpdateStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { RefreshStatus(); }); UpdateCommand = ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://wasabiwallet.io/#download"); } catch (Exception ex) { Logging.Logger.LogWarning <StatusBarViewModel>(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel()); } }, this.WhenAnyValue(x => x.UpdateStatus) .ObserveOn(RxApp.MainThreadScheduler) .Select(x => x != UpdateStatus.Latest)); updateChecker.Start(TimeSpan.FromMinutes(7), () => { UpdateStatus = UpdateStatus.Critical; return(Task.CompletedTask); }, () => { if (UpdateStatus != UpdateStatus.Critical) { UpdateStatus = UpdateStatus.Optional; } return(Task.CompletedTask); }); }
public HelpCommands(CommandIconService commandIconService, AvaloniaGlobalComponent global) { Global = global.Global; AboutCommand = new CommandDefinition( "About", commandIconService.GetCompletionKindImage("About"), ReactiveCommand.Create(() => IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)))); CustomerSupportCommand = new CommandDefinition( "Customer Support", commandIconService.GetCompletionKindImage("CustomerSupport"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://www.reddit.com/r/WasabiWallet/"); } catch (Exception ex) { Logger.LogWarning(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); ReportBugCommand = new CommandDefinition( "Report Bug", commandIconService.GetCompletionKindImage("ReportBug"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://github.com/zkSNACKs/WalletWasabi/issues"); } catch (Exception ex) { Logger.LogWarning(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); DocsCommand = new CommandDefinition( "Documentation", commandIconService.GetCompletionKindImage("Documentation"), ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://docs.wasabiwallet.io/"); } catch (Exception ex) { Logger.LogWarning(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } })); LegalDocumentsCommand = new CommandDefinition( "Legal Documents", commandIconService.GetCompletionKindImage("LegalDocuments"), ReactiveCommand.Create(() => IoC.Get <IShell>().AddOrSelectDocument(() => new LegalDocumentsViewModel(Global)))); Observable .Merge(AboutCommand.GetReactiveCommand().ThrownExceptions) .Merge(CustomerSupportCommand.GetReactiveCommand().ThrownExceptions) .Merge(ReportBugCommand.GetReactiveCommand().ThrownExceptions) .Merge(DocsCommand.GetReactiveCommand().ThrownExceptions) .Merge(LegalDocumentsCommand.GetReactiveCommand().ThrownExceptions) .ObserveOn(RxApp.TaskpoolScheduler) .Subscribe(ex => Logger.LogError(ex)); }
public void Initialize(NodesCollection nodes, WasabiSynchronizer synchronizer, UpdateChecker updateChecker) { Nodes = nodes; Synchronizer = synchronizer; HashChain = synchronizer.BitcoinStore.HashChain; UseTor = Global.Config.UseTor; // Do not make it dynamic, because if you change this config settings only next time will it activate. UseBitcoinCore = Global.Config.StartLocalBitcoinCoreOnStartup; _status = ActiveStatuses.WhenAnyValue(x => x.CurrentStatus) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.Status) .DisposeWith(Disposables); Observable .Merge(Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Added)).Select(x => true) .Merge(Observable.FromEventPattern <NodeEventArgs>(nodes, nameof(nodes.Removed)).Select(x => true) .Merge(Synchronizer.WhenAnyValue(x => x.TorStatus).Select(x => true)))) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => Peers = Synchronizer.TorStatus == TorStatus.NotRunning ? 0 : Nodes.Count) // Set peers to 0 if Tor is not running, because we get Tor status from backend answer so it seems to the user that peers are connected over clearnet, while they are not. .DisposeWith(Disposables); Peers = Tor == TorStatus.NotRunning ? 0 : Nodes.Count; Observable.FromEventPattern <bool>(typeof(WalletService), nameof(WalletService.DownloadingBlockChanged)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => DownloadingBlock = x.EventArgs) .DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.TorStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(status => Tor = UseTor ? status : TorStatus.TurnedOff) .DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.BackendStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => Backend = Synchronizer.BackendStatus) .DisposeWith(Disposables); _filtersLeft = HashChain.WhenAnyValue(x => x.HashesLeft) .Throttle(TimeSpan.FromMilliseconds(100)) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.FiltersLeft) .DisposeWith(Disposables); Synchronizer.WhenAnyValue(x => x.UsdExchangeRate) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(usd => BtcPrice = $"${(long)usd}") .DisposeWith(Disposables); _bitcoinCoreStatus = Global.RpcMonitor .WhenAnyValue(x => x.Status) .Throttle(TimeSpan.FromMilliseconds(100)) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.BitcoinCoreStatus) .DisposeWith(Disposables); _updateStatus = Global.UpdateChecker .WhenAnyValue(x => x.Status) .Throttle(TimeSpan.FromMilliseconds(100)) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, x => x.UpdateStatus) .DisposeWith(Disposables); Observable.FromEventPattern <bool>(Synchronizer, nameof(Synchronizer.ResponseArrivedIsGenSocksServFail)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(e => OnResponseArrivedIsGenSocksServFail(e.EventArgs)) .DisposeWith(Disposables); this.WhenAnyValue(x => x.FiltersLeft, x => x.DownloadingBlock) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(tup => { (int filtersLeft, bool downloadingBlock) = tup; if (filtersLeft == 0 && !downloadingBlock) { TryRemoveStatus(StatusBarStatus.Synchronizing); } else { TryAddStatus(StatusBarStatus.Synchronizing); } }); this.WhenAnyValue(x => x.Tor, x => x.Backend, x => x.Peers) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(tup => { (TorStatus tor, BackendStatus backend, int peers) = tup; if (tor == TorStatus.NotRunning || backend != BackendStatus.Connected || peers < 1) { TryAddStatus(StatusBarStatus.Connecting); } else { TryRemoveStatus(StatusBarStatus.Connecting); } }); this.WhenAnyValue(x => x.UpdateStatus) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => { if (x.BackendCompatible) { TryRemoveStatus(StatusBarStatus.CriticalUpdate); } else { TryAddStatus(StatusBarStatus.CriticalUpdate); } if (x.ClientUpToDate) { TryRemoveStatus(StatusBarStatus.OptionalUpdate); } else { TryAddStatus(StatusBarStatus.OptionalUpdate); } UpdateAvailable = !x.ClientUpToDate; CriticalUpdateAvailable = !x.BackendCompatible; }); UpdateCommand = ReactiveCommand.Create(() => { try { IoHelpers.OpenBrowser("https://wasabiwallet.io/#download"); } catch (Exception ex) { Logger.LogWarning(ex); IoC.Get <IShell>().AddOrSelectDocument(() => new AboutViewModel(Global)); } }, this.WhenAnyValue(x => x.UpdateAvailable, x => x.CriticalUpdateAvailable, (x, y) => x || y)); this.RaisePropertyChanged(nameof(UpdateCommand)); // The binding happens after the constructor. So, if the command is not in constructor, then we need this line. updateChecker.Start(); }