Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UI] Make CJ settings more discoverable via MusicBox #12694

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion WalletWasabi.Fluent/ViewModels/Wallets/CoinJoinStateViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Threading;
using ReactiveUI;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Models.UI;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.State;
using WalletWasabi.WabiSabi.Backend.Rounds;
Expand Down Expand Up @@ -62,93 +64,97 @@
private DateTimeOffset _countDownStartTime;
private DateTimeOffset _countDownEndTime;

private CoinJoinStateViewModel(IWalletModel wallet)
public CoinJoinStateViewModel(UiContext uiContext, IWalletModel wallet, ReactiveCommand<Unit, Unit> navigateToCoinjoinSettingsCommand)
{
UiContext = uiContext;
_wallet = wallet;

wallet.Coinjoin.StatusUpdated
.Do(ProcessStatusChange)
.Subscribe();

wallet.Privacy.IsWalletPrivate
.BindTo(this, x => x.AreAllCoinsPrivate);

var initialState =
wallet.Settings.AutoCoinjoin
? State.WaitingForAutoStart
: State.StoppedOrPaused;

if (wallet.IsHardwareWallet || wallet.IsWatchOnlyWallet)
{
initialState = State.Disabled;
}

if (wallet.Settings.IsCoinJoinPaused)
{
initialState = State.StoppedOrPaused;
}

_stateMachine = new StateMachine<State, Trigger>(initialState);

ConfigureStateMachine();

wallet.Balances
.Do(_ => _stateMachine.Fire(Trigger.BalanceChanged))
.Subscribe();

this.WhenAnyValue(x => x.AreAllCoinsPrivate)
.Do(_ => _stateMachine.Fire(Trigger.AreAllCoinsPrivateChanged))
.Subscribe();

PlayCommand = ReactiveCommand.CreateFromTask(async () =>
{
if (!wallet.Settings.IsCoinjoinProfileSelected)
{
await UiContext.Navigate().To().CoinJoinProfiles(wallet.Settings).GetResultAsync();
}

if (wallet.Settings.IsCoinjoinProfileSelected)
{
var overridePlebStop = _stateMachine.IsInState(State.PlebStopActive);
await wallet.Coinjoin.StartAsync(stopWhenAllMixed: !IsAutoCoinJoinEnabled, overridePlebStop);
}
});

var stopPauseCommandCanExecute =
this.WhenAnyValue(
x => x.IsInCriticalPhase,
x => x.PauseSpreading,
(isInCriticalPhase, pauseSpreading) => !isInCriticalPhase && !pauseSpreading);

StopPauseCommand = ReactiveCommand.CreateFromTask(wallet.Coinjoin.StopAsync, stopPauseCommandCanExecute);

AutoCoinJoinObservable = wallet.Settings.WhenAnyValue(x => x.AutoCoinjoin);

AutoCoinJoinObservable
.Skip(1) // The first one is triggered at the creation.
.Where(x => !x)
.Do(_ => _stateMachine.Fire(Trigger.AutoCoinJoinOff))
.Subscribe();

wallet.Settings.WhenAnyValue(x => x.PlebStopThreshold)
.SubscribeAsync(async _ =>
{
// Hack: we take the value from KeyManager but it is saved later.
await Task.Delay(1500);
_stateMachine.Fire(Trigger.PlebStopChanged);
});

_autoCoinJoinStartTimer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(Random.Shared.Next(5, 16)) };
_autoCoinJoinStartTimer.Tick += async (_, _) =>
{
await wallet.Coinjoin.StartAsync(stopWhenAllMixed: false, false);
_autoCoinJoinStartTimer.Stop();
};

_countdownTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_countdownTimer.Tick += (_, _) => _stateMachine.Fire(Trigger.Tick);

_stateMachine.Start();

CanNavigateToCoinjoinSettings = navigateToCoinjoinSettingsCommand.CanExecute;
NavigateToCoinjoinSettingsCommand = navigateToCoinjoinSettingsCommand;

Check warning on line 157 in WalletWasabi.Fluent/ViewModels/Wallets/CoinJoinStateViewModel.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ New issue: Large Method

CoinJoinStateViewModel has 71 lines, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
}

private enum State
Expand All @@ -175,6 +181,10 @@
AreAllCoinsPrivateChanged
}

