Implement QR Code export.
MODIFY
WalletWasabi.Gui/Controls/QrCode.cs
WalletWasabi.Gui/Controls/QrCode.cs
@@ -7,6 +7,7 @@ using Avalonia.Platform;
using ReactiveUI;
using System;
using System.Reactive.Linq;
+using WalletWasabi.Helpers;
namespace WalletWasabi.Gui.Controls
{
@@ -29,7 +30,37 @@ namespace WalletWasabi.Gui.Controls
this.WhenAnyValue(x => x.Matrix)
.Where(x => x != null)
.ObserveOn(RxApp.MainThreadScheduler)
- .Subscribe(x => FinalMatrix = AddPaddingToMatrix(x));
+ .Do(x => FinalMatrix = AddPaddingToMatrix(x))
+ .Subscribe();
+
+ this.WhenAnyValue(x => x.QRImageSavePath)
+ .Where(x => !string.IsNullOrWhiteSpace(x) || !string.IsNullOrEmpty(x))
+ .Where(x => !(FinalMatrix is null))
+ .Subscribe(x => GenerateQRCodeBitmap(x));
+ }
+
+ private void GenerateQRCodeBitmap(string x)
+ {
+ var pixSize = PixelSize.FromSize(CoercedSize, 1);
+ using (var rtb = new RenderTargetBitmap(pixSize))
+ {
+ rtb.Render(this);
+ rtb.Save(x);
+ }
+ }
+
+ public static readonly DirectProperty<QrCode, string> QRImageSavePathProperty =
+ AvaloniaProperty.RegisterDirect<QrCode, string>(
+ nameof(QRImageSavePath),
+ o => o.QRImageSavePath,
+ (o, v) => o.QRImageSavePath = v);
+
+ private string _qRImageSavePath;
+
+ public string QRImageSavePath
+ {
+ get => _qRImageSavePath;
+ set => SetAndRaise(QRImageSavePathProperty, ref _qRImageSavePath, value);
}
public static readonly DirectProperty<QrCode, bool[,]> MatrixProperty =

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml
<Path HorizontalAlignment="Left" Data="M16.803,18.615h-4.535c-1,0-1.814-0.812-1.814-1.812v-4.535c0-1.002,0.814-1.814,1.814-1.814h4.535c1.001,0,1.813,0.812,1.813,1.814v4.535C18.616,17.803,17.804,18.615,16.803,18.615zM17.71,12.268c0-0.502-0.405-0.906-0.907-0.906h-4.535c-0.501,0-0.906,0.404-0.906,0.906v4.535c0,0.502,0.405,0.906,0.906,0.906h4.535c0.502,0,0.907-0.404,0.907-0.906V12.268z M16.803,9.546h-4.535c-1,0-1.814-0.812-1.814-1.814V3.198c0-1.002,0.814-1.814,1.814-1.814h4.535c1.001,0,1.813,0.812,1.813,1.814v4.534C18.616,8.734,17.804,9.546,16.803,9.546zM17.71,3.198c0-0.501-0.405-0.907-0.907-0.907h-4.535c-0.501,0-0.906,0.406-0.906,0.907v4.534c0,0.501,0.405,0.908,0.906,0.908h4.535c0.502,0,0.907-0.406,0.907-0.908V3.198z M7.733,18.615H3.198c-1.002,0-1.814-0.812-1.814-1.812v-4.535c0-1.002,0.812-1.814,1.814-1.814h4.535c1.002,0,1.814,0.812,1.814,1.814v4.535C9.547,17.803,8.735,18.615,7.733,18.615zM8.64,12.268c0-0.502-0.406-0.906-0.907-0.906H3.198c-0.501,0-0.907,0.404-0.907,0.906v4.535c0,0.502,0.406,0.906,0.907,0.906h4.535c0.501,0,0.907-0.404,0.907-0.906V12.268z M7.733,9.546H3.198c-1.002,0-1.814-0.812-1.814-1.814V3.198c0-1.002,0.812-1.814,1.814-1.814h4.535c1.002,0,1.814,0.812,1.814,1.814v4.534C9.547,8.734,8.735,9.546,7.733,9.546z M8.64,3.198c0-0.501-0.406-0.907-0.907-0.907H3.198c-0.501,0-0.907,0.406-0.907,0.907v4.534c0,0.501,0.406,0.908,0.907,0.908h4.535c0.501,0,0.907-0.406,0.907-0.908V3.198z" Fill="#22B14C" Height="16" Width="16" Stretch="Fill" />
</MenuItem.Icon>
</MenuItem>
+ <MenuItem Header="Save QR Code" Command="{Binding SaveQRCodeCommand}" IsVisible="{Binding SelectedAddress.IsExpanded}">
+ <MenuItem.Icon>
+ <Path HorizontalAlignment="Left" Data="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z" Stretch="Fill" Fill="#22B14C" />
+ </MenuItem.Icon>
+ </MenuItem>
<MenuItem Header="Copy address" Command="{Binding CopyAddress}">
<MenuItem.Icon>
<Path HorizontalAlignment="Left" Data="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" Fill="#22B14C" Height="16" Width="16" Stretch="Fill" />
<Grid>
<Expander Name="coinExpander" ExpandDirection="Down" IsExpanded="{Binding IsExpanded}" Classes="coloredExpander" Background="{Binding ElementName=coinExpander, Path=IsExpanded, Converter={StaticResource CoinItemExpanderColorConverter}}">
<StackPanel Orientation="Horizontal" Spacing="16" Margin="35 10 0 5">
- <controls:QrCode Height="220" Matrix="{Binding QrCode}" HorizontalAlignment="Left" />
+ <controls:QrCode Height="220" Matrix="{Binding QrCode}" QRImageSavePath="{Binding QRImageSavePath, Mode=TwoWay}" HorizontalAlignment="Left" />
<Grid ColumnDefinitions="140, *" RowDefinitions="22,22" Margin="6">
<TextBlock Classes="monospaceFont" Text="Public key:" Grid.Row="0" Grid.Column="0" />
<controls:ExtendedTextBox Classes="selectableTextBlock monospaceFont Transparent" Text="{Binding PubKey}" Grid.Row="0" Grid.Column="1" />

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
@@ -33,6 +33,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public ReactiveCommand<Unit, Unit> CopyLabel { get; }
public ReactiveCommand<Unit, Unit> ToggleQrCode { get; }
public ReactiveCommand<Unit, Unit> ChangeLabelCommand { get; }
+ public ReactiveCommand<Unit, Unit> SaveQRCodeCommand { get; }
public ReceiveTabViewModel(WalletViewModel walletViewModel)
: base("Receive", walletViewModel)
@@ -129,6 +130,11 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
SelectedAddress.InEditMode = true;
});
+ SaveQRCodeCommand = ReactiveCommand.CreateFromTask(async () =>
+ {
+ await SelectedAddress?.SaveQRCodeAsync();
+ });
+
_suggestions = new ObservableCollection<SuggestionViewModel>();
}

