using BuyMeCofee.Maui.Constants;
using BuyMeCofee.Maui.Enums;
using BuyMeCofee.Maui.Helpers;
using Microsoft.Maui.Controls.Shapes;
namespace BuyMeCofee.Maui.Controls;
///
/// 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.
///
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
/// Your Buy Me a Coffee username/slug.
public string Username
{
get => (string)GetValue(UsernameProperty);
set => SetValue(UsernameProperty, value);
}
/// Button label text. Default: "Buy me a coffee"
public string ButtonText
{
get => (string)GetValue(ButtonTextProperty);
set => SetValue(ButtonTextProperty, value);
}
/// Color theme preset. Default: Yellow (official BMC brand color).
public BmcButtonTheme Theme
{
get => (BmcButtonTheme)GetValue(ThemeProperty);
set => SetValue(ThemeProperty, value);
}
/// Custom background color. Only used when Theme is set to Custom.
public Color? CustomBackgroundColor
{
get => (Color?)GetValue(CustomBackgroundColorProperty);
set => SetValue(CustomBackgroundColorProperty, value);
}
/// Custom text color. Only used when Theme is set to Custom.
public Color? CustomTextColor
{
get => (Color?)GetValue(CustomTextColorProperty);
set => SetValue(CustomTextColorProperty, value);
}
/// Border corner radius. Default: 8
public double CornerRadius
{
get => (double)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
/// Font size for the button text. Default: 16
public double FontSize
{
get => (double)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
/// Cup logo image height. Default: 28
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();
}
}