Implement BmcSupporterService to verify supporters via BMC API with configurable caching. Add SupporterEmail and HideIfSupporter properties to all controls to automatically hide donation prompts for existing supporters. Replace PNG logo with SVG for crisp rendering at all scales. Add BmcOptions and BmcConfiguration for library-wide settings. Bump version to 1.1.0.
267 lines
8.9 KiB
C#
267 lines
8.9 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);
|
|
|
|
public static readonly BindableProperty SupporterEmailProperty =
|
|
BindableProperty.Create(nameof(SupporterEmail), typeof(string), typeof(BuyMeACoffeeButton),
|
|
null, propertyChanged: OnSupporterPropertyChanged);
|
|
|
|
public static readonly BindableProperty HideIfSupporterProperty =
|
|
BindableProperty.Create(nameof(HideIfSupporter), typeof(bool), typeof(BuyMeACoffeeButton),
|
|
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>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);
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
|
|
#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();
|
|
}
|
|
|
|
private static void OnSupporterPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is BuyMeACoffeeButton button)
|
|
BmcSupporterCheck.CheckAndHide(button, button.SupporterEmail, button.HideIfSupporter);
|
|
}
|
|
}
|