WasabiDocumentTab implements and manages disposables, so we dont constantly re-implement it in every viewmodel.
Some viewmodels implement this without even needing it, this way future developers wont get this wrong. tidies code too
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
@@ -133,13 +133,9 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private WalletService WalletService { get; }
- private CompositeDisposable Disposables { get; set; }
-
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
- base.OnOpen();
-
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
+ base.OnOpen(disposables);
TargetPrivacy = Global.Config.GetTargetPrivacy();
@@ -154,7 +150,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
.Merge(Observable.FromEventPattern(WalletService.ChaumianClient, nameof(WalletService.ChaumianClient.StateUpdated)))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => UpdateStates())
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
ClientRound mostAdvancedRound = WalletService.ChaumianClient?.State?.GetMostAdvancedRoundOrDefault();
@@ -179,7 +175,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
this.RaisePropertyChanged(nameof(AmountQueued));
this.RaisePropertyChanged(nameof(IsLurkingWifeMode));
- }).DisposeWith(Disposables);
+ }).DisposeWith(disposables);
Observable.Interval(TimeSpan.FromSeconds(1))
.ObserveOn(RxApp.MainThreadScheduler)
@@ -187,16 +183,13 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
TimeSpan left = RoundTimesout - DateTimeOffset.UtcNow;
TimeLeftTillRoundTimeout = left > TimeSpan.Zero ? left : TimeSpan.Zero; // Make sure cannot be less than zero.
- }).DisposeWith(Disposables);
+ }).DisposeWith(disposables);
}
public override bool OnClose()
{
CoinsList.OnClose();
- Disposables?.Dispose();
- Disposables = null;
-
return base.OnClose();
}

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs
@@ -20,8 +20,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
public class HistoryTabViewModel : WasabiDocumentTabViewModel
{
- private CompositeDisposable Disposables { get; set; }
-
private ObservableCollection<TransactionViewModel> _transactions;
private TransactionViewModel _selectedTransaction;
private SortOrder _dateSortDirection;
@@ -56,18 +54,16 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
_ = TryRewriteTableAsync();
}
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
- base.OnOpen();
-
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
+ base.OnOpen(disposables);
Observable.FromEventPattern(WalletService, nameof(WalletService.NewBlockProcessed))
.Merge(Observable.FromEventPattern(WalletService.TransactionProcessor, nameof(WalletService.TransactionProcessor.WalletRelevantTransactionProcessed)))
.Throttle(TimeSpan.FromSeconds(3))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await TryRewriteTableAsync())
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
Global.UiConfig.WhenAnyValue(x => x.LurkingWifeMode).ObserveOn(RxApp.MainThreadScheduler).Subscribe(_ =>
{
@@ -75,15 +71,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
transaction.Refresh();
}
- }).DisposeWith(Disposables);
- }
-
- public override bool OnClose()
- {
- Disposables.Dispose();
- Disposables = null;
-
- return base.OnClose();
+ }).DisposeWith(disposables);
}
private async Task TryRewriteTableAsync()

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/PinPadViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/PinPadViewModel.cs
@@ -23,7 +23,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
internal class PinPadViewModel : WasabiDocumentTabViewModel
{
- private CompositeDisposable Disposables { get; set; }
private string _maskedPin;
public ReactiveCommand<Unit, Unit> SendPinCommand { get; }
@@ -63,21 +62,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
});
}
- public override void OnOpen()
- {
- base.OnOpen();
-
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
- }
-
- public override bool OnClose()
- {
- Disposables.Dispose();
- Disposables = null;
-
- return base.OnClose();
- }
-
public static async Task UnlockAsync()
{
var global = Locator.Current.GetService<Global>();

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
@@ -25,8 +25,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
public class ReceiveTabViewModel : WasabiDocumentTabViewModel
{
- private CompositeDisposable Disposables { get; set; }
-
private ObservableCollection<AddressViewModel> _addresses;
private AddressViewModel _selectedAddress;
@@ -98,26 +96,15 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public SuggestLabelViewModel LabelSuggestion { get; }
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
- base.OnOpen();
-
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
+ base.OnOpen(disposables);
Observable
.FromEventPattern(WalletService.TransactionProcessor, nameof(WalletService.TransactionProcessor.WalletRelevantTransactionProcessed))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => InitializeAddresses())
- .DisposeWith(Disposables);
- }
-
- public override bool OnClose()
- {
- Disposables.Dispose();
-
- Disposables = null;
-
- return base.OnClose();
+ .DisposeWith(disposables);
}
private void InitializeAddresses()

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/SendControlViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/SendControlViewModel.cs
@@ -42,8 +42,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
public abstract class SendControlViewModel : WasabiDocumentTabViewModel
{
- private CompositeDisposable Disposables { get; set; }
-
protected Global Global { get; }
private WalletService WalletService { get; }
@@ -845,10 +843,8 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public ReactiveCommand<KeyEventArgs, Unit> AmountKeyUpCommand { get; }
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
-
Observable
.FromEventPattern<AllFeeEstimate>(Global.FeeProviders, nameof(Global.FeeProviders.AllFeeEstimateChanged))
.ObserveOn(RxApp.MainThreadScheduler)
@@ -867,12 +863,12 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
SetFeesAndTexts();
})
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
_usdExchangeRate = Global.Synchronizer
.WhenAnyValue(x => x.UsdExchangeRate)
.ToProperty(this, x => x.UsdExchangeRate, scheduler: RxApp.MainThreadScheduler)
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
this.WhenAnyValue(x => x.UsdExchangeRate)
.ObserveOn(RxApp.MainThreadScheduler)
@@ -881,24 +877,20 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Global.UiConfig.WhenAnyValue(x => x.IsCustomFee)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => IsCustomFee = x)
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
this.WhenAnyValue(x => x.IsCustomFee)
.Where(x => !x)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => IsSliderFeeUsed = true);
- base.OnOpen();
+ base.OnOpen(disposables);
}
protected abstract Task DoAfterBuildTransaction(BuildTransactionResult result);
public override bool OnClose()
{
- Disposables.Dispose();
-
- Disposables = null;
-
CoinList.OnClose();
return base.OnClose();

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/TransactionBroadcasterViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/TransactionBroadcasterViewModel.cs
@@ -31,8 +31,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private string _buttonText;
private int _caretIndex;
- private CompositeDisposable Disposables { get; set; }
-
private Global Global { get; }
public ReactiveCommand<Unit, Unit> PasteCommand { get; set; }
@@ -174,18 +172,8 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
});
}
- public override void OnOpen()
- {
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
-
- base.OnOpen();
- }
-
public override bool OnClose()
{
- Disposables?.Dispose();
- Disposables = null;
-
TransactionString = "";
return base.OnClose();

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewerViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/TransactionViewerViewModel.cs
@@ -84,7 +84,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
});
}
- private CompositeDisposable Disposables { get; set; }
private Global Global { get; }
public ReactiveCommand<Unit, Unit> ExportBinaryPsbt { get; set; }
public ReactiveCommand<Unit, Unit> OpenTransactionBroadcaster { get; set; }
@@ -121,11 +120,9 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
set => this.RaiseAndSetIfChanged(ref _psbtBytes, value);
}
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
-
- base.OnOpen();
+ base.OnOpen(disposables);
Global.UiConfig.WhenAnyValue(x => x.LurkingWifeMode)
.ObserveOn(RxApp.MainThreadScheduler)
@@ -136,15 +133,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
this.RaisePropertyChanged(nameof(PsbtJsonText));
this.RaisePropertyChanged(nameof(TransactionHexText));
this.RaisePropertyChanged(nameof(PsbtBase64Text));
- }).DisposeWith(Disposables);
- }
-
- public override bool OnClose()
- {
- Disposables?.Dispose();
- Disposables = null;
-
- return base.OnClose();
+ }).DisposeWith(disposables);
}
public void Update(BuildTransactionResult result)

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletInfoViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/WalletInfoViewModel.cs
@@ -22,8 +22,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
{
public class WalletInfoViewModel : WasabiDocumentTabViewModel
{
- private CompositeDisposable Disposables { get; set; }
-
private bool _showSensitiveKeys;
private string _password;
private string _extendedMasterPrivateKey;
@@ -161,21 +159,19 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
});
}
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
-
Closing = new CancellationTokenSource();
Global.UiConfig.WhenAnyValue(x => x.LurkingWifeMode).Subscribe(_ =>
{
this.RaisePropertyChanged(nameof(ExtendedAccountPublicKey));
this.RaisePropertyChanged(nameof(ExtendedAccountZpub));
- }).DisposeWith(Disposables);
+ }).DisposeWith(disposables);
- Closing.DisposeWith(Disposables);
+ Closing.DisposeWith(disposables);
- base.OnOpen();
+ base.OnOpen(disposables);
}
public override void OnDeselected()
@@ -193,8 +189,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public override bool OnClose()
{
Closing.Cancel();
- Disposables?.Dispose();
- Disposables = null;
ClearSensitiveData(true);
return base.OnClose();

MODIFY
WalletWasabi.Gui/Tabs/SettingsViewModel.cs
WalletWasabi.Gui/Tabs/SettingsViewModel.cs
@@ -26,8 +26,6 @@ namespace WalletWasabi.Gui.Tabs
{
internal class SettingsViewModel : WasabiDocumentTabViewModel
{
- private CompositeDisposable Disposables { get; set; }
-
private Network _network;
private string _torSocks5EndPoint;
private string _bitcoinP2pEndPoint;
@@ -161,12 +159,10 @@ namespace WalletWasabi.Gui.Tabs
.Subscribe(ex => Logger.LogError(ex));
}
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
try
{
- Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
-
Config.LoadOrCreateDefaultFileAsync(Global.Config.FilePath)
.ToObservable(RxApp.TaskpoolScheduler)
.Take(1)
@@ -190,26 +186,26 @@ namespace WalletWasabi.Gui.Tabs
IsModified = !Global.Config.AreDeepEqual(x);
})
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
Global.UiConfig
.WhenAnyValue(x => x.LurkingWifeMode)
.Subscribe(_ => this.RaisePropertyChanged(nameof(LurkingWifeMode)))
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
_isPinSet = Global.UiConfig
.WhenAnyValue(x => x.LockScreenPinHash, x => !string.IsNullOrWhiteSpace(x))
.ToProperty(this, x => x.IsPinSet, scheduler: RxApp.MainThreadScheduler)
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
this.RaisePropertyChanged(nameof(IsPinSet)); // Fire now otherwise the button won't update for restart.
Global.UiConfig.WhenAnyValue(x => x.LockScreenPinHash, x => x.Autocopy, x => x.IsCustomFee)
.Throttle(TimeSpan.FromSeconds(1))
.ObserveOn(RxApp.TaskpoolScheduler)
.Subscribe(async _ => await Global.UiConfig.ToFileAsync())
- .DisposeWith(Disposables);
+ .DisposeWith(disposables);
- base.OnOpen();
+ base.OnOpen(disposables);
}
finally
{
@@ -220,8 +216,6 @@ namespace WalletWasabi.Gui.Tabs
public override bool OnClose()
{
TabOpened = false;
- Disposables?.Dispose();
- Disposables = null;
return base.OnClose();
}

MODIFY
WalletWasabi.Gui/ViewModels/TextResourceViewModelBase.cs
WalletWasabi.Gui/ViewModels/TextResourceViewModelBase.cs
@@ -14,8 +14,6 @@ namespace WalletWasabi.Gui.ViewModels
{
public abstract class TextResourceViewModelBase : WasabiDocumentTabViewModel
{
- protected CompositeDisposable Disposables { get; private set; }
-
private string _text;
protected TextResourceViewModelBase(string title, Uri target) : base(title)
@@ -41,25 +39,16 @@ namespace WalletWasabi.Gui.ViewModels
return await reader.ReadToEndAsync();
}
- public override void OnOpen()
+ public override void OnOpen(CompositeDisposable disposables)
{
- base.OnOpen();
+ base.OnOpen(disposables);
- Disposables = new CompositeDisposable();
LoadDocumentAsync(Target)
.ToObservable(RxApp.TaskpoolScheduler)
.Take(1)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => Text = x)
- .DisposeWith(Disposables);
- }
-
- public override bool OnClose()
- {
- Disposables?.Dispose();
- Disposables = null;
-
- return base.OnClose();
+ .DisposeWith(disposables);
}
}
}

