Create a .NET MAUI library for Buy Me a Coffee integration with branded button, QR code, and widget controls. Includes 8 theme presets (yellow, black, white, blue, violet, orange, red, green), customizable styling, and SkiaSharp-based rendering. Supports opening BMC pages in browser and generating QR codes for donations.
234 lines
7.6 KiB
C#
234 lines
7.6 KiB
C#
using BuyMeCofee.Maui.Constants;
|
|
using BuyMeCofee.Maui.Enums;
|
|
using BuyMeCofee.Maui.Helpers;
|
|
using Microsoft.Maui.Controls.Shapes;
|
|
|
|
namespace BuyMeCofee.Maui.Controls;
|
|
|
|
/// <summary>
|
|
/// A branded Buy Me a Coffee button with the official cup logo and 8 color theme presets.
|
|
/// Opens the user's BMC page in the default browser when tapped.
|
|
/// </summary>
|
|
public class BuyMeACoffeeButton : ContentView
|
|
{
|
|
#region Bindable Properties
|
|
|
|
public static readonly BindableProperty UsernameProperty =
|
|
BindableProperty.Create(nameof(Username), typeof(string), typeof(BuyMeACoffeeButton),
|
|
string.Empty);
|
|
|
|
public static readonly BindableProperty ButtonTextProperty =
|
|
BindableProperty.Create(nameof(ButtonText), typeof(string), typeof(BuyMeACoffeeButton),
|
|
BmcConstants.DefaultButtonText, propertyChanged: OnVisualPropertyChanged);
|
|
|
|
public static readonly BindableProperty ThemeProperty =
|
|
BindableProperty.Create(nameof(Theme), typeof(BmcButtonTheme), typeof(BuyMeACoffeeButton),
|
|
BmcButtonTheme.Yellow, propertyChanged: OnThemePropertyChanged);
|
|
|
|
public static readonly BindableProperty CustomBackgroundColorProperty =
|
|
BindableProperty.Create(nameof(CustomBackgroundColor), typeof(Color), typeof(BuyMeACoffeeButton),
|
|
null, propertyChanged: OnVisualPropertyChanged);
|
|
|
|
public static readonly BindableProperty CustomTextColorProperty =
|
|
BindableProperty.Create(nameof(CustomTextColor), typeof(Color), typeof(BuyMeACoffeeButton),
|
|
null, propertyChanged: OnVisualPropertyChanged);
|
|
|
|
public static readonly BindableProperty CornerRadiusProperty =
|
|
BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(BuyMeACoffeeButton),
|
|
BmcConstants.ButtonCornerRadius, propertyChanged: OnVisualPropertyChanged);
|
|
|
|
public static readonly BindableProperty FontSizeProperty =
|
|
BindableProperty.Create(nameof(FontSize), typeof(double), typeof(BuyMeACoffeeButton),
|
|
16.0, propertyChanged: OnVisualPropertyChanged);
|
|
|
|
public static readonly BindableProperty CupSizeProperty =
|
|
BindableProperty.Create(nameof(CupSize), typeof(double), typeof(BuyMeACoffeeButton),
|
|
28.0, propertyChanged: OnVisualPropertyChanged);
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>Your Buy Me a Coffee username/slug.</summary>
|
|
public string Username
|
|
{
|
|
get => (string)GetValue(UsernameProperty);
|
|
set => SetValue(UsernameProperty, value);
|
|
}
|
|
|
|
/// <summary>Button label text. Default: "Buy me a coffee"</summary>
|
|
public string ButtonText
|
|
{
|
|
get => (string)GetValue(ButtonTextProperty);
|
|
set => SetValue(ButtonTextProperty, value);
|
|
}
|
|
|
|
/// <summary>Color theme preset. Default: Yellow (official BMC brand color).</summary>
|
|
public BmcButtonTheme Theme
|
|
{
|
|
get => (BmcButtonTheme)GetValue(ThemeProperty);
|
|
set => SetValue(ThemeProperty, value);
|
|
}
|
|
|
|
/// <summary>Custom background color. Only used when Theme is set to Custom.</summary>
|
|
public Color? CustomBackgroundColor
|
|
{
|
|
get => (Color?)GetValue(CustomBackgroundColorProperty);
|
|
set => SetValue(CustomBackgroundColorProperty, value);
|
|
}
|
|
|
|
/// <summary>Custom text color. Only used when Theme is set to Custom.</summary>
|
|
public Color? CustomTextColor
|
|
{
|
|
get => (Color?)GetValue(CustomTextColorProperty);
|
|
set => SetValue(CustomTextColorProperty, value);
|
|
}
|
|
|
|
/// <summary>Border corner radius. Default: 8</summary>
|
|
public double CornerRadius
|
|
{
|
|
get => (double)GetValue(CornerRadiusProperty);
|
|
set => SetValue(CornerRadiusProperty, value);
|
|
}
|
|
|
|
/// <summary>Font size for the button text. Default: 16</summary>
|
|
public double FontSize
|
|
{
|
|
get => (double)GetValue(FontSizeProperty);
|
|
set => SetValue(FontSizeProperty, value);
|
|
}
|
|
|
|
/// <summary>Cup logo image height. Default: 28</summary>
|
|
public double CupSize
|
|
{
|
|
get => (double)GetValue(CupSizeProperty);
|
|
set => SetValue(CupSizeProperty, value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private Border _border = null!;
|
|
private Image _cupImage = null!;
|
|
private Label _textLabel = null!;
|
|
|
|
public BuyMeACoffeeButton()
|
|
{
|
|
BuildLayout();
|
|
ApplyTheme();
|
|
}
|
|
|
|
private void BuildLayout()
|
|
{
|
|
_cupImage = new Image
|
|
{
|
|
Source = BmcBrandAssets.GetCupLogo(),
|
|
HeightRequest = CupSize,
|
|
Aspect = Aspect.AspectFit,
|
|
VerticalOptions = LayoutOptions.Center,
|
|
};
|
|
|
|
_textLabel = new Label
|
|
{
|
|
Text = ButtonText,
|
|
FontSize = FontSize,
|
|
FontAttributes = FontAttributes.Bold,
|
|
VerticalOptions = LayoutOptions.Center,
|
|
};
|
|
|
|
var stack = new HorizontalStackLayout
|
|
{
|
|
Spacing = 8,
|
|
HorizontalOptions = LayoutOptions.Center,
|
|
VerticalOptions = LayoutOptions.Center,
|
|
Children = { _cupImage, _textLabel }
|
|
};
|
|
|
|
_border = new Border
|
|
{
|
|
StrokeShape = new RoundRectangle { CornerRadius = new Microsoft.Maui.CornerRadius(CornerRadius) },
|
|
Stroke = Colors.Transparent,
|
|
Padding = new Thickness(16, 10),
|
|
Content = stack,
|
|
Shadow = new Shadow
|
|
{
|
|
Brush = new SolidColorBrush(Colors.Black),
|
|
Offset = new Point(0, 2),
|
|
Radius = 4,
|
|
Opacity = 0.25f,
|
|
}
|
|
};
|
|
|
|
var tap = new TapGestureRecognizer();
|
|
tap.Tapped += OnTapped;
|
|
_border.GestureRecognizers.Add(tap);
|
|
|
|
var hover = new PointerGestureRecognizer();
|
|
hover.PointerEntered += (_, _) => _border.Opacity = 0.85;
|
|
hover.PointerExited += (_, _) => _border.Opacity = 1.0;
|
|
_border.GestureRecognizers.Add(hover);
|
|
|
|
Content = _border;
|
|
}
|
|
|
|
private void ApplyTheme()
|
|
{
|
|
if (_border is null) return;
|
|
|
|
Color bg, text;
|
|
Color stroke = Colors.Transparent;
|
|
|
|
if (Theme == BmcButtonTheme.Custom)
|
|
{
|
|
bg = CustomBackgroundColor ?? Color.FromArgb(BmcColors.YellowBg);
|
|
text = CustomTextColor ?? Color.FromArgb(BmcColors.BrandDark);
|
|
}
|
|
else
|
|
{
|
|
var info = BmcThemeResolver.Resolve(Theme);
|
|
bg = info.Background;
|
|
text = info.TextColor;
|
|
stroke = info.StrokeColor;
|
|
}
|
|
|
|
_border.BackgroundColor = bg;
|
|
_border.Stroke = new SolidColorBrush(stroke);
|
|
_textLabel.TextColor = text;
|
|
}
|
|
|
|
private void UpdateVisuals()
|
|
{
|
|
if (_border is null) return;
|
|
|
|
_textLabel.Text = ButtonText;
|
|
_textLabel.FontSize = FontSize;
|
|
_cupImage.HeightRequest = CupSize;
|
|
|
|
if (_border.StrokeShape is RoundRectangle rr)
|
|
rr.CornerRadius = new Microsoft.Maui.CornerRadius(CornerRadius);
|
|
|
|
ApplyTheme();
|
|
}
|
|
|
|
private async void OnTapped(object? sender, TappedEventArgs e)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(Username)) return;
|
|
|
|
await _border.ScaleToAsync(0.95, 80, Easing.CubicIn);
|
|
await _border.ScaleToAsync(1.0, 80, Easing.CubicOut);
|
|
|
|
await Launcher.Default.OpenAsync(new Uri($"{BmcConstants.BaseUrl}{Username}"));
|
|
}
|
|
|
|
private static void OnVisualPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is BuyMeACoffeeButton button)
|
|
button.UpdateVisuals();
|
|
}
|
|
|
|
private static void OnThemePropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is BuyMeACoffeeButton button)
|
|
button.ApplyTheme();
|
|
}
|
|
}
|