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(); } }