MODIFY
WalletWasabi.Gui/ViewModels/WasabiDocumentTabViewModel.cs
WalletWasabi.Gui/ViewModels/WasabiDocumentTabViewModel.cs
using Avalonia.Threading;
using AvalonStudio.Documents;
using AvalonStudio.Extensibility;
+using AvalonStudio.MVVM;
using AvalonStudio.Shell;
using Dock.Model;
using ReactiveUI;
using System;
using System.Reactive;
+using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using WalletWasabi.Helpers;
@@ -30,6 +32,8 @@ namespace WalletWasabi.Gui.ViewModels
.Subscribe(ex => Logger.LogError(ex));
}
+ private CompositeDisposable Disposables { get; set; }
+
public object Context { get; set; }
public double Width { get; set; }
public double Height { get; set; }
@@ -71,13 +75,23 @@ namespace WalletWasabi.Gui.ViewModels
IsSelected = false;
}
- public virtual void OnOpen()
+ void IDockableViewModel.OnOpen()
+ {
+ Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
+
+ OnOpen(Disposables);
+ }
+
+ public virtual void OnOpen(CompositeDisposable disposables)
{
IsClosed = false;
}
public virtual bool OnClose()
{
+ Disposables.Dispose();
+ Disposables = null;
+
IsSelected = false;
IoC.Get<IShell>().RemoveDocument(this);
IsClosed = true;

WasabiDocumentTab implements and manages disposables, so we dont constantly re-implement it in every viewmodel.

Some viewmodels implement this without even needing it, this way future developers wont get this wrong. tidies code too