Refactor NavBar logic
MODIFY
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
@@ -53,7 +53,7 @@ public partial class MainViewModel : ViewModelBase
_addWalletPage = new AddWalletPageViewModel();
_settingsPage = new SettingsPageViewModel();
_privacyMode = new PrivacyModeViewModel();
- _navBar = new NavBarViewModel(MainScreen);
+ _navBar = new NavBarViewModel();
NavigationManager.RegisterType(_navBar);
RegisterViewModels();

MODIFY
WalletWasabi.Fluent/ViewModels/NavBar/NavBarViewModel.cs
WalletWasabi.Fluent/ViewModels/NavBar/NavBarViewModel.cs
-using ReactiveUI;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
+using DynamicData;
+using DynamicData.Binding;
using WalletWasabi.Fluent.ViewModels.Navigation;
using WalletWasabi.Fluent.ViewModels.Wallets;
@@ -11,55 +12,42 @@ namespace WalletWasabi.Fluent.ViewModels.NavBar;
/// <summary>
/// The ViewModel that represents the structure of the sidebar.
/// </summary>
-public partial class NavBarViewModel : ViewModelBase
+public class NavBarViewModel : ViewModelBase
{
- private NavBarItemViewModel? _selectedItem;
- private bool _isNavigating;
- [AutoNotify] private ObservableCollection<NavBarItemViewModel> _topItems;
- [AutoNotify] private ObservableCollection<NavBarItemViewModel> _bottomItems;
-
- public NavBarViewModel(TargettedNavigationStack mainScreen)
+ public NavBarViewModel()
{
- _topItems = new ObservableCollection<NavBarItemViewModel>();
- _bottomItems = new ObservableCollection<NavBarItemViewModel>();
-
- mainScreen.WhenAnyValue(x => x.CurrentPage)
- .WhereNotNull()
- .OfType<NavBarItemViewModel>()
- .DistinctUntilChanged()
- .Subscribe(CurrentPageChanged);
+ TopItems = new ObservableCollection<NavBarItemViewModel>();
+ BottomItems = new ObservableCollection<NavBarItemViewModel>();
- this.WhenAnyValue(x => x.SelectedItem)
- .WhereNotNull()
- .Subscribe(selectedItem =>
+ Observable.Amb(
+ Wallets.ToObservableChangeSet().Transform(x => x as NavBarItemViewModel),
+ TopItems.ToObservableChangeSet(),
+ BottomItems.ToObservableChangeSet())
+ .WhenPropertyChanged(x => x.IsSelected)
+ .Where(x => x.Value)
+ .Select(x => x.Sender)
+ .Buffer(2, 1)
+ .Select(buffer => (OldValue: buffer[0], NewValue: buffer[1]))
+ .Subscribe(x =>
{
- NavigateItem(selectedItem);
+ if (x.OldValue is { } old)
+ {
+ old.IsSelected = false;
+ }
- if (selectedItem is WalletViewModelBase wallet)
+ if (x.NewValue is WalletViewModelBase wallet)
{
Services.UiConfig.LastSelectedWallet = wallet.WalletName;
}
});
+ }
- this.WhenAnyValue(x => x.Wallets.Count)
- .Where(count => count > 0 && SelectedItem is null && !UiServices.WalletManager.IsLoadingWallet)
- .Select(_ => Wallets.FirstOrDefault(item => item.WalletName == Services.UiConfig.LastSelectedWallet) ?? Wallets.FirstOrDefault())
- .Subscribe(itemToSelect => SelectedItem = itemToSelect);
+ public ObservableCollection<NavBarItemViewModel> TopItems { get; }
- UiServices.WalletManager.WhenAnyValue(x => x.SelectedWallet)
- .WhereNotNull()
- .OfType<NavBarItemViewModel>()
- .Subscribe(x => SelectedItem = x);
- }
+ public ObservableCollection<NavBarItemViewModel> BottomItems { get; }
public ObservableCollection<WalletViewModelBase> Wallets => UiServices.WalletManager.Wallets;
- public NavBarItemViewModel? SelectedItem
- {
- get => _selectedItem;
- set => SetSelectedItem(value);
- }
-
public async Task InitialiseAsync()
{
var topItems = NavigationManager.MetaData.Where(x => x.NavBarPosition == NavBarPosition.Top);
@@ -72,7 +60,7 @@ public partial class NavBarViewModel : ViewModelBase
if (viewModel is NavBarItemViewModel navBarItem)
{
- _topItems.Add(navBarItem);
+ TopItems.Add(navBarItem);
}
}
@@ -82,118 +70,8 @@ public partial class NavBarViewModel : ViewModelBase
if (viewModel is NavBarItemViewModel navBarItem)
{
- _bottomItems.Add(navBarItem);
- }
- }
- }
-
- private void RaiseAndChangeSelectedItem(NavBarItemViewModel? value)
- {
- _selectedItem = value;
- this.RaisePropertyChanged(nameof(SelectedItem));
+ BottomItems.Add(navBarItem);
}
-
- private void Select(NavBarItemViewModel? value)
- {
- if (_selectedItem == value)
- {
- return;
- }
-
- if (_selectedItem is { })
- {
- _selectedItem.IsSelected = false;
- }
-
- RaiseAndChangeSelectedItem(null);
- RaiseAndChangeSelectedItem(value);
-
- if (_selectedItem is { })
- {
- _selectedItem.IsSelected = true;
- }
- }
-
- private void SetSelectedItem(NavBarItemViewModel? value)
- {
- if (value is null || value.SelectionMode == NavBarItemSelectionMode.Selected)
- {
- Select(value);
- return;
- }
-
- if (value.SelectionMode == NavBarItemSelectionMode.Button)
- {
- _isNavigating = true;
- var previous = _selectedItem;
- RaiseAndChangeSelectedItem(null);
- RaiseAndChangeSelectedItem(value);
- _isNavigating = false;
- NavigateItem(value);
- _isNavigating = true;
- RaiseAndChangeSelectedItem(null);
- RaiseAndChangeSelectedItem(previous);
- _isNavigating = false;
- return;
- }
-
- if (value.SelectionMode == NavBarItemSelectionMode.Toggle)
- {
- _isNavigating = true;
- var previous = _selectedItem;
- RaiseAndChangeSelectedItem(null);
- RaiseAndChangeSelectedItem(value);
- _isNavigating = false;
- value.Toggle();
- _isNavigating = true;
- RaiseAndChangeSelectedItem(null);
- RaiseAndChangeSelectedItem(previous);
- _isNavigating = false;
- }
- }
-
- private void CurrentPageChanged(NavBarItemViewModel x)
- {
- if (UiServices.WalletManager.Wallets.Contains(x) || _topItems.Contains(x) || _bottomItems.Contains(x))
- {
- if (!_isNavigating)
- {
- _isNavigating = true;
-
- var result = UiServices.WalletManager.SelectionChanged(x);
- if (result is not null)
- {
- SelectedItem = x;
- }
-
- if (x.SelectionMode == NavBarItemSelectionMode.Selected)
- {
- SetSelectedItem(x);
- }
-
- _isNavigating = false;
- }
- }
- }
-
- private void NavigateItem(NavBarItemViewModel x)
- {
- if (!_isNavigating)
- {
- _isNavigating = true;
-
- var result = UiServices.WalletManager.SelectionChanged(x);
- if (result is not null)
- {
- SelectedItem = result;
- }
-
- if (x.OpenCommand.CanExecute(default))
- {
- x.OpenCommand.Execute(default);
- }
-
- _isNavigating = false;
}
}
}

