Refactor PrivacyBar
MODIFY
WalletWasabi.Fluent/App.axaml
WalletWasabi.Fluent/App.axaml
<StyleInclude Source="avares://WalletWasabi.Fluent/Controls/TileButton.axaml" />
<StyleInclude Source="avares://WalletWasabi.Fluent/Controls/QuestionControl.axaml" />
<StyleInclude Source="avares://WalletWasabi.Fluent/Controls/PreviewItem.axaml" />
+ <StyleInclude Source="avares://WalletWasabi.Fluent/Controls/PrivacyBar.axaml" />
<StyleInclude Source="avares://WalletWasabi.Fluent/Controls/AdorningContentControl.axaml" />
<StyleInclude Source="avares://WalletWasabi.Fluent/Controls/TileControl.axaml" />
<StyleInclude Source="avares://WalletWasabi.Fluent/Controls/StatusItem.axaml" />

ADD
WalletWasabi.Fluent/Controls/PrivacyBar.axaml
+<Styles xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:controls="using:WalletWasabi.Fluent.Controls">
+ <Style Selector="controls|PrivacyBar">
+ <Setter Property="ItemsPanel">
+ <ItemsPanelTemplate>
+ <Panel />
+ </ItemsPanelTemplate>
+ </Setter>
+ </Style>
+
+ <Style Selector="controls|PrivacyBarSegment">
+ <Setter Property="Template">
+ <ControlTemplate>
+ <Path Data="{TemplateBinding Data}"
+ Fill="{TemplateBinding Foreground}" />
+ </ControlTemplate>
+ </Setter>
+ </Style>
+
+ <Style Selector="controls|PrivacyBarSegment[PrivacyLevel=Invalid]">
+ <Setter Property="Foreground" Value="{DynamicResource PrivacyLevelNoneBrush}" />
+ </Style>
+
+ <Style Selector="controls|PrivacyBarSegment[PrivacyLevel=Private]">
+ <Setter Property="Foreground" Value="{DynamicResource PrivacyLevelStrongBrush}" />
+ </Style>
+
+ <Style Selector="controls|PrivacyBarSegment[PrivacyLevel=SemiPrivate]">
+ <Setter Property="Foreground" Value="{DynamicResource PrivacyLevelMediumBrush}" />
+ </Style>
+
+ <Style Selector="controls|PrivacyBarSegment[PrivacyLevel=NonPrivate]">
+ <Setter Property="Foreground" Value="{DynamicResource PrivacyLevelNoneBrush}" />
+ </Style>
+</Styles>

ADD
WalletWasabi.Fluent/Controls/PrivacyBar.axaml.cs
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Generators;
+using Avalonia.Controls.Presenters;
+using Avalonia.VisualTree;
+using System.Linq;
+
+namespace WalletWasabi.Fluent.Controls;
+
+public class PrivacyBar : ItemsPresenter
+{
+ private const double GapBetweenSegments = 1.5;
+ private const double EnlargeThreshold = 2;
+ private const double EnlargeBy = 1;
+
+ public static readonly StyledProperty<decimal> TotalAmountProperty =
+ AvaloniaProperty.Register<PrivacyBarSegment, decimal>(nameof(TotalAmount));
+
+ public decimal TotalAmount
+ {
+ get => GetValue(TotalAmountProperty);
+ set => SetValue(TotalAmountProperty, value);
+ }
+
+ protected override IItemContainerGenerator CreateItemContainerGenerator()
+ {
+ return new PrivacyBarItemContainerGenerator(this);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ var children =
+ this.GetVisualDescendants()
+ .OfType<PrivacyBarSegment>()
+ .ToList();
+
+ var coinCount = children.Count;
+ var totalAmount = TotalAmount;
+ var usableWidth = finalSize.Width - (coinCount - 1) * GapBetweenSegments;
+
+ // Calculate the width of the segments.
+ var rawSegments =
+ children.Select(segment =>
+ {
+ var amount = (double)segment.Amount;
+ var width = Math.Abs(usableWidth * amount / (double)totalAmount);
+
+ return (Coin: segment, Width: width);
+ }).ToArray();
+
+ // Artificially enlarge segments smaller than the threshold px in order to make them visible.
+ // Meanwhile decrease those segments that are larger than threshold px in order to fit all in the bar.
+ var segmentsToEnlarge = rawSegments.Where(x => x.Width < EnlargeThreshold).ToArray();
+ var segmentsToReduce = rawSegments.Except(segmentsToEnlarge).ToArray();
+ var reduceBy = segmentsToEnlarge.Length * EnlargeBy / segmentsToReduce.Length;
+ if (segmentsToEnlarge.Any() && segmentsToReduce.Any() && segmentsToReduce.All(x => x.Width - reduceBy > 0))
+ {
+ rawSegments = rawSegments.Select(x =>
+ {
+ var finalWidth = x.Width < EnlargeThreshold ? x.Width + EnlargeBy : x.Width - reduceBy;
+ return (Coin: x.Coin, Width: finalWidth);
+ }).ToArray();
+ }
+
+ var start = 0.0;
+ foreach (var (coin, width) in rawSegments)
+ {
+ coin.Start = start;
+ coin.BarWidth = width;
+
+ start += width + GapBetweenSegments;
+ }
+
+ return base.ArrangeOverride(finalSize);
+ }
+
+ private class PrivacyBarItemContainerGenerator : ItemContainerGenerator
+ {
+ public PrivacyBarItemContainerGenerator(IControl owner) : base(owner)
+ {
+ }
+
+ protected override IControl CreateContainer(object item)
+ {
+ return new PrivacyBarSegment
+ {
+ DataContext = item,
+ };
+ }
+ }
+}