public IObservable<bool> CanNavigateToCoinjoinSettings { get; }

public ICommand NavigateToCoinjoinSettingsCommand { get; }

public bool IsAutoCoinJoinEnabled => _wallet.Settings.AutoCoinjoin;

public IObservable<bool> AutoCoinJoinObservable { get; }
Expand Down
10 changes: 7 additions & 3 deletions WalletWasabi.Fluent/ViewModels/Wallets/WalletViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
Expand Down Expand Up @@ -97,19 +98,22 @@
{
Settings.SelectedTab = 0;
Navigate(NavigationTarget.DialogScreen).To(Settings);
});
},
Observable.Return(!WalletModel.IsWatchOnlyWallet));

WalletCoinsCommand = ReactiveCommand.Create(() => Navigate(NavigationTarget.DialogScreen).To().WalletCoins(WalletModel));

CoinJoinSettingsCommand = ReactiveCommand.Create(
var coinJoinSettingsCommand = ReactiveCommand.Create(
() =>
{
Settings.SelectedTab = 1;
Navigate(NavigationTarget.DialogScreen).To(Settings);
},
Observable.Return(!WalletModel.IsWatchOnlyWallet));

CoinJoinSettingsCommand = coinJoinSettingsCommand;

CoinJoinStateViewModel = new CoinJoinStateViewModel(uiContext, WalletModel);
CoinJoinStateViewModel = new CoinJoinStateViewModel(uiContext, WalletModel, coinJoinSettingsCommand);

Check warning on line 116 in WalletWasabi.Fluent/ViewModels/Wallets/WalletViewModel.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (master)

❌ Getting worse: Complex Method

WalletViewModel already has high cyclomatic complexity, and now it increases in Lines of Code from 74 to 76. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
Copy link
Collaborator Author

@SuperJMN SuperJMN Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not particularly proud of this line, but I felt I needed to reuse the original command to avoid having a dependency on WalletSettingsViewModel just to set the correct SelectedTab number.

@ichthus1604 can you think of a better way?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SuperJMN Looks like you can remove the command completely from WalletViewModel, and keep it in CoinJoinStateViewModel.

It's not being used in XAML other than the MusicBox, and the only place where WalletViewModel references it is line 240, which could be replaced by CoinJoinStateViewModel.CoinJoinSettingsCommand.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I had to add a dependency on WalletSettingsViewModel to do that. Please check :)


Tiles = GetTiles().ToList();

Expand Down
31 changes: 12 additions & 19 deletions WalletWasabi.Fluent/Views/Wallets/MusicControlsView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,18 @@
<PathIcon Data="{StaticResource stop_regular}" />
</Button>

<Ellipse Height="12" Width="12"
Classes.disabled="{Binding !AutoCoinJoinObservable^}"
Classes.waiting="{Binding IsAutoWaiting}">
<Ellipse.Styles>
<Style Selector="Ellipse">
<Setter Property="Fill" Value="{DynamicResource SystemAccentColor}" />
<Setter Property="ToolTip.Tip" Value="Auto-start coinjoin enabled" />
</Style>
<Style Selector="Ellipse.waiting">
<Setter Property="Fill" Value="{DynamicResource CoinjoinActiveColor}" />
<Setter Property="ToolTip.Tip"
Value="Auto-start coinjoin enabled. Press Play to start immediately." />
</Style>
<Style Selector="Ellipse.disabled">
<Setter Property="Fill" Value="{DynamicResource SystemBaseMediumColor}" />
<Setter Property="ToolTip.Tip" Value="Auto-start coinjoin disabled" />
</Style>
</Ellipse.Styles>
</Ellipse>
<Button Classes="plain" IsVisible="{Binding CanNavigateToCoinjoinSettings^}">
<Button.Flyout>
<MenuFlyout Placement="Top">
<MenuItem Header="Coinjoin Settings" Command="{Binding NavigateToCoinjoinSettingsCommand}">
<MenuItem.Icon>
<PathIcon Data="{StaticResource settings_general_regular}" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Button.Flyout>
<PathIcon Data="{StaticResource more_regular}" Opacity="0.6" />
</Button>
</StackPanel>
</Panel>
</Border>
Expand Down