make difference between transaction and cj items
MODIFY
WalletWasabi.Fluent/ViewModels/ViewModelBase.cs
WalletWasabi.Fluent/ViewModels/ViewModelBase.cs
@@ -3,6 +3,7 @@ using System;
using System.Collections;
using System.ComponentModel;
using WalletWasabi.Fluent.Validation;
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
namespace WalletWasabi.Fluent.ViewModels
{

DELETE
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryItemViewModel.cs
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Linq;
-using System.Threading.Tasks;
-using System.Windows.Input;
-using NBitcoin;
-using ReactiveUI;
-using WalletWasabi.Blockchain.Transactions;
-using WalletWasabi.Fluent.Extensions;
-using WalletWasabi.Fluent.ViewModels.Navigation;
-using WalletWasabi.Models;
-
-namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
-{
- public partial class HistoryItemViewModel : ViewModelBase
- {
- [AutoNotify] private bool _isFlashing;
- [AutoNotify] private bool _isConfirmed;
- [AutoNotify] private int _orderIndex;
- [AutoNotify] private DateTimeOffset _date;
-
- public HistoryItemViewModel(int orderIndex, TransactionSummary transactionSummary, WalletViewModel walletViewModel, Money balance, IObservable<Unit> updateTrigger)
- {
- TransactionSummary = transactionSummary;
- Date = transactionSummary.DateTime.ToLocalTime();
- IsCoinJoin = transactionSummary.IsLikelyCoinJoinOutput;
- OrderIndex = orderIndex;
- Balance = balance;
-
- var confirmations = transactionSummary.Height.Type == HeightType.Chain ? (int) Services.BitcoinStore.SmartHeaderChain.TipHeight - transactionSummary.Height.Value + 1 : 0;
- IsConfirmed = confirmations > 0;
-
- var amount = transactionSummary.Amount;
- if (amount < Money.Zero)
- {
- OutgoingAmount = amount * -1;
- }
- else
- {
- IncomingAmount = amount;
- }
-
- Label = transactionSummary.Label.Take(1).ToList();
- FilteredLabel = transactionSummary.Label.Skip(1).ToList();
-
- ShowDetailsCommand = ReactiveCommand.Create(() => RoutableViewModel.Navigate(NavigationTarget.DialogScreen).To(new TransactionDetailsViewModel(transactionSummary, walletViewModel.Wallet, updateTrigger)));
-
- this.WhenAnyValue(x => x.IsFlashing)
- .Where(x => x)
- .Subscribe(async _ =>
- {
- await Task.Delay(1260);
- IsFlashing = false;
- });
- }
-
- public ICommand ShowDetailsCommand { get; }
-
- public TransactionSummary TransactionSummary { get; }
-
- public Money Balance { get; set; }
-
- public Money? IncomingAmount { get; }
-
- public Money? OutgoingAmount { get; }
-
- public List<string> FilteredLabel { get; }
-
- public List<string> Label { get; }
-
- public bool IsCoinJoin { get; }
-
- public void Update(HistoryItemViewModel item)
- {
- OrderIndex = item.OrderIndex;
- Date = item.TransactionSummary.DateTime.ToLocalTime();
- IsConfirmed = item.TransactionSummary.IsConfirmed();
- }
- }
-}

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryItems/CoinJoinHistoryItemViewModel.cs
+using System;
+using NBitcoin;
+using WalletWasabi.Blockchain.Transactions;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems
+{
+ public class CoinJoinHistoryItemViewModel : HistoryItemViewModelBase
+ {
+
+ public CoinJoinHistoryItemViewModel(
+ int orderIndex,
+ TransactionSummary transactionSummary,
+ WalletViewModel walletViewModel,
+ Money balance,
+ DateTimeOffset lastCjDateInGroup)
+ : base(orderIndex, transactionSummary, balance)
+ {
+ LastCjDateInGroup = lastCjDateInGroup;
+ OutgoingAmount = transactionSummary.Amount * -1;
+ IsCoinJoin = true;
+
+ UpdateDateString();
+ }
+
+ public DateTimeOffset LastCjDateInGroup { get; private set; }
+
+ public override void Update(HistoryItemViewModelBase item)
+ {
+ if (item is not CoinJoinHistoryItemViewModel coinJoinHistoryItemViewModel)
+ {
+ throw new InvalidOperationException("Not the same type!");
+ }
+
+ LastCjDateInGroup = coinJoinHistoryItemViewModel.LastCjDateInGroup;
+
+ base.Update(item);
+ }
+
+ protected sealed override void UpdateDateString()
+ {
+ DateString = Date.Day == LastCjDateInGroup.Day
+ ? $"{Date.ToLocalTime():MM/dd/yyyy}"
+ : $"{Date.ToLocalTime():MM/dd/yy} - {LastCjDateInGroup.ToLocalTime():MM/dd/yy}";
+ }
+ }
+}

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryItems/HistoryItemViewModelBase.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using NBitcoin;
+using ReactiveUI;
+using WalletWasabi.Blockchain.Transactions;
+using WalletWasabi.Fluent.Extensions;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems
+{
+ public abstract partial class HistoryItemViewModelBase : ViewModelBase
+ {
+ [AutoNotify] private bool _isFlashing;
+ [AutoNotify] private int _orderIndex;
+ [AutoNotify] private DateTimeOffset _date;
+ [AutoNotify] private string? _dateString;
+ [AutoNotify] private bool _isConfirmed;
+
+ protected HistoryItemViewModelBase(int orderIndex, TransactionSummary transactionSummary, Money balance)
+ {
+ TransactionSummary = transactionSummary;
+ Date = transactionSummary.DateTime.ToLocalTime();
+ OrderIndex = orderIndex;
+ Balance = balance;
+ IsConfirmed = transactionSummary.IsConfirmed();
+ Label = transactionSummary.Label.Take(1).ToList();
+ FilteredLabel = transactionSummary.Label.Skip(1).ToList();
+
+ this.WhenAnyValue(x => x.IsFlashing)
+ .Where(x => x)
+ .Subscribe(async _ =>
+ {
+ await Task.Delay(1260);
+ IsFlashing = false;
+ });
+ }
+
+ public uint256 TransactionId => TransactionSummary.TransactionId;
+
+ public List<string> FilteredLabel { get; protected set; }
+
+ public List<string> Label { get; protected set; }
+
+ public bool IsCoinJoin { get; protected set; }
+
+ public TransactionSummary TransactionSummary { get; }
+
+ public Money Balance { get; set; }
+
+ public Money? OutgoingAmount { get; protected set; }
+
+ public Money? IncomingAmount { get; protected set; }
+
+ public ICommand? ShowDetailsCommand { get; protected set; }
+
+ public virtual void Update(HistoryItemViewModelBase item)
+ {
+ OrderIndex = item.OrderIndex;
+ Date = item.Date;
+ IsConfirmed = item.IsConfirmed;
+ }
+
+ protected abstract void UpdateDateString();
+ }
+}

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryItems/TransactionHistoryItemViewModel.cs
+using System;
+using System.Reactive;
+using NBitcoin;
+using ReactiveUI;
+using WalletWasabi.Blockchain.Transactions;
+using WalletWasabi.Fluent.ViewModels.Navigation;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems
+{
+ public class TransactionHistoryItemViewModel : HistoryItemViewModelBase
+ {
+ public TransactionHistoryItemViewModel(int orderIndex, TransactionSummary transactionSummary, WalletViewModel walletViewModel, Money balance, IObservable<Unit> updateTrigger)
+ : base(orderIndex, transactionSummary, balance)
+ {
+ var amount = transactionSummary.Amount;
+ if (amount < Money.Zero)
+ {
+ OutgoingAmount = amount * -1;
+ }
+ else
+ {
+ IncomingAmount = amount;
+ }
+
+ ShowDetailsCommand = ReactiveCommand.Create(() =>
+ RoutableViewModel.Navigate(NavigationTarget.DialogScreen).To(
+ new TransactionDetailsViewModel(transactionSummary, walletViewModel.Wallet, updateTrigger)));
+
+ UpdateDateString();
+ }
+
+ protected sealed override void UpdateDateString()
+ {
+ DateString = $"{Date.ToLocalTime():MM/dd/yyyy HH:mm}";
+ }
+ }
+}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryViewModel.cs
@@ -14,20 +14,21 @@ using NBitcoin;
using ReactiveUI;
using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.Fluent.Extensions;
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
using WalletWasabi.Logging;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
{
public partial class HistoryViewModel : ActivatableViewModel
{
- private readonly SourceList<HistoryItemViewModel> _transactionSourceList;
+ private readonly SourceList<HistoryItemViewModelBase> _transactionSourceList;
private readonly WalletViewModel _walletViewModel;
private readonly IObservable<Unit> _updateTrigger;
- private readonly ObservableCollectionExtended<HistoryItemViewModel> _transactions;
- private readonly ObservableCollectionExtended<HistoryItemViewModel> _unfilteredTransactions;
+ private readonly ObservableCollectionExtended<HistoryItemViewModelBase> _transactions;
+ private readonly ObservableCollectionExtended<HistoryItemViewModelBase> _unfilteredTransactions;
private readonly object _transactionListLock = new();
- [AutoNotify] private HistoryItemViewModel? _selectedItem;
+ [AutoNotify] private HistoryItemViewModelBase? _selectedItem;
[AutoNotify(SetterModifier = AccessModifier.Private)] private bool _isTransactionHistoryEmpty;
[AutoNotify(SetterModifier = AccessModifier.Private)] private bool _isInitialized;
@@ -35,21 +36,21 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
{
_walletViewModel = walletViewModel;
_updateTrigger = updateTrigger;
- _transactionSourceList = new SourceList<HistoryItemViewModel>();
- _transactions = new ObservableCollectionExtended<HistoryItemViewModel>();
- _unfilteredTransactions = new ObservableCollectionExtended<HistoryItemViewModel>();
+ _transactionSourceList = new SourceList<HistoryItemViewModelBase>();
+ _transactions = new ObservableCollectionExtended<HistoryItemViewModelBase>();
+ _unfilteredTransactions = new ObservableCollectionExtended<HistoryItemViewModelBase>();
this.WhenAnyValue(x => x.UnfilteredTransactions.Count)
.Subscribe(x => IsTransactionHistoryEmpty = x <= 0);
- var sortDescription = DataGridSortDescription.FromPath(nameof(HistoryItemViewModel.OrderIndex), ListSortDirection.Descending);
+ var sortDescription = DataGridSortDescription.FromPath(nameof(TransactionHistoryItemViewModel.OrderIndex), ListSortDirection.Descending);
CollectionView = new DataGridCollectionView(Transactions);
CollectionView.SortDescriptions.Add(sortDescription);
_transactionSourceList
.Connect()
.ObserveOn(RxApp.MainThreadScheduler)
- .Sort(SortExpressionComparer<HistoryItemViewModel>.Descending(x => x.OrderIndex))
+ .Sort(SortExpressionComparer<HistoryItemViewModelBase>.Descending(x => x.OrderIndex))
.Bind(_unfilteredTransactions)
.Bind(_transactions)
.Subscribe();
@@ -57,9 +58,9 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
public DataGridCollectionView CollectionView { get; }
- public ObservableCollection<HistoryItemViewModel> UnfilteredTransactions => _unfilteredTransactions;
+ public ObservableCollection<HistoryItemViewModelBase> UnfilteredTransactions => _unfilteredTransactions;
- public ObservableCollection<HistoryItemViewModel> Transactions => _transactions;
+ public ObservableCollection<HistoryItemViewModelBase> Transactions => _transactions;
public void SelectTransaction(uint256 txid)
{
@@ -87,28 +88,23 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
{
var historyBuilder = new TransactionHistoryBuilder(_walletViewModel.Wallet);
var txRecordList = await Task.Run(historyBuilder.BuildHistorySummary);
- var rawHistoryList = GroupConsecutiveCoinJoins(txRecordList).ToArray();
+ var newTransactionsList = GetHistoryList(txRecordList).ToArray();
lock (_transactionListLock)
{
var copyList = Transactions.ToList();
- foreach (HistoryItemViewModel historyItemViewModel in copyList)
+ foreach (var oldItem in copyList)
{
- if (rawHistoryList.All(x => x.TransactionId != historyItemViewModel.TransactionSummary.TransactionId))
+ if (newTransactionsList.All(x => x.TransactionId != oldItem.TransactionId))
{
- _transactionSourceList.Remove(historyItemViewModel);
+ _transactionSourceList.Remove(oldItem);
}
}
- Money balance = Money.Zero;
- for (var i = 0; i < rawHistoryList.Length; i++)
+ foreach (var newItem in newTransactionsList)
{
- var transactionSummary = rawHistoryList[i];
- balance += transactionSummary.Amount;
- var newItem = new HistoryItemViewModel(i, transactionSummary, _walletViewModel, balance, _updateTrigger);
-
- if (_transactions.FirstOrDefault(x => x.TransactionSummary.TransactionId == newItem.TransactionSummary.TransactionId) is { } item)
+ if (_transactions.FirstOrDefault(x => x.TransactionId == newItem.TransactionId) is { } item)
{
item.Update(newItem);
}
@@ -130,15 +126,20 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
}
}
- private IEnumerable<TransactionSummary> GroupConsecutiveCoinJoins(List<TransactionSummary> txRecordList)
+ private IEnumerable<HistoryItemViewModelBase> GetHistoryList(List<TransactionSummary> txRecordList)
{
+ Money balance = Money.Zero;
+
for (var i = 0; i < txRecordList.Count; i++)
{
var item = txRecordList[i];
+ balance += item.Amount;
+
if (item.IsLikelyCoinJoinOutput)
{
item.Label = "Privacy Increasement";
+ DateTimeOffset lastCjDateInGroup = item.DateTime;
for (var j = i + 1; j < txRecordList.Count; j++)
{
@@ -147,7 +148,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
if (!nextItem.IsLikelyCoinJoinOutput)
{
i = j - 1;
- yield return item;
+ yield return new CoinJoinHistoryItemViewModel(i, item, _walletViewModel, balance, lastCjDateInGroup);
break;
}
@@ -156,12 +157,14 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History
continue;
}
+ balance += nextItem.Amount;
item.Amount += nextItem.Amount;
+ lastCjDateInGroup = nextItem.DateTime;
}
}
else
{
- yield return item;
+ yield return new TransactionHistoryItemViewModel(i, item, _walletViewModel, balance, _updateTrigger);
}
}
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/WalletBalanceChartTileViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/WalletBalanceChartTileViewModel.cs
@@ -10,15 +10,15 @@ using ReactiveUI;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Models;
using WalletWasabi.Fluent.Morph;
-using WalletWasabi.Fluent.ViewModels.Wallets.Home.History;
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
{
public partial class WalletBalanceChartTileViewModel : TileViewModel
{
- private readonly ObservableCollection<HistoryItemViewModel> _history;
+ private readonly ObservableCollection<HistoryItemViewModelBase> _history;
- public WalletBalanceChartTileViewModel(ObservableCollection<HistoryItemViewModel> history)
+ public WalletBalanceChartTileViewModel(ObservableCollection<HistoryItemViewModelBase> history)
{
_history = history;

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/WalletBalanceTileViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/WalletBalanceTileViewModel.cs
@@ -9,7 +9,7 @@ using NBitcoin;
using ReactiveUI;
using WalletWasabi.Wallets;
using WalletWasabi.Fluent.Helpers;
-using WalletWasabi.Fluent.ViewModels.Wallets.Home.History;
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
using WalletWasabi.Models;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
@@ -18,7 +18,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
{
private readonly Wallet _wallet;
private readonly IObservable<Unit> _balanceChanged;
- private readonly ObservableCollection<HistoryItemViewModel> _history;
+ private readonly ObservableCollection<HistoryItemViewModelBase> _history;
[AutoNotify(SetterModifier = AccessModifier.Private)] private string? _balanceBtc;
[AutoNotify(SetterModifier = AccessModifier.Private)] private string? _balanceFiat;
[AutoNotify(SetterModifier = AccessModifier.Private)] private string? _balancePrivateBtc;
@@ -28,7 +28,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
[AutoNotify(SetterModifier = AccessModifier.Private)] private string? _recentTransactionStatus;
[AutoNotify(SetterModifier = AccessModifier.Private)] private bool _showRecentTransaction;
- public WalletBalanceTileViewModel(Wallet wallet, IObservable<Unit> balanceChanged, ObservableCollection<HistoryItemViewModel> history)
+ public WalletBalanceTileViewModel(Wallet wallet, IObservable<Unit> balanceChanged, ObservableCollection<HistoryItemViewModelBase> history)
{
_wallet = wallet;
_balanceChanged = balanceChanged;

MODIFY
WalletWasabi.Fluent/Views/Wallets/Home/History/HistoryTable.axaml
WalletWasabi.Fluent/Views/Wallets/Home/History/HistoryTable.axaml
xmlns:c="using:WalletWasabi.Fluent.Controls"
xmlns:history="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Home.History"
xmlns:converters="clr-namespace:WalletWasabi.Fluent.Converters"
+ xmlns:historyItems="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="history:HistoryViewModel"
x:CompileBindings="True"
SortMemberPath="OrderIndex">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
- <c:PrivacyContentControl x:DataType="history:HistoryItemViewModel"
- Content="{Binding Date, StringFormat='{}{0:MM/dd/yyyy HH:mm}'}"
+ <c:PrivacyContentControl x:DataType="historyItems:HistoryItemViewModelBase"
+ Content="{Binding DateString}"
Classes="monoSpaced"
FontSize="15"
Margin="10 0"
<Setter Property="Margin" Value="0" />
</Style>
</c:PrivacyContentControl.Styles>
- <Panel x:DataType="history:HistoryItemViewModel">
+ <Panel x:DataType="historyItems:HistoryItemViewModelBase">
<PathIcon ToolTip.Tip="Transaction" Data="{StaticResource normal_transaction}" Opacity="0.6"
IsVisible="{Binding !IsCoinJoin}" />
<PathIcon ToolTip.Tip="CoinJoin" Data="{StaticResource shield_regular}" Height="15" Opacity="0.6"
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<c:PrivacyContentControl PrivacyReplacementMode="Text" NumberOfPrivacyChars="15" Classes="monoSpacedPrivacyReplacement">
- <DockPanel LastChildFill="False" x:DataType="history:HistoryItemViewModel">
+ <DockPanel LastChildFill="False" x:DataType="historyItems:HistoryItemViewModelBase">
<c:TagsBox Padding="0"
Margin="0"
MinWidth="0"
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Panel Name="Panel" HorizontalAlignment="Stretch">
- <StackPanel x:DataType="history:HistoryItemViewModel"
+ <StackPanel x:DataType="historyItems:HistoryItemViewModelBase"
IsVisible="{Binding !!IncomingAmount}"
HorizontalAlignment="Right">
<PathIcon Data="{StaticResource arrow_down_right_regular}" Opacity="0.6">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Panel Name="Panel" HorizontalAlignment="Stretch">
- <StackPanel x:DataType="history:HistoryItemViewModel"
+ <StackPanel x:DataType="historyItems:HistoryItemViewModelBase"
IsVisible="{Binding !!OutgoingAmount}"
HorizontalAlignment="Right">
<PathIcon Data="{StaticResource arrow_up_right_regular}" Opacity="0.6">
SortMemberPath="Balance">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
- <c:PrivacyContentControl x:DataType="history:HistoryItemViewModel"
+ <c:PrivacyContentControl x:DataType="historyItems:HistoryItemViewModelBase"
Content="{Binding Balance, Converter={x:Static converters:MoneyConverters.ToFormattedString}}"
Classes="monoSpaced"
FontSize="15"
SortMemberPath="IsConfirmed">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
- <Panel x:DataType="history:HistoryItemViewModel">
+ <Panel x:DataType="historyItems:HistoryItemViewModelBase">
<!-- Confirmed -->
<StackPanel IsVisible="{Binding IsConfirmed}">
<PathIcon Data="{StaticResource checkmark_filled}"
CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
- <Button x:DataType="history:HistoryItemViewModel" Classes="plain" ToolTip.Tip="Show Details"
+ <Button x:DataType="historyItems:HistoryItemViewModelBase" Classes="plain" ToolTip.Tip="Show Details"
Command="{Binding ShowDetailsCommand}">
<PathIcon Data="{StaticResource details_regular}" Height="15" Opacity="0.6" />
</Button>

make difference between transaction and cj items