[Feature] Extend Input/Output Lists to TxPreview and TxDetails (#13507)

This commit is contained in:
Turbolay 2024-10-20 20:29:44 +02:00 коммит произвёл GitHub
Родитель 39aabb4825
Коммит 04667a8584
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
39 изменённых файлов: 891 добавлений и 34 удалений

Просмотреть файл

@ -6,4 +6,10 @@ public static class IntConverter
{
public static readonly IValueConverter ToOrdinalString =
new FuncValueConverter<int, string>(x => $"{x}.");
public static readonly IValueConverter IsNullOrZero =
new FuncValueConverter<int?, bool>(x => x is null or 0);
public static readonly IValueConverter FPlusConverter =
new FuncValueConverter<int, string>(x => x > 0 ? "+" + x : x.ToString());
}

Просмотреть файл

@ -115,6 +115,7 @@
<StreamGeometry x:Key="stats_wallet_regular">m 21.192982 12.984447 c 0.997413 0 1.805901 0.808529 1.805901 1.805902 v 6.390112 c 0 0.997377 -0.808488 1.805902 -1.805901 1.805902 h -7.501436 c -0.997373 0 -1.805902 -0.808525 -1.805902 -1.805902 v -6.390112 c 0 -0.997373 0.808529 -1.805902 1.805902 -1.805902 z m 0 0.833493 h -7.501436 c -0.537048 0 -0.972409 0.435361 -0.972409 0.972409 v 6.390112 c 0 0.537048 0.435361 0.972408 0.972409 0.972408 h 7.501436 c 0.537047 0 0.972409 -0.43536 0.972409 -0.972408 v -6.390112 c 0 -0.537048 -0.435362 -0.972409 -0.972409 -0.972409 z m -6.66886 1.111324 h 5.837756 c 0.230165 0 0.416747 0.186582 0.416747 0.416746 0 0.210985 -0.15678 0.385343 -0.360199 0.41294 l -0.05654 0.0038 H 14.52413 c -0.230165 0 -0.416746 -0.186583 -0.416746 -0.416746 0 -0.210985 0.156789 -0.385343 0.360198 -0.412941 l 0.05654 -0.0038 h 5.837756 z M 20.25 3.00001 c 0.4142 0 0.75 0.33579 0.75 0.75 v 4.99996 c 0 0.41422 -0.3358 0.75001 -0.75 0.75001 -0.4142 0 -0.75 -0.33579 -0.75 -0.75 V 5.56066 l -5.9697 5.96964 c -0.2929 0.2929 -0.7677 0.2929 -1.0606 0 L 10.25 9.31066 4.28033 15.2803 c -0.29289 0.2929 -0.76777 0.2929 -1.06066 0 -0.29289 -0.2929 -0.29289 -0.7677 0 -1.0606 l 6.5 -6.50003 c 0.29293 -0.29289 0.76773 -0.29289 1.06063 0 L 13 9.93934 18.4394 4.50001 15.25 4.5 C 14.8358 4.5 14.5 4.16421 14.5 3.75 14.5 3.33578 14.8358 3 15.25 3 Z</StreamGeometry>
<StreamGeometry x:Key="navigation_regular">M2.75254822,18 L21.2525482,18 C21.6667618,18 22.0025482,18.3357864 22.0025482,18.75 C22.0025482,19.1296958 21.7203943,19.443491 21.3543188,19.4931534 L21.2525482,19.5 L2.75254822,19.5 C2.33833466,19.5 2.00254822,19.1642136 2.00254822,18.75 C2.00254822,18.3703042 2.2847021,18.056509 2.65077766,18.0068466 L2.75254822,18 L21.2525482,18 L2.75254822,18 Z M2.75254822,11.5030063 L21.2525482,11.5030063 C21.6667618,11.5030063 22.0025482,11.8387927 22.0025482,12.2530063 C22.0025482,12.6327021 21.7203943,12.9464972 21.3543188,12.9961597 L21.2525482,13.0030063 L2.75254822,13.0030063 C2.33833466,13.0030063 2.00254822,12.6672199 2.00254822,12.2530063 C2.00254822,11.8733105 2.2847021,11.5595153 2.65077766,11.5098529 L2.75254822,11.5030063 L21.2525482,11.5030063 L2.75254822,11.5030063 Z M2.75168905,5.0032392 L21.251689,5.0032392 C21.6659026,5.0032392 22.001689,5.33902564 22.001689,5.7532392 C22.001689,6.13293497 21.7195352,6.44673016 21.3534596,6.49639258 L21.251689,6.5032392 L2.75168905,6.5032392 C2.33747549,6.5032392 2.00168905,6.16745276 2.00168905,5.7532392 C2.00168905,5.37354343 2.28384293,5.05974824 2.64991849,5.01008582 L2.75168905,5.0032392 L21.251689,5.0032392 L2.75168905,5.0032392 Z</StreamGeometry>
<StreamGeometry x:Key="arrow_left_regular">M12.7347,4.20949 C13.0332,3.92233 13.508,3.93153 13.7952,4.23005 C14.0823,4.52857 14.0731,5.00335 13.7746,5.29051 L5.50039,13.25 L24.2532,13.25 C24.6674,13.25 25.0032,13.5858 25.0032,13.9999982 C25.0032,14.4142 24.6674,14.75 24.2532,14.75 L5.50137,14.75 L13.7746,22.7085 C14.0731,22.9957 14.0823,23.4705 13.7952,23.769 C13.508,24.0675 13.0332,24.0767 12.7347,23.7896 L3.30673,14.7202 C2.89776,14.3268 2.89776,13.6723 3.30673,13.2788 L12.7347,4.20949 Z</StreamGeometry>
<StreamGeometry x:Key="arrow_right_regular">M1.5 6a.5.5 0 0 1 .5-.5h6.793L6.146 2.854a.5.5 0 1 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L8.793 6.5H2a.5.5 0 0 1-.5-.5</StreamGeometry>
<StreamGeometry x:Key="add_regular">M8.41687 7.57953V2.41851C8.41687 2.18743 8.22932 1.99988 7.99823 1.99988C7.76715 1.99988 7.5796 2.18743 7.5796 2.41851V7.57953H2.41863C2.18755 7.57953 2 7.76708 2 7.99816C2 8.22925 2.18755 8.41679 2.41863 8.41679H7.5796V13.5812C7.5796 13.8123 7.76715 13.9999 7.99823 13.9999C8.22932 13.9999 8.41687 13.8123 8.41687 13.5812V8.41679L13.5799 8.41851C13.811 8.41851 13.9985 8.23096 13.9985 7.99988C13.9985 7.76879 13.811 7.58125 13.5799 7.58125L8.41687 7.57953Z</StreamGeometry>
<StreamGeometry x:Key="recover_arrow_right_regular">M3 6.42589C3 3.98154 4.98154 2 7.42589 2H11.4092C11.6536 2 11.8518 2.19815 11.8518 2.44259C11.8518 2.68702 11.6536 2.88518 11.4092 2.88518H7.42589C5.47041 2.88518 3.88518 4.47041 3.88518 6.42589C3.88518 8.38137 5.47041 9.9666 7.42589 9.9666H12.1111L9.72185 7.57734C9.54901 7.4045 9.54901 7.12427 9.72185 6.95143C9.89469 6.77859 10.1749 6.77859 10.3478 6.95143L13.4804 10.0841C13.5995 10.2032 13.6365 10.3733 13.5915 10.5241C13.5741 10.6042 13.5342 10.6804 13.472 10.7427L10.3443 13.8704C10.1715 14.0432 9.89123 14.0432 9.71839 13.8704C9.54555 13.6975 9.54555 13.4173 9.71839 13.2445L12.1111 10.8518H7.42589C4.98154 10.8518 3 8.87024 3 6.42589Z</StreamGeometry>
<StreamGeometry x:Key="import_regular">M3.5 13H12.5C12.7761 13 13 13.2239 13 13.5C13 13.7455 12.8231 13.9496 12.5899 13.9919L12.5 14H3.5C3.22386 14 3 13.7761 3 13.5C3 13.2545 3.17688 13.0504 3.41012 13.0081L3.5 13H12.5H3.5ZM7.91012 1.00806L8 1C8.24546 1 8.44961 1.17688 8.49194 1.41012L8.5 1.5V10.292L11.182 7.61091C11.3555 7.43735 11.625 7.41806 11.8198 7.55306L11.8891 7.61091C12.0627 7.78448 12.0819 8.0539 11.9469 8.24877L11.8891 8.31802L8.35355 11.8536C8.17999 12.0271 7.91056 12.0464 7.71569 11.9114L7.64645 11.8536L4.11091 8.31802C3.91565 8.12276 3.91565 7.80617 4.11091 7.61091C4.28448 7.43735 4.5539 7.41806 4.74877 7.55306L4.81802 7.61091L7.5 10.292V1.5C7.5 1.25454 7.67688 1.05039 7.91012 1.00806L8 1L7.91012 1.00806Z</StreamGeometry>

Просмотреть файл

@ -40,8 +40,8 @@ public partial class TransactionModel : ReactiveObject
public Lazy<IReadOnlyCollection<OutPoint>> ForeignInputs => new(ForeignInputsFunction());
public required IReadOnlyCollection<SmartCoin> WalletInputs { get; set; }
public required Func<IReadOnlyCollection<OutPoint>> ForeignOutputsFunction{ get; set; }
public Lazy<IReadOnlyCollection<OutPoint>> ForeignOutputs => new(ForeignOutputsFunction());
public required Func<IReadOnlyCollection<IndexedTxOut>> ForeignOutputsFunction{ get; set; }
public Lazy<IReadOnlyCollection<IndexedTxOut>> ForeignOutputs => new(ForeignOutputsFunction());
public required IReadOnlyCollection<SmartCoin> WalletOutputs { get; set; }
public required Money Amount { get; set; }

Просмотреть файл

@ -13,8 +13,8 @@ public static class CoinjoinCoinListDataGridSource
public static HierarchicalTreeDataGridSource<CoinjoinCoinListItem> Create(IEnumerable<CoinjoinCoinListItem> source)
{
// [Column] [View] [Header] [Width] [MinWidth] [MaxWidth] [CanUserSort]
// AnonymityScore AnonymityColumnView <custom> 50 - - true
// Amount AmountColumnView Amount Auto - - true
// AnonymityScore AnonymityColumnView <custom> 3 Star - - true
// Amount AmountColumnView Amount 8 Star - - true
var result = new HierarchicalTreeDataGridSource<CoinjoinCoinListItem>(source)
{
Columns =

Просмотреть файл

@ -36,8 +36,16 @@ public partial class CoinJoinsDetailsViewModel : RoutableViewModel
var freshWalletInputs = allWalletInputs.Where(x => !allWalletOutputs.Select(y => y.Outpoint).Contains(x.Outpoint)).OrderByDescending(x => x.Amount).ToList();
var finalWalletOutputs = allWalletOutputs.Where(x => !allWalletInputs.Select(y => y.Outpoint).Contains(x.Outpoint)).OrderByDescending(x => x.Amount).ToList();
var allInputs = transaction.ForeignInputs.Value.Union(transaction.Children.SelectMany(x => x.ForeignInputs.Value)).ToList();
var allOutputs = transaction.ForeignOutputs.Value.Union(transaction.Children.SelectMany(x => x.ForeignOutputs.Value)).ToList();
var allInputs = transaction.ForeignInputs.Value
.Union(transaction.Children.SelectMany(x => x.ForeignInputs.Value))
.Union(allWalletInputs.Select(x => x.Outpoint))
.ToList();
var allOutputs = transaction.ForeignOutputs.Value.Select(x => new OutPoint(x.Transaction.GetHash(), x.N))
.Union(transaction.Children.SelectMany(x => x.ForeignOutputs.Value.Select(y => new OutPoint(y.Transaction.GetHash(), y.N))))
.Union(allWalletOutputs.Select(x => x.Outpoint))
.ToList();
var freshInputs = allInputs.Where(x => !allOutputs.Contains(x));
var finalOutputs = allOutputs.Where(x => !allInputs.Contains(x));

Просмотреть файл

@ -11,6 +11,8 @@ using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.Models.UI;
using WalletWasabi.Fluent.ViewModels.Navigation;
using WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs;
using WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History.Details;
@ -38,6 +40,11 @@ public partial class TransactionDetailsViewModel : RoutableViewModel
UiContext = uiContext;
_wallet = wallet;
InputList = new InputsCoinListViewModel(model.WalletInputs, model.WalletInputs.Count + model.ForeignInputs.Value.Count);
OutputList = new OutputsCoinListViewModel(
model.WalletOutputs.Select(x => x.TxOut).ToList(),
model.ForeignOutputs.Value.Select(x => x.TxOut).ToList());
NextCommand = ReactiveCommand.Create(OnNext);
Fee = wallet.AmountProvider.Create(model.Fee);
IsFeeVisible = model.Fee != null;
@ -50,6 +57,9 @@ public partial class TransactionDetailsViewModel : RoutableViewModel
Task.Run(() => UpdateValuesAsync(model, CancellationToken.None));
}
public InputsCoinListViewModel InputList { get; }
public OutputsCoinListViewModel OutputList { get; }
public BitcoinAddress? SingleAddress { get; set; }
public uint256 TransactionId { get; }

Просмотреть файл

@ -1,9 +1,12 @@
using System.Linq;
using NBitcoin;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Blockchain.TransactionBuilding;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs;
using WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs;
using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
@ -24,6 +27,8 @@ public partial class TransactionSummaryViewModel : ViewModelBase
[AutoNotify] private FeeRate? _feeRate;
[AutoNotify] private double? _amountDiff;
[AutoNotify] private double? _feeDiff;
[AutoNotify] private InputsCoinListViewModel? _inputList;
[AutoNotify] private OutputsCoinListViewModel? _outputList;
private TransactionSummaryViewModel(TransactionPreviewViewModel parent, IWalletModel wallet, TransactionInfo info, bool isPreview = false)
{
@ -52,11 +57,26 @@ public partial class TransactionSummaryViewModel : ViewModelBase
ConfirmationTime = _wallet.Transactions.TryEstimateConfirmationTime(info);
var destinationAmount = _transaction.CalculateDestinationAmount(info.Destination);
var destinationIndexedTxOut =
transactionResult.Transaction.ForeignOutputs.Select(x => x.TxOut)
.Union(transactionResult.Transaction.WalletOutputs.Select(x => x.TxOut))
.FirstOrDefault(x => x.ScriptPubKey == info.Destination.ScriptPubKey);
Amount = UiContext.AmountProvider.Create(destinationAmount);
Fee = UiContext.AmountProvider.Create(_transaction.Fee);
FeeRate = info.FeeRate;
InputList = new InputsCoinListViewModel(transactionResult.Transaction.WalletInputs,
transactionResult.Transaction.WalletInputs.Count + transactionResult.Transaction.ForeignInputs.Count,
Parent.CurrentTransactionSummary.InputList?.TreeDataGridSource.Items.First().IsExpanded,
!IsPreview ? null : Parent.CurrentTransactionSummary.InputList?.TreeDataGridSource.Items.First().Children.Count);
OutputList = new OutputsCoinListViewModel(transactionResult.Transaction.WalletOutputs.Select(x => x.TxOut).ToList(),
transactionResult.Transaction.ForeignOutputs.Select(x => x.TxOut).ToList(),
destinationIndexedTxOut?.ScriptPubKey,
Parent.CurrentTransactionSummary.OutputList?.TreeDataGridSource.Items.First().IsExpanded,
!IsPreview ? null : Parent.CurrentTransactionSummary.OutputList?.TreeDataGridSource.Items.First().Children.Count);
Recipient = info.Recipient;
IsCustomFeeUsed = info.IsCustomFeeUsed;
IsOtherPocketSelectionPossible = info.IsOtherPocketSelectionPossible;

Просмотреть файл

@ -0,0 +1,60 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Controls.Templates;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Cells;
using WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Columns;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs;
public static class InputsCoinListDataGridSource
{
public static HierarchicalTreeDataGridSource<InputsCoinListItem> Create(IEnumerable<InputsCoinListItem> source)
{
// [Column] [View] [Header] [Width] [MinWidth] [MaxWidth] [CanUserSort]
// AnonymityScore AnonymityColumnView <custom> 3 Star - - true
// Amount AmountColumnView Amount 10 Star - - true
var result = new HierarchicalTreeDataGridSource<InputsCoinListItem>(source)
{
Columns =
{
AnonymityScoreColumn(),
AmountColumn(),
}
};
return result;
}
private static IColumn<InputsCoinListItem> AmountColumn()
{
return new TemplateColumn<InputsCoinListItem>(
null,
new FuncDataTemplate<InputsCoinListItem>((item, _) => item is { IsChild: true } ? new AmountCellView() : new TextCellView()),
null,
new GridLength(10, GridUnitType.Star),
new TemplateColumnOptions<InputsCoinListItem>
{
CompareAscending = Sort<InputsCoinListItem>.Ascending(x => x.Amount),
CompareDescending = Sort<InputsCoinListItem>.Descending(x => x.Amount)
});
}
private static IColumn<InputsCoinListItem> AnonymityScoreColumn()
{
return new HierarchicalExpanderColumn<InputsCoinListItem>(
new TemplateColumn<InputsCoinListItem>(
null,
new FuncDataTemplate<InputsCoinListItem>((_, _) => new AnonymityScoreColumnView(), true),
null,
new GridLength(3, GridUnitType.Star),
new TemplateColumnOptions<InputsCoinListItem>
{
CompareAscending = Sort<InputsCoinListItem>.Ascending(b => b.AnonymityScore),
CompareDescending = Sort<InputsCoinListItem>.Descending(b => b.AnonymityScore)
}),
group => group.Children,
node => true,
node => node.IsExpanded);
}
}

Просмотреть файл

@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.TreeDataGrid;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs;
public abstract partial class InputsCoinListItem : ViewModelBase, ITreeDataGridExpanderItem, IDisposable
{
protected readonly CompositeDisposable _disposables = new();
[AutoNotify] private bool _isParentPointerOver;
[AutoNotify] private bool _isControlPointerOver;
[AutoNotify] private bool _isExpanded;
protected InputsCoinListItem()
{
this.WhenAnyValue(x => x.IsControlPointerOver)
.Do(x =>
{
foreach (var child in Children)
{
child.IsParentPointerOver = x;
}
})
.Subscribe();
this.WhenAnyValue(x => x.IsExpanded)
.Do(x =>
{
foreach (var child in Children)
{
child.IsExpanded = x;
}
})
.Subscribe();
}
public Amount Amount { get; protected set; } = new(Money.Zero);
public int? AnonymityScore { get; protected set; }
public int? TotalInputs { get; set; }
public IReadOnlyCollection<InputsCoinViewModel> Children { get; protected set; } = new List<InputsCoinViewModel>();
public bool HasChildren => Children.Count > 0;
public bool IsChild { get; set; }
public bool IsLastChild { get; set; }
public bool IsParentSelected { get; set; } = false;
public string TitleText { get; set; }
public int? NbDiff { get; set; }
public string? Tip { get; set; }
public void Dispose() => _disposables.Dispose();
}

Просмотреть файл

@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Controls;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.ViewModels.Wallets.Coinjoins;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs;
public class InputsCoinListViewModel : ViewModelBase, IDisposable
{
private readonly CompositeDisposable _disposables = new();
public InputsCoinListViewModel(IEnumerable<SmartCoin> availableCoins, int inputCount, bool? isExpanded = null, int? oldInputCount = null)
{
var coinItems = availableCoins.OrderByDescending(x => x.Amount).Select(x => new InputsCoinViewModel(x)).ToList();
foreach (var coin in coinItems)
{
coin.IsChild = true;
}
if (coinItems.Count > 0)
{
coinItems.Last().IsLastChild = true;
}
if (oldInputCount is not null)
{
NbDiff = inputCount - oldInputCount;
}
var parentItem = new InputsCoinViewModel(coinItems.ToArray(), inputCount, isExpanded.GetValueOrDefault(), NbDiff);
coinItems.Insert(0, parentItem);
_disposables.Add(parentItem);
TreeDataGridSource = InputsCoinListDataGridSource.Create(new List<InputsCoinListItem> { parentItem });
TreeDataGridSource.DisposeWith(_disposables);
}
public int? NbDiff { get; }
public HierarchicalTreeDataGridSource<InputsCoinListItem> TreeDataGridSource { get; }
public void Dispose()
{
_disposables.Dispose();
}
}

Просмотреть файл

@ -0,0 +1,47 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Fluent.Models.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs;
public class InputsCoinViewModel : InputsCoinListItem
{
public InputsCoinViewModel(SmartCoin coin)
{
Coin = coin;
Amount = new Amount(coin.Amount);
if(coin.HdPubKey.HistoricalAnonSet.TryGetValue(coin.Outpoint.Hash, out var anonSetWhenTxProcessed))
{
AnonymityScore = (int)anonSetWhenTxProcessed;
}
else
{
AnonymityScore = (int)coin.AnonymitySet;
}
}
public InputsCoinViewModel(InputsCoinViewModel[] coins, int inputCount, bool isExpanded, int? nbDiff)
{
Amount = new Amount(coins.Sum(x => x.Amount.Btc));
Children = coins;
TotalInputs = inputCount;
IsExpanded = isExpanded;
NbDiff = nbDiff;
if (IsExpanded)
{
foreach (var coin in Children)
{
coin.IsExpanded = true;
}
}
TitleText = $"{TotalInputs} input{(TotalInputs == 1 ? "" : "s")}";
Tip = Children.Count == TotalInputs ?
"All inputs belong to one of your opened wallets" :
"Only inputs belonging to one of your opened wallets can be shown.";
}
public SmartCoin? Coin { get; }
}

Просмотреть файл

@ -0,0 +1,55 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Controls.Templates;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Cells;
using WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Columns;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs;
public static class OutputsCoinListDataGridSource
{
public static HierarchicalTreeDataGridSource<OutputsCoinListItem> Create(IEnumerable<OutputsCoinListItem> source)
{
// [Column] [View] [Header] [Width] [MinWidth] [MaxWidth] [CanUserSort]
// ChangeIndicator ChangeIndicatorView <custom> 3 Star - - false
// Amount AmountColumnView Amount 10 Star - - true
var result = new HierarchicalTreeDataGridSource<OutputsCoinListItem>(source)
{
Columns =
{
ChangeIndicatorColumn(),
AmountColumn(),
}
};
return result;
}
private static IColumn<OutputsCoinListItem> AmountColumn()
{
return new TemplateColumn<OutputsCoinListItem>(
null,
new FuncDataTemplate<OutputsCoinListItem>((item, _) => item is { IsChild: true } ? new AmountCellView() : new TextCellView()),
null,
new GridLength(10, GridUnitType.Star),
new TemplateColumnOptions<OutputsCoinListItem>
{
CompareAscending = Sort<OutputsCoinListItem>.Ascending(x => x.Amount),
CompareDescending = Sort<OutputsCoinListItem>.Descending(x => x.Amount)
});
}
private static IColumn<OutputsCoinListItem> ChangeIndicatorColumn()
{
return new HierarchicalExpanderColumn<OutputsCoinListItem>(
new TemplateColumn<OutputsCoinListItem>(
null,
new FuncDataTemplate<OutputsCoinListItem>((_, _) => new IsOwnIndicatorView(), true),
null,
new GridLength(3, GridUnitType.Star)),
group => group.Children,
node => node.HasChildren,
node => node.IsExpanded);
}
}

Просмотреть файл

@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.TreeDataGrid;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs;
public abstract partial class OutputsCoinListItem : ViewModelBase, ITreeDataGridExpanderItem, IDisposable
{
protected readonly CompositeDisposable _disposables = new();
[AutoNotify] private bool _isParentPointerOver;
[AutoNotify] private bool _isControlPointerOver;
[AutoNotify] private bool _isExpanded;
protected OutputsCoinListItem()
{
this.WhenAnyValue(x => x.IsControlPointerOver)
.Do(x =>
{
foreach (var child in Children)
{
child.IsParentPointerOver = x;
}
})
.Subscribe();
this.WhenAnyValue(x => x.IsExpanded)
.Do(x =>
{
foreach (var child in Children)
{
child.IsExpanded = x;
}
})
.Subscribe();
}
public Amount Amount { get; protected set; } = new(Money.Zero);
public bool ShowOwn { get; protected set; }
public bool ShowChange { get; protected set; }
public int? TotalOutputs { get; set; }
public IReadOnlyCollection<OutputsCoinViewModel> Children { get; protected set; } = new List<OutputsCoinViewModel>();
public bool HasChildren => Children.Count > 0;
public bool IsChild { get; set; }
public bool IsLastChild { get; set; }
public bool IsParentSelected { get; set; } = false;
public string TitleText { get; set; }
public int? NbDiff { get; set; }
public void Dispose() => _disposables.Dispose();
}

Просмотреть файл

@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Controls;
using NBitcoin;
using WalletWasabi.Fluent.Models.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs;
public class OutputsCoinListViewModel : ViewModelBase, IDisposable
{
private readonly CompositeDisposable _disposables = new();
public OutputsCoinListViewModel(List<TxOut> ownOutputs, List<TxOut> foreignOutputs, Script? destinationScript = null, bool? isExpanded = null, int? oldOutputCount = null)
{
var outputCount = ownOutputs.Count + foreignOutputs.Count;
var coinItems = ownOutputs
.Union(foreignOutputs)
.OrderByDescending(x => x.Value)
.Select(x =>
new OutputsCoinViewModel(
x,
ownOutputs.Contains(x),
destinationScript is not null && x.ScriptPubKey != destinationScript))
.ToList();
foreach (var coin in coinItems)
{
coin.IsChild = true;
}
if (coinItems.Count > 0)
{
coinItems.Last().IsLastChild = true;
}
if (oldOutputCount is not null)
{
NbDiff = outputCount - oldOutputCount;
}
var parentItem = new OutputsCoinViewModel(coinItems.ToArray(), outputCount, isExpanded.GetValueOrDefault(), NbDiff);
coinItems.Insert(0, parentItem);
_disposables.Add(parentItem);
TreeDataGridSource = OutputsCoinListDataGridSource.Create(new List<OutputsCoinListItem> { parentItem });
TreeDataGridSource.DisposeWith(_disposables);
}
public int? NbDiff { get; }
public HierarchicalTreeDataGridSource<OutputsCoinListItem> TreeDataGridSource { get; }
public void Dispose()
{
_disposables.Dispose();
}
}

Просмотреть файл

@ -0,0 +1,34 @@
using System.Linq;
using NBitcoin;
using WalletWasabi.Fluent.Models.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs;
public class OutputsCoinViewModel : OutputsCoinListItem
{
public OutputsCoinViewModel(TxOut txOut, bool isOwn, bool isChange)
{
TxOut = txOut;
Amount = new Amount(txOut.Value);
ShowChange = isChange;
ShowOwn = !isChange && isOwn;
}
public OutputsCoinViewModel(OutputsCoinViewModel[] coins, int outputCount, bool isExpanded, int? nbDiff)
{
Amount = new Amount(coins.Sum(x => x.Amount.Btc));
Children = coins;
TotalOutputs = outputCount;
IsExpanded = isExpanded;
if (IsExpanded)
{
foreach (var coin in Children)
{
coin.IsExpanded = true;
}
}
TitleText = $"{Children.Count} output{(Children.Count == 1 ? "" : "s")}";
NbDiff = nbDiff;
}
public TxOut? TxOut { get; }
}

Просмотреть файл

@ -12,7 +12,8 @@
VerticalAlignment="Center"
HorizontalAlignment="Center"
UseOpacity="True"
MaxPrivacyChars="14">
MaxPrivacyChars="14"
Margin="10 0 0 0">
<AmountControl MinWidth="140" Amount="{Binding Amount}"/>
</PrivacyContentControl>
</UserControl>

Просмотреть файл

@ -8,5 +8,5 @@
x:DataType="coinjoins:CoinjoinCoinViewModel"
x:CompileBindings="True">
<TextBlock Margin="-57 0 0 0" HorizontalAlignment="Center" VerticalAlignment="Center" Classes="bold" Text="{Binding TitleText}"/>
<TextBlock Margin="-52 0 0 0" HorizontalAlignment="Center" VerticalAlignment="Center" Classes="bold" Text="{Binding TitleText}"/>
</UserControl>

Просмотреть файл

@ -51,20 +51,15 @@
<Separator />
<PreviewItem Icon="{StaticResource input_count}" />
<DockPanel Margin="0 -35 0 0" LastChildFill="True">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,30,*">
<StackPanel Grid.Column="0">
<TextBlock Margin="-5 0 0 0" Text="Inputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="15 15 25 0" DataContext="{Binding InputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<TextBlock Margin="35 0 25 0" Text="Inputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="10 15 10 0" DataContext="{Binding InputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Margin="-5 0 0 0" Text="Outputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="15 15 25 0" DataContext="{Binding OutputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<PathIcon Grid.Column="1" Opacity="0.6" Margin="0 48 0 0" VerticalAlignment="Top" Data="{StaticResource arrow_right_regular}"/>
<StackPanel Grid.Column="2">
<TextBlock Margin="35 0 25 0" Text="Outputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="10 15 10 0" DataContext="{Binding OutputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>
</Grid>
</DockPanel>

Просмотреть файл

@ -43,20 +43,15 @@
<Separator />
<PreviewItem Icon="{StaticResource input_count}" />
<DockPanel Margin="0 -35 0 0" LastChildFill="True">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,30,*">
<StackPanel Grid.Column="0">
<TextBlock Margin="-5 0 0 0" Text="Inputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="15 15 25 0" DataContext="{Binding InputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<TextBlock Margin="35 0 25 0" Text="Inputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="10 15 10 0" DataContext="{Binding InputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Margin="-5 0 0 0" Text="Outputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="15 15 25 0" DataContext="{Binding OutputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<PathIcon Grid.Column="1" Opacity="0.6" Margin="0 48 0 0" VerticalAlignment="Top" Data="{StaticResource arrow_right_regular}"/>
<StackPanel Grid.Column="2">
<TextBlock Margin="35 0 25 0" Text="Outputs" FontSize="14" FontWeight="Bold" HorizontalAlignment="Center"/>
<coinjoins:CoinjoinCoinListView Margin="10 15 10 0" DataContext="{Binding OutputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>
</Grid>
</DockPanel>

Просмотреть файл

@ -4,6 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:details="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Home.History.Details"
xmlns:conv="clr-namespace:WalletWasabi.Fluent.Converters"
xmlns:inputs="clr-namespace:WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs"
xmlns:outputs="clr-namespace:WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="details:TransactionDetailsViewModel"
x:CompileBindings="True"
@ -113,6 +115,22 @@
</PrivacyContentControl>
</PreviewItem>
<!-- Inputs/Outputs -->
<Separator />
<PreviewItem Icon="{StaticResource input_count}" />
<DockPanel Margin="30 -30 0 0" LastChildFill="True">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<inputs:InputsCoinListView Grid.Column="0" Margin="10 -15 10 0" DataContext="{Binding InputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<PathIcon Grid.Column="1" Opacity="0.6" Margin="0 2 0 0" VerticalAlignment="Top" Data="{StaticResource arrow_right_regular}"/>
<outputs:OutputsCoinListView Grid.Column="2" Margin="10 -15 10 0" DataContext="{Binding OutputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</DockPanel>
<Separator IsVisible="{Binding IsConfirmed}" />
<!-- Block hash -->

Просмотреть файл

@ -81,7 +81,7 @@
<ShowFlyoutOnPointerOverBehavior />
</Interaction.Behaviors>
<FlyoutBase.AttachedFlyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Flyout Placement="BottomEdgeAlignedLeft" ShowMode="TransientWithDismissOnPointerMoveAway">
<Panel DataContext="{Binding PrivacySuggestions}" Margin="0 5 0 0">
<!-- Warnings -->
<send:PrivacyWarningsView IsVisible="{Binding !MaxPrivacy}" />

Просмотреть файл

@ -4,6 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:send="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Send"
xmlns:converters="clr-namespace:WalletWasabi.Fluent.Converters"
xmlns:outputs="clr-namespace:WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs"
xmlns:inputs="clr-namespace:WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:CompileBindings="True"
x:DataType="send:TransactionSummaryViewModel"
@ -79,6 +81,18 @@
HorizontalAlignment="Left"
VerticalAlignment="Center" />
</PreviewItem>
<!-- Inputs/Outputs -->
<Separator />
<PreviewItem Icon="{StaticResource input_count}" />
<DockPanel Margin="30 -30 0 0" LastChildFill="True">
<Grid ColumnDefinitions="*,30,*">
<inputs:InputsCoinListView Grid.Column="0" Margin="10 -15 10 0" DataContext="{Binding InputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<PathIcon Grid.Column="1" Opacity="0.6" Margin="0 2 0 0" VerticalAlignment="Top" Data="{StaticResource arrow_right_regular}"/>
<outputs:OutputsCoinListView Grid.Column="2" Margin="10 -15 10 0" DataContext="{Binding OutputList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</DockPanel>
<Separator />
<!-- Fee and confirmation time -->

Просмотреть файл

@ -0,0 +1,19 @@
<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:inputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Cells.AmountCellView"
x:DataType="inputs:InputsCoinListItem"
x:CompileBindings="True">
<PrivacyContentControl PrivacyReplacementMode="Text"
VerticalAlignment="Center"
HorizontalAlignment="Center"
UseOpacity="True"
MaxPrivacyChars="14"
Margin="5 0 0 0">
<AmountControl MinWidth="140" Amount="{Binding Amount}"/>
</PrivacyContentControl>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Cells;
public class AmountCellView : UserControl
{
public AmountCellView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -0,0 +1,40 @@
<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:inputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs"
xmlns:converters="clr-namespace:WalletWasabi.Fluent.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Cells.TextCellView"
x:DataType="inputs:InputsCoinViewModel"
x:CompileBindings="True">
<UserControl.Resources>
<converters:DiffToBrushConverter x:Key="InvertedDiffConverter"
PositiveBrush="{StaticResource UncertainBrush}"
NegativeBrush="{StaticResource PositiveBrush}"
ZeroBrush="{StaticResource UncertainBrush}" />
</UserControl.Resources>
<StackPanel Orientation="Horizontal" ToolTip.Tip="{Binding Tip, FallbackValue=''}">
<TextBlock IsVisible="{Binding !!NbDiff}"
FontWeight="Bold"
Margin="45 0 0 0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock.Inlines>
<Run Text="{Binding TitleText}" />
<Run Text=" " />
<Run Foreground="{Binding NbDiff, Converter={StaticResource InvertedDiffConverter}}"
Text="{Binding NbDiff, Converter={x:Static converters:IntConverter.FPlusConverter}}" />
</TextBlock.Inlines>
</TextBlock>
<TextBlock IsVisible="{Binding NbDiff, Converter={x:Static converters:IntConverter.IsNullOrZero}}"
Margin="45 0 0 0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Classes="bold"
Text="{Binding TitleText}">
</TextBlock>
</StackPanel>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Cells;
public class TextCellView : UserControl
{
public TextCellView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -0,0 +1,14 @@
<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:inputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="inputs:InputsCoinListItem"
x:CompileBindings="True"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Columns.AnonymityScoreColumnView">
<StackPanel Margin="10 0 0 0" ToolTip.Tip="Anonymity Score" IsVisible="{Binding !!AnonymityScore}" Orientation="Horizontal" HorizontalAlignment="Right">
<PathIcon Data="{StaticResource anonscore}" VerticalAlignment="Center" Opacity="0.6" Height="15"/>
<TextBlock Text="{Binding AnonymityScore}" VerticalAlignment="Center" FontWeight="Bold" />
</StackPanel>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.Columns;
public class AnonymityScoreColumnView : UserControl
{
public AnonymityScoreColumnView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -0,0 +1,37 @@
<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:inputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Inputs"
xmlns:treeDataGrid="clr-namespace:WalletWasabi.Fluent.TreeDataGrid"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs.InputsCoinListView"
x:DataType="inputs:InputsCoinListViewModel"
x:CompileBindings="True">
<Panel>
<TreeDataGrid x:Name="TreeDataGrid" Source="{Binding TreeDataGridSource}">
<TreeDataGrid.Styles>
<Style Selector="treeDataGrid|TreeDataGridAmountPrivacyTextCell">
<Setter Property="FontFamily" Value="{StaticResource MonospacedFont}" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Opacity" Value="1" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource TextForegroundColor}" />
</Style>
<Style Selector="TreeDataGridExpanderCell">
<Setter Property="Theme" Value="{StaticResource CoinjoinCoinsListViewTreeDataGridExpanderCell}" />
</Style>
<Style Selector="TreeDataGridRow" x:DataType="inputs:InputsCoinListItem">
<Setter Property="Theme" Value="{StaticResource CoinjoinCoinsListViewTreeDataGridRow}" />
</Style>
</TreeDataGrid.Styles>
<Interaction.Behaviors>
<SetLastChildBehavior />
</Interaction.Behaviors>
<TreeDataGrid.ElementFactory>
<treeDataGrid:PrivacyElementFactory />
</TreeDataGrid.ElementFactory>
</TreeDataGrid>
</Panel>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Inputs;
public class InputsCoinListView : UserControl
{
public InputsCoinListView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -0,0 +1,19 @@
<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:outputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Cells.AmountCellView"
x:DataType="outputs:OutputsCoinListItem"
x:CompileBindings="True">
<PrivacyContentControl PrivacyReplacementMode="Text"
VerticalAlignment="Center"
HorizontalAlignment="Center"
UseOpacity="True"
MaxPrivacyChars="14"
Margin="5 0 0 0">
<AmountControl MinWidth="140" Amount="{Binding Amount}"/>
</PrivacyContentControl>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Cells;
public class AmountCellView : UserControl
{
public AmountCellView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -0,0 +1,39 @@
<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:outputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs"
xmlns:converters="clr-namespace:WalletWasabi.Fluent.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Cells.TextCellView"
x:DataType="outputs:OutputsCoinViewModel"
x:CompileBindings="True">
<UserControl.Resources>
<converters:DiffToBrushConverter x:Key="InvertedDiffConverter"
PositiveBrush="{StaticResource UncertainBrush}"
NegativeBrush="{StaticResource PositiveBrush}"
ZeroBrush="{StaticResource UncertainBrush}" />
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<TextBlock IsVisible="{Binding !!NbDiff}"
FontWeight="Bold"
Margin="45 0 0 0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock.Inlines>
<Run Text="{Binding TitleText}" />
<Run Text=" " />
<Run Foreground="{Binding NbDiff, Converter={StaticResource InvertedDiffConverter}}"
Text="{Binding NbDiff, Converter={x:Static converters:IntConverter.FPlusConverter}}" />
</TextBlock.Inlines>
</TextBlock>
<TextBlock IsVisible="{Binding NbDiff, Converter={x:Static converters:IntConverter.IsNullOrZero}}"
Margin="45 0 0 0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Classes="bold"
Text="{Binding TitleText}">
</TextBlock>
</StackPanel>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Cells;
public class TextCellView : UserControl
{
public TextCellView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
<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:outputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="outputs:OutputsCoinListItem"
x:CompileBindings="True"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Columns.IsOwnIndicatorView">
<InvalidatingStackPanel Orientation="Horizontal" Spacing="2" HorizontalAlignment="Right">
<Border BorderThickness="1"
ToolTip.Tip="This output belongs to one of your opened wallets"
VerticalAlignment="Center"
Margin="0 0 8 0"
IsVisible="{Binding ShowOwn}"
BorderBrush="{DynamicResource TextControlForeground}" Opacity="0.6" CornerRadius="4" Padding="3">
<TextBlock Classes="h10" Text="OWN"/>
</Border>
<Border BorderThickness="1"
ToolTip.Tip="This output goes back to your own wallet"
VerticalAlignment="Center"
IsVisible="{Binding ShowChange}"
BorderBrush="{DynamicResource TextControlForeground}" Opacity="0.6" CornerRadius="4" Padding="3">
<TextBlock Classes="h10" Text="CHANGE"/>
</Border>
</InvalidatingStackPanel>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.Columns;
public class IsOwnIndicatorView : UserControl
{
public IsOwnIndicatorView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -0,0 +1,37 @@
<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:outputs="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Transactions.Outputs"
xmlns:treeDataGrid="clr-namespace:WalletWasabi.Fluent.TreeDataGrid"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs.OutputsCoinListView"
x:DataType="outputs:OutputsCoinListViewModel"
x:CompileBindings="True">
<Panel>
<TreeDataGrid x:Name="TreeDataGrid" Source="{Binding TreeDataGridSource}">
<TreeDataGrid.Styles>
<Style Selector="treeDataGrid|TreeDataGridAmountPrivacyTextCell">
<Setter Property="FontFamily" Value="{StaticResource MonospacedFont}" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Opacity" Value="1" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource TextForegroundColor}" />
</Style>
<Style Selector="TreeDataGridExpanderCell">
<Setter Property="Theme" Value="{StaticResource CoinjoinCoinsListViewTreeDataGridExpanderCell}" />
</Style>
<Style Selector="TreeDataGridRow" x:DataType="outputs:OutputsCoinListItem">
<Setter Property="Theme" Value="{StaticResource CoinjoinCoinsListViewTreeDataGridRow}" />
</Style>
</TreeDataGrid.Styles>
<Interaction.Behaviors>
<SetLastChildBehavior />
</Interaction.Behaviors>
<TreeDataGrid.ElementFactory>
<treeDataGrid:PrivacyElementFactory />
</TreeDataGrid.ElementFactory>
</TreeDataGrid>
</Panel>
</UserControl>

Просмотреть файл

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.Wallets.Transactions.Outputs;
public class OutputsCoinListView : UserControl
{
public OutputsCoinListView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

Просмотреть файл

@ -22,7 +22,7 @@ public class TransactionSummary
public Func<IReadOnlyCollection<OutPoint>> ForeignInputs => () => Transaction.ForeignInputs.Select(x => x.PrevOut).ToArray();
public IReadOnlyCollection<SmartCoin> WalletInputs => Transaction.WalletInputs;
public Func<IReadOnlyCollection<OutPoint>> ForeignOutputs => () => Transaction.ForeignOutputs.Select(x => new OutPoint(GetHash(), x.N)).ToArray();
public Func<IReadOnlyCollection<IndexedTxOut>> ForeignOutputs => () => Transaction.ForeignOutputs;
public IReadOnlyCollection<SmartCoin> WalletOutputs => Transaction.WalletOutputs;
public DateTimeOffset FirstSeen => Transaction.FirstSeen;
public LabelsArray Labels => Transaction.Labels;