ADD
WalletWasabi.Fluent/Controls/PrivacyBarSegment.cs
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using Avalonia.Media;
+using WalletWasabi.Fluent.Models;
+
+namespace WalletWasabi.Fluent.Controls;
+
+public class PrivacyBarSegment : TemplatedControl
+{
+ public static readonly StyledProperty<decimal> AmountProperty =
+ AvaloniaProperty.Register<PrivacyBarSegment, decimal>(nameof(Amount));
+
+ public static readonly StyledProperty<double> StartProperty =
+ AvaloniaProperty.Register<PrivacyBarSegment, double>(nameof(Start));
+
+ public static readonly StyledProperty<double> BarWidthProperty =
+ AvaloniaProperty.Register<PrivacyBarSegment, double>(nameof(BarWidth));
+
+ public static readonly StyledProperty<Geometry> DataProperty =
+ AvaloniaProperty.Register<PrivacyBarSegment, Geometry>(nameof(Data));
+
+ public static readonly StyledProperty<PrivacyLevel> PrivacyLevelProperty =
+ AvaloniaProperty.Register<PrivacyBarSegment, PrivacyLevel>(nameof(PrivacyLevel));
+
+ static PrivacyBarSegment()
+ {
+ AffectsArrange<PrivacyBarSegment>(StartProperty);
+ AffectsArrange<PrivacyBarSegment>(BarWidthProperty);
+ }
+
+ public decimal Amount
+ {
+ get => GetValue(AmountProperty);
+ set => SetValue(AmountProperty, value);
+ }
+
+ public double Start
+ {
+ get => GetValue(StartProperty);
+ set => SetValue(StartProperty, value);
+ }
+
+ public double BarWidth
+ {
+ get => GetValue(BarWidthProperty);
+ set => SetValue(BarWidthProperty, value);
+ }
+
+ public Geometry Data
+ {
+ get => GetValue(DataProperty);
+ set => SetValue(DataProperty, value);
+ }
+
+ public PrivacyLevel PrivacyLevel
+ {
+ get => GetValue(PrivacyLevelProperty);
+ set => SetValue(PrivacyLevelProperty, value);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ var size = base.ArrangeOverride(finalSize);
+ Data = new RectangleGeometry(new Rect(Start, 0, BarWidth, size.Height));
+ return size;
+ }
+}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyBarItemViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyBarItemViewModel.cs
-using Avalonia;
-using Avalonia.Media;
-using System.Linq;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Models;
-using WalletWasabi.Fluent.ViewModels.Wallets.Advanced.WalletCoins;
-using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles.PrivacyRing;
-public class PrivacyBarItemViewModel : ViewModelBase, IDisposable
+public class PrivacyBarItemViewModel : ViewModelBase
{
- public const double BarHeight = 10;
-
- public PrivacyBarItemViewModel(PrivacyBarViewModel parent, SmartCoin coin, double start, double width)
+ public PrivacyBarItemViewModel(PrivacyBarViewModel parent, SmartCoin coin)
{
- Coin = new WalletCoinViewModel(coin);
-
- var anonScore = parent.Wallet.AnonScoreTarget;
- IsPrivate = coin.IsPrivate(anonScore);
- IsSemiPrivate = coin.IsSemiPrivate(anonScore);
- IsNonPrivate = !IsPrivate && !IsSemiPrivate;
-
- Data = new RectangleGeometry
- {
- Rect = new Rect(start, 0, width, BarHeight)
- };
+ PrivacyLevel = coin.GetPrivacyLevel(parent.Wallet.AnonScoreTarget);
+ Amount = coin.Amount.ToDecimal(NBitcoin.MoneyUnit.BTC);
}
- public PrivacyBarItemViewModel(PrivacyLevel privacyLevel, double start, double width)
+ public PrivacyBarItemViewModel(PrivacyLevel privacyLevel, decimal amount)
{
- IsPrivate = privacyLevel == PrivacyLevel.Private;
- IsSemiPrivate = privacyLevel == PrivacyLevel.SemiPrivate;
- IsNonPrivate = privacyLevel == PrivacyLevel.NonPrivate;
-
- Data = new RectangleGeometry
- {
- Rect = new Rect(start, 0, width, BarHeight)
- };
+ PrivacyLevel = privacyLevel;
+ Amount = amount;
}
- public RectangleGeometry Data { get; }
- public WalletCoinViewModel? Coin { get; }
-
- public bool IsPrivate { get; }
- public bool IsSemiPrivate { get; }
- public bool IsNonPrivate { get; }
+ public decimal Amount { get; }
- public void Dispose()
- {
- Coin?.Dispose();
- }
+ public PrivacyLevel PrivacyLevel { get; }
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyBarViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyBarViewModel.cs
@@ -3,23 +3,19 @@ using DynamicData.Binding;
using ReactiveUI;
using System.Collections.Generic;
using System.Linq;
-using System.Reactive;
using System.Reactive.Linq;
using NBitcoin;
-using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Wallets;
using System.Reactive.Disposables;
+using WalletWasabi.Fluent.Helpers;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles.PrivacyRing;
public partial class PrivacyBarViewModel : ActivatableViewModel
{
- private const decimal GapBetweenSegments = 1.5m;
- private const decimal EnlargeThreshold = 2m;
- private const decimal EnlargeBy = 1m;
private readonly WalletViewModel _walletViewModel;
- [AutoNotify] private double _width;
+ [AutoNotify] private decimal _totalAmount;
public PrivacyBarViewModel(WalletViewModel walletViewModel)
{
@@ -34,38 +30,31 @@ public partial class PrivacyBarViewModel : ActivatableViewModel
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Uses DisposeWith()")]
protected override void OnActivated(CompositeDisposable disposables)
{
+ Items.Clear();
+
var itemsSourceList = new SourceList<PrivacyBarItemViewModel>();
itemsSourceList
.DisposeWith(disposables)
.Connect()
- .ObserveOn(RxApp.MainThreadScheduler)
.Bind(Items)
- .DisposeMany()
.Subscribe()
.DisposeWith(disposables);
_walletViewModel.UiTriggers.PrivacyProgressUpdateTrigger
- .CombineLatest(this.WhenAnyValue(x => x.Width))
- .ObserveOn(RxApp.MainThreadScheduler)
- .Subscribe(_ => RenderBar(itemsSourceList))
+ .Subscribe(_ => itemsSourceList.Edit(Update))
.DisposeWith(disposables);
}
- private void RenderBar(SourceList<PrivacyBarItemViewModel> itemsSourceList)
+ private void Update(IExtendedList<PrivacyBarItemViewModel> list)
{
- Items.Clear();
+ TotalAmount = _walletViewModel.Wallet.Coins.TotalAmount().ToDecimal(MoneyUnit.BTC);
- itemsSourceList.Edit(list => CreateSegments(list));
- }
-
- private void CreateSegments(IExtendedList<PrivacyBarItemViewModel> list)
- {
list.Clear();
var coinCount = _walletViewModel.Wallet.Coins.Count();
- if (Width == 0d || coinCount == 0d)
+ if (coinCount == 0d)
{
return;
}
@@ -82,95 +71,21 @@ public partial class PrivacyBarViewModel : ActivatableViewModel
private IEnumerable<PrivacyBarItemViewModel> CreateSegmentsByCoin()
{
- var coinCount = _walletViewModel.Wallet.Coins.Count();
- var totalAmount = _walletViewModel.Wallet.Coins.Sum(x => Math.Abs(x.Amount.ToDecimal(MoneyUnit.BTC)));
- var usableWidth = (decimal)Width - (coinCount - 1) * GapBetweenSegments;
-
- // Calculate the width of the segments.
- var rawSegments =
- _walletViewModel.Wallet.Coins.Select(coin =>
- {
- var amount = coin.Amount.ToDecimal(MoneyUnit.BTC);
- var width = Math.Abs(usableWidth * amount / totalAmount);
-
- return (Coin: coin, Width: width);
- }).ToArray();
-
- // Artificially enlarge segments smaller than the threshold px in order to make them visible.
- // Meanwhile decrease those segments that are larger than threshold px in order to fit all in the bar.
- var segmentsToEnlarge = rawSegments.Where(x => x.Width < EnlargeThreshold).ToArray();
- var segmentsToReduce = rawSegments.Except(segmentsToEnlarge).ToArray();
- var reduceBy = segmentsToEnlarge.Length * EnlargeBy / segmentsToReduce.Length;
- if (segmentsToEnlarge.Any() && segmentsToReduce.Any() && segmentsToReduce.All(x => x.Width - reduceBy > 0))
- {
- rawSegments = rawSegments.Select(x =>
- {
- var finalWidth = x.Width < EnlargeThreshold ? x.Width + EnlargeBy : x.Width - reduceBy;
- return (Coin: x.Coin, Width: finalWidth);
- }).ToArray();
- }
-
- // Order the coins as they will be shown in the bar.
- rawSegments = rawSegments
- .OrderBy(x => x.Coin.GetPrivacyLevel(_walletViewModel.Wallet))
- .ThenByDescending(x => x.Width)
- .ToArray();
-
- var start = 0.0m;
- foreach (var (coin, width) in rawSegments)
- {
- yield return new PrivacyBarItemViewModel(this, coin, (double)start, (double)width);
-
- start += width + GapBetweenSegments;
- }
+ return
+ _walletViewModel.Wallet.Coins
+ .Select(coin => new PrivacyBarItemViewModel(this, coin))
+ .OrderBy(x => x.PrivacyLevel)
+ .ThenByDescending(x => x.Amount)
+ .ToList();
}
private IEnumerable<PrivacyBarItemViewModel> CreateSegmentsByPrivacyLevel()
{
- var groupsByPrivacy =
- _walletViewModel.Wallet.Coins.GroupBy(x => x.GetPrivacyLevel(_walletViewModel.Wallet))
+ return
+ _walletViewModel.Wallet.Coins
+ .GroupBy(x => x.GetPrivacyLevel(_walletViewModel.Wallet))
.OrderBy(x => (int)x.Key)
+ .Select(x => new PrivacyBarItemViewModel(x.Key, x.Sum(x => x.Amount.ToDecimal(MoneyUnit.BTC))))
.ToList();
-
- var totalAmount = _walletViewModel.Wallet.Coins.Sum(x => Math.Abs(x.Amount.ToDecimal(MoneyUnit.BTC)));
-
- var usableWidth = (decimal)Width - (groupsByPrivacy.Count - 1) * GapBetweenSegments;
-
- // Calculate the width of the segments.
- var rawSegments = groupsByPrivacy.Select(group =>
- {
- var amount = group.Sum(x => x.Amount.ToDecimal(MoneyUnit.BTC));
- var width = Math.Abs(usableWidth * amount / totalAmount);
-
- return (PrivacyLevel: group.Key, Width: width);
- }).ToArray();
-
- // Artificially enlarge segments smaller than threshold px in order to make them visible.
- // Meanwhile decrease those segments that are larger than threshold px in order to fit all in the bar.
- var segmentsToEnlarge = rawSegments.Where(x => x.Width < EnlargeThreshold).ToArray();
- var segmentsToReduce = rawSegments.Except(segmentsToEnlarge).ToArray();
- var reduceBy = segmentsToEnlarge.Length * EnlargeBy / segmentsToReduce.Length;
- if (segmentsToEnlarge.Any() && segmentsToReduce.Any() && segmentsToReduce.All(x => x.Width - reduceBy > 0))
- {
- rawSegments = rawSegments.Select(x =>
- {
- var finalWidth = x.Width < EnlargeThreshold ? x.Width + EnlargeBy : x.Width - reduceBy;
- return (PrivacyLevel: x.PrivacyLevel, Width: finalWidth);
- }).ToArray();
- }
-
- // Order the segments as they will be shown in the bar.
- rawSegments = rawSegments
- .OrderBy(x => x.PrivacyLevel)
- .ThenByDescending(x => x.Width)
- .ToArray();
-
- var start = 0.0m;
- foreach (var (privacyLevel, width) in rawSegments)
- {
- yield return new PrivacyBarItemViewModel(privacyLevel, (double)start, (double)width);
-
- start += width + GapBetweenSegments;
- }
}
}

DELETE
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyBar/PrivacyBar.axaml
-<UserControl xmlns="https://github.com/avaloniaui"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:i="using:Avalonia.Xaml.Interactivity"
- xmlns:behaviors="using:WalletWasabi.Fluent.Behaviors"
- xmlns:tiles="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles"
- mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="WalletWasabi.Fluent.Views.Wallets.Home.Tiles.PrivacyBar.PrivacyBar"
- x:CompileBindings="True" x:DataType="tiles:PrivacyControlTileViewModel">
- <Panel x:Name="Panel">
- <i:Interaction.Behaviors>
- <behaviors:BoundsObserverBehavior Bounds="{Binding #Panel.Bounds, Mode=OneWay}"
- Width="{Binding PrivacyBar.Width, Mode=TwoWay}"
- x:CompileBindings="False" />
- </i:Interaction.Behaviors>
-
- <Button Classes="plain" Command="{Binding ShowDetailsCommand}"
- ToolTip.Tip="Show Details"
- IsVisible="{Binding !!PrivacyBar.Items.Count}">
- <ItemsControl Items="{Binding PrivacyBar.Items}" Height="8">
- <ItemsControl.Styles>
- <Style Selector="Path.private">
- <Setter Property="Fill" Value="{DynamicResource PrivacyLevelStrongBrush}" />
- </Style>
-
- <Style Selector="Path.semiPrivate">
- <Setter Property="Fill" Value="{DynamicResource PrivacyLevelMediumBrush}" />
- </Style>
-
- <Style Selector="Path.nonPrivate">
- <Setter Property="Fill" Value="{DynamicResource PrivacyLevelNoneBrush}" />
- </Style>
- </ItemsControl.Styles>
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <Panel />
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate>
- <Path Data="{Binding Data}"
- Classes.private="{Binding IsPrivate}"
- Classes.semiPrivate="{Binding IsSemiPrivate}"
- Classes.nonPrivate="{Binding IsNonPrivate}" />
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
- </Button>
- </Panel>
-</UserControl>

ADD
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyBar/PrivacyBarView.axaml
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:c="using:WalletWasabi.Fluent.Controls"
+ xmlns:behaviors="using:WalletWasabi.Fluent.Behaviors"
+ xmlns:tiles="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="WalletWasabi.Fluent.Views.Wallets.Home.Tiles.PrivacyBar.PrivacyBarView"
+ x:CompileBindings="True" x:DataType="tiles:PrivacyControlTileViewModel">
+ <Button Classes="plain" Command="{Binding ShowDetailsCommand}"
+ ToolTip.Tip="Show Details"
+ IsVisible="{Binding !!PrivacyBar.Items.Count}">
+ <c:PrivacyBar Items="{Binding PrivacyBar.Items}"
+ TotalAmount="{Binding PrivacyBar.TotalAmount}"
+ Height="8">
+ <c:PrivacyBar.Styles>
+ <Style Selector="c|PrivacyBarSegment" x:CompileBindings="False">
+ <Setter Property="Amount" Value="{Binding Amount}" />
+ <Setter Property="PrivacyLevel" Value="{Binding PrivacyLevel}" />
+ </Style>
+ </c:PrivacyBar.Styles>
+ </c:PrivacyBar>
+ </Button>
+</UserControl>

RENAME
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyBar/PrivacyBar.axaml.cs
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyBar/PrivacyBarView.axaml.cs
@@ -3,9 +3,9 @@ using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Home.Tiles.PrivacyBar;
-public partial class PrivacyBar : UserControl
+public partial class PrivacyBarView : UserControl
{
- public PrivacyBar()
+ public PrivacyBarView()
{
InitializeComponent();
}

MODIFY
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyControlTileView.axaml
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyControlTileView.axaml
<c:TileControl Title="PRIVACY PROGRESS" IsBottomContentVisible="{Binding HasPrivateBalance}">
<DockPanel>
- <privacyBar:PrivacyBar DockPanel.Dock="Bottom" />
+ <privacyBar:PrivacyBarView DockPanel.Dock="Bottom" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="6">
<Button Command="{Binding ShowDetailsCommand}" Classes="plain"
ToolTip.Tip="Show Details" VerticalAlignment="Center" HorizontalAlignment="Center"

Refactor PrivacyBar