MODIFY
WalletWasabi.Gui/ViewModels/AddressViewModel.cs
WalletWasabi.Gui/ViewModels/AddressViewModel.cs
using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using Gma.QrCodeNet.Encoding;
using ReactiveUI;
using System;
+using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
@@ -25,6 +28,7 @@ namespace WalletWasabi.Gui.ViewModels
private double _clipboardNotificationOpacity;
private string _label;
private bool _inEditMode;
+ private string _qRImageSavePath;
private ObservableAsPropertyHelper<string> _expandMenuCaption;
public HdPubKey Model { get; }
@@ -135,10 +139,40 @@ namespace WalletWasabi.Gui.ViewModels
set => this.RaiseAndSetIfChanged(ref _qrCode, value);
}
+ public string QRImageSavePath
+ {
+ get => _qRImageSavePath;
+ set => this.RaiseAndSetIfChanged(ref _qRImageSavePath, value);
+ }
+
public string ExpandMenuCaption => _expandMenuCaption?.Value ?? string.Empty;
public CancellationTokenSource CancelClipboardNotification { get; set; }
+ public async Task SaveQRCodeAsync()
+ {
+ var sfd = new SaveFileDialog();
+
+ sfd.InitialFileName = $"{Address}.png";
+ sfd.Directory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
+ sfd.Filters.Add(new FileDialogFilter() { Name = "Portable Network Graphics (PNG) Image file", Extensions = { "png" } });
+
+ var visualRoot = (ClassicDesktopStyleApplicationLifetime)Application.Current.ApplicationLifetime;
+ var path = await sfd.ShowAsync(visualRoot.MainWindow);
+
+ if (!string.IsNullOrWhiteSpace(path))
+ {
+ var ext = Path.GetExtension(path);
+
+ if (string.IsNullOrWhiteSpace(ext))
+ {
+ path = $"{path}.png";
+ }
+
+ QRImageSavePath = path;
+ }
+ }
+
public async Task TryCopyToClipboardAsync()
{
try

Implement QR Code export.