MODIFY
WalletWasabi.Fluent/ViewModels/WalletManagerViewModel.cs
WalletWasabi.Fluent/ViewModels/WalletManagerViewModel.cs
@@ -9,7 +9,6 @@ using ReactiveUI;
using WalletWasabi.Blockchain.TransactionProcessing;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Helpers;
-using WalletWasabi.Fluent.ViewModels.NavBar;
using WalletWasabi.Fluent.ViewModels.Wallets;
using WalletWasabi.Logging;
using WalletWasabi.Wallets;
@@ -21,8 +20,6 @@ public partial class WalletManagerViewModel : ViewModelBase
private readonly SourceList<WalletViewModelBase> _walletsSourceList = new();
private readonly ObservableCollectionExtended<WalletViewModelBase> _wallets = new();
- private NavBarItemViewModel? _currentSelection;
- [AutoNotify] private WalletViewModelBase? _selectedWallet;
[AutoNotify(SetterModifier = AccessModifier.Private)] private bool _isLoadingWallet;
public WalletManagerViewModel()
@@ -152,9 +149,9 @@ public partial class WalletManagerViewModel : ViewModelBase
var walletViewModelItem = OpenWallet(closedWalletViewModel.Wallet);
- if (_currentSelection == closedWalletViewModel)
+ if (closedWalletViewModel.IsSelected && walletViewModelItem.OpenCommand.CanExecute(default))
{
- SelectedWallet = walletViewModelItem;
+ walletViewModelItem.OpenCommand.Execute(default);
}
IsLoadingWallet = false;
@@ -190,35 +187,13 @@ public partial class WalletManagerViewModel : ViewModelBase
{
InsertWallet(ClosedWalletViewModel.Create(wallet));
}
- }
- public NavBarItemViewModel? SelectionChanged(NavBarItemViewModel item)
- {
- if (item.SelectionMode == NavBarItemSelectionMode.Selected)
- {
- _currentSelection = item;
- }
+ var walletToSelect = Wallets.FirstOrDefault(item => item.WalletName == Services.UiConfig.LastSelectedWallet) ?? Wallets.FirstOrDefault();
- if (IsLoadingWallet || SelectedWallet == item)
+ if (walletToSelect is { } && walletToSelect.OpenCommand.CanExecute(default))
{
- return default;
+ walletToSelect.OpenCommand.Execute(default);
}
-
- var result = default(NavBarItemViewModel);
-
- if (SelectedWallet is { IsLoggedIn: true } && item is WalletViewModelBase && SelectedWallet != item)
- {
- SelectedWallet = null;
- result = item;
- }
-
- if (item is WalletViewModel { IsLoggedIn: true } walletViewModelItem)
- {
- SelectedWallet = walletViewModelItem;
- result = item;
- }
-
- return result;
}
private bool TryGetWalletViewModel(Wallet wallet, [NotNullWhen(true)] out WalletViewModelBase? walletViewModel)

