add logic to LoadingViewModel
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/ClosedWalletViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/ClosedWalletViewModel.cs
@@ -11,7 +11,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets
protected ClosedWalletViewModel(WalletManagerViewModel walletManagerViewModel, Wallet wallet)
: base(wallet)
{
- Loading = new LoadingViewModel();
+ Loading = new LoadingViewModel(wallet);
OpenCommand = ReactiveCommand.Create(() => OnOpen(walletManagerViewModel));
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/LoadingViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/LoadingViewModel.cs
+using System;
+using System.Diagnostics;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Threading;
+using ReactiveUI;
+using WalletWasabi.Blockchain.Blocks;
+using WalletWasabi.Fluent.Helpers;
+using WalletWasabi.Models;
+using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets
{
public partial class LoadingViewModel : ActivatableViewModel
{
+ private readonly Wallet _wallet;
+
[AutoNotify] private double _percent;
[AutoNotify] private string? _statusText;
+ [AutoNotify] private bool _isBackendConnected;
+
+ private uint? _startingFilterIndex;
+ private Stopwatch? _stopwatch;
+ private bool _isLoading;
- public LoadingViewModel()
+ public LoadingViewModel(Wallet wallet)
{
+ _wallet = wallet;
_statusText = "";
_percent = 0;
+ _isBackendConnected = Services.Synchronizer.BackendStatus == BackendStatus.Connected;
}
protected override void OnActivated(CompositeDisposable disposables)
{
base.OnActivated(disposables);
+
+ var deactivateCancelToken = new CancellationTokenSource();
+ disposables.Add(Disposable.Create(() => deactivateCancelToken.Cancel()));
+
+ if (_isLoading)
+ {
+ // TODO: Refactor status
+ ShowFilterProcessingStatus(disposables);
+ }
+ else
+ {
+ Services.Synchronizer.WhenAnyValue(x => x.BackendStatus)
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Subscribe(status => IsBackendConnected = status == BackendStatus.Connected)
+ .DisposeWith(disposables);
+
+ this.RaisePropertyChanged(nameof(IsBackendConnected));
+
+ Observable.FromEventPattern<bool>(Services.Synchronizer, nameof(Services.Synchronizer.ResponseArrivedIsGenSocksServFail))
+ .Select(x => x.EventArgs)
+ .Where(x => x)
+ .Subscribe(_ => LoadWallet(disposables, syncFilters: false))
+ .DisposeWith(disposables);
+
+ this.WhenAnyValue(x => x.IsBackendConnected)
+ .Where(x => x)
+ .Subscribe(_ => LoadWallet(disposables, syncFilters: true))
+ .DisposeWith(disposables);
+ }
+ }
+
+ private void LoadWallet(CompositeDisposable disposables, bool syncFilters)
+ {
+ if (_isLoading)
+ {
+ return;
+ }
+
+ _isLoading = true;
+
+ if (syncFilters)
+ {
+ // TODO: filter sync here
+ }
+
+ RxApp.MainThreadScheduler.Schedule(async () => await UiServices.WalletManager.LoadWalletAsync(_wallet));
+ ShowFilterProcessingStatus(disposables);
+ }
+
+ private void ShowFilterProcessingStatus(CompositeDisposable disposables)
+ {
+ _stopwatch ??= Stopwatch.StartNew();
+
+ Observable.Interval(TimeSpan.FromSeconds(1))
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Subscribe(_ =>
+ {
+ var segwitActivationHeight = SmartHeader.GetStartingHeader(_wallet.Network).Height;
+ if (_wallet.LastProcessedFilter?.Header?.Height is { } lastProcessedFilterHeight
+ && lastProcessedFilterHeight > segwitActivationHeight
+ && Services.BitcoinStore.SmartHeaderChain.TipHeight is { } tipHeight
+ && tipHeight > segwitActivationHeight)
+ {
+ var allFilters = tipHeight - segwitActivationHeight;
+ var processedFilters = lastProcessedFilterHeight - segwitActivationHeight;
+
+ UpdateStatus(allFilters, processedFilters, _stopwatch.ElapsedMilliseconds);
+ }
+ })
+ .DisposeWith(disposables);
+ }
+
+ private void UpdateStatus(uint allFilters, uint processedFilters, double elapsedMilliseconds)
+ {
+ var percent = (decimal) processedFilters / allFilters * 100;
+ _startingFilterIndex ??= processedFilters; // Store the filter index we started on. It is needed for better remaining time calculation.
+ var realProcessedFilters = processedFilters - _startingFilterIndex.Value;
+ var remainingFilterCount = allFilters - processedFilters;
+
+ var tempPercent = (uint) Math.Round(percent);
+
+ if (tempPercent == 0 || realProcessedFilters == 0 || remainingFilterCount == 0)
+ {
+ return;
+ }
+
+ Percent = tempPercent;
+ var percentText = $"{Percent}% completed";
+
+ var remainingMilliseconds = elapsedMilliseconds / realProcessedFilters * remainingFilterCount;
+ var userFriendlyTime = TextHelpers.TimeSpanToFriendlyString(TimeSpan.FromMilliseconds(remainingMilliseconds));
+ var remainingTimeText = string.IsNullOrEmpty(userFriendlyTime) ? "" : $"- {userFriendlyTime} remaining";
+
+ StatusText = $"{percentText} {remainingTimeText}";
}
}
}

add logic to LoadingViewModel