Limited
2
0
Files
bmc.maui/BuyMeCofee.Maui/Controls/BuyMeACoffeeQrCode.cs

258 lines
8.1 KiB
C#
Raw Normal View History

using BuyMeCofee.Maui.Constants;
using BuyMeCofee.Maui.Helpers;
using QRCoder;
using SkiaSharp;
using SkiaSharp.Views.Maui;
using SkiaSharp.Views.Maui.Controls;
namespace BuyMeCofee.Maui.Controls;
/// <summary>
/// Generates and displays a QR code linking to a Buy Me a Coffee profile,
/// with the BMC coffee cup logo overlaid in the center.
/// </summary>
public class BuyMeACoffeeQrCode : ContentView
{
#region Bindable Properties
public static readonly BindableProperty UsernameProperty =
BindableProperty.Create(nameof(Username), typeof(string), typeof(BuyMeACoffeeQrCode),
string.Empty, propertyChanged: OnQrPropertyChanged);
public static readonly BindableProperty SizeProperty =
BindableProperty.Create(nameof(Size), typeof(double), typeof(BuyMeACoffeeQrCode),
200.0, propertyChanged: OnSizeChanged);
public static readonly BindableProperty ForegroundColorProperty =
BindableProperty.Create(nameof(ForegroundColor), typeof(Color), typeof(BuyMeACoffeeQrCode),
Colors.Black, propertyChanged: OnQrPropertyChanged);
public static new readonly BindableProperty BackgroundColorProperty =
BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(BuyMeACoffeeQrCode),
Colors.White, propertyChanged: OnQrPropertyChanged);
public static readonly BindableProperty LogoSizeFractionProperty =
BindableProperty.Create(nameof(LogoSizeFraction), typeof(double), typeof(BuyMeACoffeeQrCode),
0.25, propertyChanged: OnVisualPropertyChanged);
public static readonly BindableProperty SupporterEmailProperty =
BindableProperty.Create(nameof(SupporterEmail), typeof(string), typeof(BuyMeACoffeeQrCode),
null, propertyChanged: OnSupporterPropertyChanged);
public static readonly BindableProperty HideIfSupporterProperty =
BindableProperty.Create(nameof(HideIfSupporter), typeof(bool), typeof(BuyMeACoffeeQrCode),
false, propertyChanged: OnSupporterPropertyChanged);
#endregion
#region Properties
/// <summary>Your Buy Me a Coffee username/slug.</summary>
public string Username
{
get => (string)GetValue(UsernameProperty);
set => SetValue(UsernameProperty, value);
}
/// <summary>Width and height of the QR code control. Default: 200</summary>
public double Size
{
get => (double)GetValue(SizeProperty);
set => SetValue(SizeProperty, value);
}
/// <summary>QR code foreground (module) color. Default: Black</summary>
public Color ForegroundColor
{
get => (Color)GetValue(ForegroundColorProperty);
set => SetValue(ForegroundColorProperty, value);
}
/// <summary>QR code background color. Default: White</summary>
public new Color BackgroundColor
{
get => (Color)GetValue(BackgroundColorProperty);
set => SetValue(BackgroundColorProperty, value);
}
/// <summary>
/// Email address to check against your BMC supporters list.
/// Requires AccessToken configured via UseBuyMeACoffee(options => ...).
/// </summary>
public string? SupporterEmail
{
get => (string?)GetValue(SupporterEmailProperty);
set => SetValue(SupporterEmailProperty, value);
}
/// <summary>
/// When true, the control auto-hides if SupporterEmail is a verified supporter. Default: false.
/// </summary>
public bool HideIfSupporter
{
get => (bool)GetValue(HideIfSupporterProperty);
set => SetValue(HideIfSupporterProperty, value);
}
/// <summary>Logo size as fraction of QR code size (0.0 - 0.35). Default: 0.25</summary>
public double LogoSizeFraction
{
get => (double)GetValue(LogoSizeFractionProperty);
set => SetValue(LogoSizeFractionProperty, value);
}
#endregion
private readonly SKCanvasView _canvas;
private SKBitmap? _qrBitmap;
private SKBitmap? _logoBitmap;
public BuyMeACoffeeQrCode()
{
_canvas = new SKCanvasView
{
WidthRequest = Size,
HeightRequest = Size,
};
_canvas.PaintSurface += OnPaintSurface;
Content = _canvas;
LoadLogo();
RegenerateQr();
}
private void LoadLogo()
{
try
{
using var stream = BmcBrandAssets.GetCupLogoStream();
if (stream is null)
{
_logoBitmap = null;
return;
}
_logoBitmap = SKBitmap.Decode(stream);
}
catch
{
_logoBitmap = null;
}
}
private void RegenerateQr()
{
_qrBitmap?.Dispose();
_qrBitmap = null;
if (string.IsNullOrWhiteSpace(Username))
{
_canvas.InvalidateSurface();
return;
}
var url = $"{BmcConstants.BaseUrl}{Username}";
using var generator = new QRCodeGenerator();
var data = generator.CreateQrCode(url, QRCodeGenerator.ECCLevel.H);
var renderer = new PngByteQRCode(data);
var darkRgba = ColorToRgba(ForegroundColor);
var lightRgba = ColorToRgba(BackgroundColor);
var pngBytes = renderer.GetGraphic(20, darkRgba, lightRgba);
using var stream = new MemoryStream(pngBytes);
_qrBitmap = SKBitmap.Decode(stream);
_canvas.InvalidateSurface();
}
private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.Transparent);
if (_qrBitmap is null) return;
var info = e.Info;
var size = Math.Min(info.Width, info.Height);
// Draw QR code scaled to fill
var qrRect = new SKRect(0, 0, size, size);
using var qrPaint = new SKPaint { IsAntialias = false };
canvas.DrawBitmap(_qrBitmap, qrRect, qrPaint);
if (_logoBitmap is null) return;
// Draw white quiet zone behind logo
var logoSize = (float)(size * Math.Clamp(LogoSizeFraction, 0.0, 0.35));
var logoPadding = logoSize * 0.15f;
var cx = size / 2f;
var cy = size / 2f;
var quietRect = new SKRect(
cx - logoSize / 2 - logoPadding,
cy - logoSize / 2 - logoPadding,
cx + logoSize / 2 + logoPadding,
cy + logoSize / 2 + logoPadding);
using var quietPaint = new SKPaint
{
Color = SKColors.White,
IsAntialias = true,
Style = SKPaintStyle.Fill,
};
canvas.DrawRoundRect(quietRect, 8, 8, quietPaint);
// Draw logo centered
var logoRect = new SKRect(
cx - logoSize / 2, cy - logoSize / 2,
cx + logoSize / 2, cy + logoSize / 2);
using var logoPaint = new SKPaint { IsAntialias = true };
canvas.DrawBitmap(_logoBitmap, logoRect, logoPaint);
}
private static byte[] ColorToRgba(Color color)
{
return
[
(byte)(color.Red * 255),
(byte)(color.Green * 255),
(byte)(color.Blue * 255),
(byte)(color.Alpha * 255),
];
}
#region Property Changed Handlers
private static void OnQrPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is BuyMeACoffeeQrCode qr)
qr.RegenerateQr();
}
private static void OnVisualPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is BuyMeACoffeeQrCode qr)
qr._canvas.InvalidateSurface();
}
private static void OnSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is BuyMeACoffeeQrCode qr)
{
var size = (double)newValue;
qr._canvas.WidthRequest = size;
qr._canvas.HeightRequest = size;
qr._canvas.InvalidateSurface();
}
}
private static void OnSupporterPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is BuyMeACoffeeQrCode qr)
BmcSupporterCheck.CheckAndHide(qr, qr.SupporterEmail, qr.HideIfSupporter);
}
#endregion
}