MODIFY
WalletWasabi.Fluent/Views/NavBar/NavBar.axaml
WalletWasabi.Fluent/Views/NavBar/NavBar.axaml
<Setter Property="Height" Value="{DynamicResource MenuFlyoutSeparatorThemeHeight}" />
</Style>
- <Style Selector="ListBox">
- <Setter Property="Margin" Value="0" />
- <Setter Property="Background" Value="Transparent" />
+ <Style Selector="c|NavBarItem" x:DataType="vm:NavBarItemViewModel">
+ <Setter Property="Width" Value="75" />
+ <Setter Property="Height" Value="55" />
+ <Setter Property="Margin" Value="4 1" />
<Setter Property="Padding" Value="0" />
+ <Setter Property="Cursor" Value="Hand" />
+ <Setter Property="IsSelectable" Value="{Binding IsSelectable}" />
+ <Setter Property="IsSelected" Value="{Binding IsSelected}" />
+ <Setter Property="Command" Value="{Binding OpenCommand}" />
</Style>
- <Style Selector=":is(Panel).NavBarItemPanel" x:DataType="vmw:WalletViewModelBase">
- <Setter Property="Width" Value="52" />
- <Setter Property="Height" Value="36" />
- <Setter Property="Background" Value="Transparent" />
+ <Style Selector="c|NavBarItem StackPanel">
+ <Setter Property="VerticalAlignment" Value="Center" />
+ <Setter Property="Spacing" Value="2" />
</Style>
- <Style Selector=":is(Panel).NavBarItemPanel DockPanel">
- <Setter Property="Background" Value="Transparent" />
+ <Style Selector="c|NavBarItem StackPanel TextBlock">
+ <Setter Property="TextAlignment" Value="Center" />
+ <Setter Property="FontSize" Value="{StaticResource FontSizeH9}" />
</Style>
- <Style Selector=":is(Panel).NavBarItemPanel DockPanel > PathIcon">
+ <Style Selector="c|NavBarItem StackPanel PathIcon" x:CompileBindings="False">
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
- <Setter Property="Background" Value="Transparent" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
+ <Setter Property="Foreground" Value="{DynamicResource AcrylicTrimForeground}" />
+ <Setter Property="Data" Value="{Binding IconName, Converter={x:Static conv:NavBarIconConverter.Instance}}" />
+ </Style>
+
+ <Style Selector="c|NavBarItem StackPanel LayoutTransformControl">
+ <Setter Property="LayoutTransform" Value="scaleY(1)" />
+ <Setter Property="Opacity" Value="1" />
+ <Setter Property="Transitions">
+ <Transitions>
+ <TransformOperationsTransition Property="LayoutTransform" Duration="0:0:0.175" />
+ <DoubleTransition Property="Opacity" Duration="0:0:0.175" Easing="1,0,1,0" />
+ </Transitions>
+ </Setter>
</Style>
- <Style Selector=":is(Panel).NavBarItemPanel.wallets Ellipse">
+ <Style Selector="c|NavBarItem:selected StackPanel LayoutTransformControl">
+ <Setter Property="LayoutTransform" Value="scaleY(0)" />
+ <Setter Property="Opacity" Value="0" />
+ </Style>
+
+ <Style Selector="c|NavBarItem:selected PathIcon" x:CompileBindings="False">
+ <Setter Property="Data" Value="{Binding IconNameFocused, Converter={x:Static conv:NavBarIconConverter.Instance}}" />
+ </Style>
+
+ <Style Selector="c|NavBarItem Ellipse">
<Setter Property="Fill" Value="{DynamicResource AcrylicTrimForeground}" />
<Setter Property="Width" Value="8" />
<Setter Property="Height" Value="8" />
- <Setter Property="Margin" Value="0,-5,-5,0" />
+ <Setter Property="Margin" Value="3" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Opacity" Value="0" />
</Setter>
</Style>
- <Style Selector=":is(Panel).NavBarItemPanel.wallets Ellipse:not(.isLoading).isLoggedIn">
+ <Style Selector="c|NavBarItem Ellipse:not(.isLoading).isLoggedIn">
<Setter Property="Opacity" Value="1" />
</Style>
- <Style Selector=":is(Panel).NavBarItemPanel.wallets Ellipse.isLoading.isLoggedIn">
+ <Style Selector="c|NavBarItem Ellipse.isLoading.isLoggedIn">
<Style.Animations>
<Animation IterationCount="Infinite" Duration="0:0:1">
<KeyFrame Cue="50%">
</Style.Animations>
</Style>
- <Style Selector=":is(Panel).NavBarItemPanel.wallets Ellipse.isLoggedIn.isCoinjoining">
+ <Style Selector="c|NavBarItem Ellipse.isLoggedIn.isCoinjoining">
<Setter Property="Fill" Value="{DynamicResource CoinjoinActiveColor}" />
</Style>
- <Style Selector="c|NavBarItem" x:DataType="vm:NavBarItemViewModel">
- <Setter Property="IsSelectable" Value="{Binding IsSelectable}" />
- </Style>
-
- <Style Selector="c|NavBarItem :is(Panel).NavBarItemPanel DockPanel LayoutTransformControl">
- <Setter Property="LayoutTransform" Value="scaleY(1)" />
- <Setter Property="Opacity" Value="1" />
- <Setter Property="Margin" Value="-9, 0" />
- <Setter Property="Transitions">
- <Transitions>
- <TransformOperationsTransition Property="LayoutTransform" Duration="0:0:0.175" />
- <DoubleTransition Property="Opacity" Duration="0:0:0.175" Easing="1,0,1,0" />
- </Transitions>
- </Setter>
- </Style>
-
- <Style Selector=":is(Panel).NavBarItemPanel DockPanel TextBlock">
- <Setter Property="TextAlignment" Value="Center" />
- <Setter Property="FontSize" Value="{StaticResource FontSizeH9}" />
- </Style>
-
- <Style Selector="c|NavBarItem:selected :is(Panel).NavBarItemPanel DockPanel LayoutTransformControl">
- <Setter Property="LayoutTransform" Value="scaleY(0)" />
- <Setter Property="Opacity" Value="0" />
- </Style>
-
- <Style Selector="c|NavBarItem PathIcon" x:CompileBindings="False">
- <Setter Property="Data" Value="{Binding IconName, Converter={x:Static conv:NavBarIconConverter.Instance}}" />
- </Style>
-
- <Style Selector="c|NavBarItem:selected PathIcon" x:CompileBindings="False">
- <Setter Property="Data" Value="{Binding IconNameFocused, Converter={x:Static conv:NavBarIconConverter.Instance}}" />
- </Style>
</UserControl.Styles>
- <DockPanel Margin="0 -5 0 5">
- <DockPanel Background="Transparent" DockPanel.Dock="Bottom">
- <c:NavBarListBox Items="{Binding BottomItems}"
- SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
- Name="BottomListBox"
- Classes="vertical narrowItemPadding"
- VerticalAlignment="Stretch">
- <c:NavBarListBox.ItemsPanel>
+ <DockPanel Margin="0 0 0 5">
+
+ <!-- Bottom items -->
+ <ItemsControl Items="{Binding BottomItems}"
+ DockPanel.Dock="Bottom">
+ <ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ReversibleStackPanel ReverseOrder="True" />
</ItemsPanelTemplate>
- </c:NavBarListBox.ItemsPanel>
- <c:NavBarListBox.DataTemplates>
+ </ItemsControl.ItemsPanel>
+ <ItemsControl.DataTemplates>
<DataTemplate DataType="vm:NavBarItemViewModel">
- <Panel Classes="NavBarItemPanel utilities"
- Cursor="Hand">
- <DockPanel>
- <LayoutTransformControl DockPanel.Dock="Bottom">
+ <c:NavBarItem>
+ <StackPanel>
+ <PathIcon Classes="new size24" />
+ <LayoutTransformControl>
<c:FadeOutTextBlock Text="{Binding Title}" />
</LayoutTransformControl>
- <PathIcon Foreground="{DynamicResource AcrylicTrimForeground}" Classes="new size24" />
- </DockPanel>
- </Panel>
+ </StackPanel>
+ </c:NavBarItem>
</DataTemplate>
- </c:NavBarListBox.DataTemplates>
- </c:NavBarListBox>
- </DockPanel>
+ </ItemsControl.DataTemplates>
+ </ItemsControl>
+
+ <!-- Separator -->
<Separator DockPanel.Dock="Bottom" />
+
<!-- Wallet List -->
- <DockPanel Background="Transparent">
- <c:NavBarListBox Items="{Binding Wallets}"
- SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
- ReSelectSelectedItem="False"
- Name="ItemsListBox"
- Classes="vertical narrowItemPadding"
- VirtualizationMode="None"
- VerticalAlignment="Stretch">
- <c:NavBarListBox.DataTemplates>
+ <ScrollViewer>
+ <ItemsControl Items="{Binding Wallets}">
+ <ItemsControl.DataTemplates>
<DataTemplate DataType="vmw:WalletViewModelBase">
- <Panel Classes="NavBarItemPanel wallets" ToolTip.Tip="{Binding Title}" Cursor="Hand" ClipToBounds="False">
+ <c:NavBarItem ToolTip.Tip="{Binding Title}">
+ <Panel>
<Ellipse Classes.isLoading="{Binding IsLoading}"
Classes.isLoggedIn="{Binding IsLoggedIn}"
Classes.isCoinjoining="{Binding IsCoinJoining}" />
- <DockPanel>
- <LayoutTransformControl DockPanel.Dock="Bottom">
+ <StackPanel>
+ <PathIcon Classes="new size24" />
+ <LayoutTransformControl>
<c:FadeOutTextBlock Text="{Binding Title}" />
</LayoutTransformControl>
- <PathIcon Foreground="{DynamicResource AcrylicTrimForeground}" Classes="new size24" />
- </DockPanel>
+ </StackPanel>
</Panel>
+ </c:NavBarItem>
</DataTemplate>
- </c:NavBarListBox.DataTemplates>
- </c:NavBarListBox>
- </DockPanel>
+ </ItemsControl.DataTemplates>
+ </ItemsControl>
+ </ScrollViewer>
+
</DockPanel>
</UserControl>

Refactor NavBar logic