feat(scenarios): add buy me a coffee maui library
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.
This commit is contained in:
233
BuyMeCofee.Maui/Controls/BuyMeACoffeeButton.cs
Normal file
233
BuyMeCofee.Maui/Controls/BuyMeACoffeeButton.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user