diff --git a/BuyMeCofee.Maui/BmcConfiguration.cs b/BuyMeCofee.Maui/BmcConfiguration.cs new file mode 100644 index 0000000..9f8e22a --- /dev/null +++ b/BuyMeCofee.Maui/BmcConfiguration.cs @@ -0,0 +1,15 @@ +using BuyMeCofee.Maui.Services; + +namespace BuyMeCofee.Maui; + +/// +/// Static configuration holder for the Buy Me a Coffee library. +/// Populated by . +/// +public static class BmcConfiguration +{ + /// + /// The supporter lookup service. Null if no AccessToken was configured. + /// + public static BmcSupporterService? SupporterService { get; internal set; } +} diff --git a/BuyMeCofee.Maui/BmcOptions.cs b/BuyMeCofee.Maui/BmcOptions.cs new file mode 100644 index 0000000..03350c2 --- /dev/null +++ b/BuyMeCofee.Maui/BmcOptions.cs @@ -0,0 +1,18 @@ +namespace BuyMeCofee.Maui; + +/// +/// Configuration options for the Buy Me a Coffee library. +/// +public class BmcOptions +{ + /// + /// Your Buy Me a Coffee API access token (from https://developers.buymeacoffee.com). + /// Required for supporter verification. This is the creator's token. + /// + public string? AccessToken { get; set; } + + /// + /// How long to cache the supporter list before re-fetching. Default: 1 hour. + /// + public TimeSpan CacheDuration { get; set; } = TimeSpan.FromHours(1); +} diff --git a/BuyMeCofee.Maui/BuyMeACoffeeExtensions.cs b/BuyMeCofee.Maui/BuyMeACoffeeExtensions.cs index 5803941..81eec42 100644 --- a/BuyMeCofee.Maui/BuyMeACoffeeExtensions.cs +++ b/BuyMeCofee.Maui/BuyMeACoffeeExtensions.cs @@ -1,3 +1,4 @@ +using BuyMeCofee.Maui.Services; using SkiaSharp.Views.Maui.Controls.Hosting; namespace BuyMeCofee.Maui; @@ -13,4 +14,32 @@ public static class BuyMeACoffeeExtensions builder.UseSkiaSharp(); return builder; } + + /// + /// Registers Buy Me a Coffee controls with optional supporter verification. + /// When an AccessToken is provided, controls with HideIfSupporter=true + /// will auto-hide for verified supporters. + /// + /// + /// builder.UseBuyMeACoffee(options => + /// { + /// options.AccessToken = "your-bmc-api-token"; + /// options.CacheDuration = TimeSpan.FromHours(2); + /// }); + /// + public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder, Action configure) + { + builder.UseSkiaSharp(); + + var options = new BmcOptions(); + configure(options); + + if (!string.IsNullOrWhiteSpace(options.AccessToken)) + { + BmcConfiguration.SupporterService = new BmcSupporterService( + options.AccessToken, options.CacheDuration); + } + + return builder; + } } diff --git a/BuyMeCofee.Maui/BuyMeCofee.Maui.csproj b/BuyMeCofee.Maui/BuyMeCofee.Maui.csproj index 109a1b4..76267ed 100644 --- a/BuyMeCofee.Maui/BuyMeCofee.Maui.csproj +++ b/BuyMeCofee.Maui/BuyMeCofee.Maui.csproj @@ -31,6 +31,8 @@ git maui;buymeacoffee;bmc;donation;tip;controls README.md + 1.1.0 + v1.1.0: Crisp SVG branding via MAUI asset pipeline, supporter verification API (auto-hide controls for existing supporters), SupporterEmail/HideIfSupporter properties on all controls. false 15.0 @@ -47,6 +49,7 @@ + diff --git a/BuyMeCofee.Maui/Controls/BuyMeACoffeeButton.cs b/BuyMeCofee.Maui/Controls/BuyMeACoffeeButton.cs index 2b37c70..c25399d 100644 --- a/BuyMeCofee.Maui/Controls/BuyMeACoffeeButton.cs +++ b/BuyMeCofee.Maui/Controls/BuyMeACoffeeButton.cs @@ -45,6 +45,14 @@ public class BuyMeACoffeeButton : ContentView 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 @@ -105,6 +113,25 @@ public class BuyMeACoffeeButton : ContentView set => SetValue(CupSizeProperty, value); } + /// + /// Email address to check against your BMC supporters list. + /// Requires AccessToken configured via UseBuyMeACoffee(options => ...). + /// + public string? SupporterEmail + { + get => (string?)GetValue(SupporterEmailProperty); + set => SetValue(SupporterEmailProperty, value); + } + + /// + /// When true, the control auto-hides if SupporterEmail is a verified supporter. Default: false. + /// + public bool HideIfSupporter + { + get => (bool)GetValue(HideIfSupporterProperty); + set => SetValue(HideIfSupporterProperty, value); + } + #endregion private Border _border = null!; @@ -230,4 +257,10 @@ public class BuyMeACoffeeButton : ContentView 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); + } } diff --git a/BuyMeCofee.Maui/Controls/BuyMeACoffeeQrCode.cs b/BuyMeCofee.Maui/Controls/BuyMeACoffeeQrCode.cs index e9a6141..34dfb62 100644 --- a/BuyMeCofee.Maui/Controls/BuyMeACoffeeQrCode.cs +++ b/BuyMeCofee.Maui/Controls/BuyMeACoffeeQrCode.cs @@ -35,6 +35,14 @@ public class BuyMeACoffeeQrCode : ContentView BindableProperty.Create(nameof(LogoSizeFraction), typeof(double), typeof(BuyMeACoffeeQrCode), 0.25, propertyChanged: OnVisualPropertyChanged); + public static readonly BindableProperty SupporterEmailProperty = + BindableProperty.Create(nameof(SupporterEmail), typeof(string), typeof(BuyMeACoffeeQrCode), + null, propertyChanged: OnSupporterPropertyChanged); + + public static readonly BindableProperty HideIfSupporterProperty = + BindableProperty.Create(nameof(HideIfSupporter), typeof(bool), typeof(BuyMeACoffeeQrCode), + false, propertyChanged: OnSupporterPropertyChanged); + #endregion #region Properties @@ -67,6 +75,25 @@ public class BuyMeACoffeeQrCode : ContentView set => SetValue(BackgroundColorProperty, value); } + /// + /// Email address to check against your BMC supporters list. + /// Requires AccessToken configured via UseBuyMeACoffee(options => ...). + /// + public string? SupporterEmail + { + get => (string?)GetValue(SupporterEmailProperty); + set => SetValue(SupporterEmailProperty, value); + } + + /// + /// When true, the control auto-hides if SupporterEmail is a verified supporter. Default: false. + /// + public bool HideIfSupporter + { + get => (bool)GetValue(HideIfSupporterProperty); + set => SetValue(HideIfSupporterProperty, value); + } + /// Logo size as fraction of QR code size (0.0 - 0.35). Default: 0.25 public double LogoSizeFraction { @@ -99,6 +126,11 @@ public class BuyMeACoffeeQrCode : ContentView try { using var stream = BmcBrandAssets.GetCupLogoStream(); + if (stream is null) + { + _logoBitmap = null; + return; + } _logoBitmap = SKBitmap.Decode(stream); } catch @@ -215,5 +247,11 @@ public class BuyMeACoffeeQrCode : ContentView } } + private static void OnSupporterPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is BuyMeACoffeeQrCode qr) + BmcSupporterCheck.CheckAndHide(qr, qr.SupporterEmail, qr.HideIfSupporter); + } + #endregion } diff --git a/BuyMeCofee.Maui/Controls/BuyMeACoffeeWidget.cs b/BuyMeCofee.Maui/Controls/BuyMeACoffeeWidget.cs index d9560bb..0ff42bd 100644 --- a/BuyMeCofee.Maui/Controls/BuyMeACoffeeWidget.cs +++ b/BuyMeCofee.Maui/Controls/BuyMeACoffeeWidget.cs @@ -41,6 +41,14 @@ public class BuyMeACoffeeWidget : ContentView BindableProperty.Create(nameof(SupportButtonText), typeof(string), typeof(BuyMeACoffeeWidget), BmcConstants.DefaultSupportButtonText); + public static readonly BindableProperty SupporterEmailProperty = + BindableProperty.Create(nameof(SupporterEmail), typeof(string), typeof(BuyMeACoffeeWidget), + null, propertyChanged: OnSupporterPropertyChanged); + + public static readonly BindableProperty HideIfSupporterProperty = + BindableProperty.Create(nameof(HideIfSupporter), typeof(bool), typeof(BuyMeACoffeeWidget), + false, propertyChanged: OnSupporterPropertyChanged); + #endregion #region Properties @@ -94,6 +102,25 @@ public class BuyMeACoffeeWidget : ContentView set => SetValue(SupportButtonTextProperty, value); } + /// + /// Email address to check against your BMC supporters list. + /// Requires AccessToken configured via UseBuyMeACoffee(options => ...). + /// + public string? SupporterEmail + { + get => (string?)GetValue(SupporterEmailProperty); + set => SetValue(SupporterEmailProperty, value); + } + + /// + /// When true, the control auto-hides if SupporterEmail is a verified supporter. Default: false. + /// + public bool HideIfSupporter + { + get => (bool)GetValue(HideIfSupporterProperty); + set => SetValue(HideIfSupporterProperty, value); + } + #endregion /// Raised when the user taps the Support button, before opening the browser. @@ -438,5 +465,11 @@ public class BuyMeACoffeeWidget : ContentView widget._monthlyRow.IsVisible = (bool)newValue; } + private static void OnSupporterPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is BuyMeACoffeeWidget widget) + BmcSupporterCheck.CheckAndHide(widget, widget.SupporterEmail, widget.HideIfSupporter); + } + #endregion } diff --git a/BuyMeCofee.Maui/Helpers/BmcBrandAssets.cs b/BuyMeCofee.Maui/Helpers/BmcBrandAssets.cs index a10f61f..2bc75f1 100644 --- a/BuyMeCofee.Maui/Helpers/BmcBrandAssets.cs +++ b/BuyMeCofee.Maui/Helpers/BmcBrandAssets.cs @@ -1,18 +1,36 @@ +using System.Reflection; + namespace BuyMeCofee.Maui.Helpers; internal static class BmcBrandAssets { - private const string LogoResourceName = "BuyMeCofee.Maui.Resources.Images.bmc_logo.png"; + // MAUI converts bmc_logo.svg → bmc_logo.png at build time with proper DPI scaling + private const string LogoFileName = "bmc_logo.png"; + // Fallback: embedded PNG for QR code overlay (SkiaSharp needs a raw stream) + private const string EmbeddedLogoName = "BuyMeCofee.Maui.Resources.Images.bmc_logo.png"; + + /// + /// Returns the cup logo as an ImageSource for MAUI Image controls. + /// Uses the MAUI asset pipeline (SVG → crisp multi-DPI PNG). + /// internal static ImageSource GetCupLogo() { - var assembly = typeof(BmcBrandAssets).Assembly; - return ImageSource.FromStream(() => assembly.GetManifestResourceStream(LogoResourceName)!); + return ImageSource.FromFile(LogoFileName); } - internal static Stream GetCupLogoStream() + /// + /// Returns the cup logo as a raw stream for SkiaSharp rendering (QR code overlay). + /// Falls back to embedded PNG resource. + /// + internal static Stream? GetCupLogoStream() { + // Try embedded resource first (for QR code SkiaSharp rendering) var assembly = typeof(BmcBrandAssets).Assembly; - return assembly.GetManifestResourceStream(LogoResourceName)!; + var stream = assembly.GetManifestResourceStream(EmbeddedLogoName); + if (stream != null) return stream; + + // If embedded PNG was removed, return null (QR code will render without logo) + return null; } } diff --git a/BuyMeCofee.Maui/Helpers/BmcSupporterCheck.cs b/BuyMeCofee.Maui/Helpers/BmcSupporterCheck.cs new file mode 100644 index 0000000..03ee6e3 --- /dev/null +++ b/BuyMeCofee.Maui/Helpers/BmcSupporterCheck.cs @@ -0,0 +1,26 @@ +namespace BuyMeCofee.Maui.Helpers; + +/// +/// Shared logic for controls to check supporter status and auto-hide. +/// +internal static class BmcSupporterCheck +{ + public static async void CheckAndHide(ContentView control, string? email, bool hideIfSupporter) + { + if (!hideIfSupporter || string.IsNullOrWhiteSpace(email)) return; + + var service = BmcConfiguration.SupporterService; + if (service == null) return; + + try + { + var isSupporter = await service.IsSupporterAsync(email); + if (isSupporter) + control.IsVisible = false; + } + catch + { + // Silently fail — keep control visible + } + } +} diff --git a/BuyMeCofee.Maui/Resources/Images/bmc_logo.svg b/BuyMeCofee.Maui/Resources/Images/bmc_logo.svg new file mode 100644 index 0000000..5ba6db9 --- /dev/null +++ b/BuyMeCofee.Maui/Resources/Images/bmc_logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/BuyMeCofee.Maui/Services/BmcSupporterService.cs b/BuyMeCofee.Maui/Services/BmcSupporterService.cs new file mode 100644 index 0000000..52f9034 --- /dev/null +++ b/BuyMeCofee.Maui/Services/BmcSupporterService.cs @@ -0,0 +1,93 @@ +using System.Net.Http.Headers; +using System.Text.Json; + +namespace BuyMeCofee.Maui.Services; + +/// +/// Checks the Buy Me a Coffee API to determine whether a given email +/// belongs to a supporter (one-time or member). Results are cached. +/// +public class BmcSupporterService +{ + private const string ApiBase = "https://developers.buymeacoffee.com/api/v1/"; + + private readonly HttpClient _http; + private readonly TimeSpan _cacheDuration; + private HashSet? _cachedEmails; + private DateTime _cacheExpiry; + + public BmcSupporterService(string accessToken, TimeSpan cacheDuration) + { + _cacheDuration = cacheDuration; + _http = new HttpClient { BaseAddress = new Uri(ApiBase) }; + _http.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", accessToken); + } + + /// + /// Returns true if the given email matches any one-time supporter + /// or active/inactive member on the creator's BMC account. + /// + public async Task IsSupporterAsync(string email) + { + if (string.IsNullOrWhiteSpace(email)) return false; + + var emails = await GetAllSupporterEmailsAsync(); + return emails.Contains(email.Trim()); + } + + private async Task> GetAllSupporterEmailsAsync() + { + if (_cachedEmails != null && DateTime.UtcNow < _cacheExpiry) + return _cachedEmails; + + var emails = new HashSet(StringComparer.OrdinalIgnoreCase); + + await FetchEmailsFromEndpoint("supporters", emails); + await FetchEmailsFromEndpoint("subscriptions", emails); + + _cachedEmails = emails; + _cacheExpiry = DateTime.UtcNow + _cacheDuration; + return emails; + } + + private async Task FetchEmailsFromEndpoint(string endpoint, HashSet emails) + { + try + { + var page = 1; + while (true) + { + var response = await _http.GetAsync($"{endpoint}?page={page}"); + if (!response.IsSuccessStatusCode) break; + + var json = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + if (root.TryGetProperty("data", out var data)) + { + foreach (var item in data.EnumerateArray()) + { + if (item.TryGetProperty("payer_email", out var emailProp)) + { + var email = emailProp.GetString(); + if (!string.IsNullOrWhiteSpace(email)) + emails.Add(email); + } + } + } + + if (!root.TryGetProperty("next_page_url", out var nextPage) || + nextPage.ValueKind == JsonValueKind.Null) + break; + + page++; + } + } + catch + { + // Silently fail — don't block the app if BMC API is unreachable + } + } +} diff --git a/README.md b/README.md index 7fa19eb..f18fe7a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![.NET MAUI](https://img.shields.io/badge/.NET%20MAUI-10.0-512BD4?logo=dotnet)](https://dotnet.microsoft.com/apps/maui) [![Platform](https://img.shields.io/badge/platform-Android%20%7C%20iOS%20%7C%20macOS%20%7C%20Windows-blue)](https://dotnet.microsoft.com/apps/maui) [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) -[![NuGet](https://img.shields.io/badge/nuget-coming%20soon-orange)](https://www.nuget.org) +[![NuGet](https://img.shields.io/badge/nuget-v1.1.0-blue)](https://git.marketally.com/misc/bmc.maui/packages) A comprehensive .NET MAUI library for integrating Buy Me a Coffee support into your cross-platform applications. Provides branded buttons, QR codes, and interactive widgets with official BMC styling and theming. @@ -16,6 +16,7 @@ A comprehensive .NET MAUI library for integrating Buy Me a Coffee support into y - [BuyMeACoffeeButton](#buymeacoffeebutton) - [BuyMeACoffeeQrCode](#buymeacoffeeqrcode) - [BuyMeACoffeeWidget](#buymeacoffeewidget) +- [Supporter Verification](#supporter-verification) - [Theming](#theming) - [API Reference](#api-reference) - [Platform Support](#platform-support) @@ -26,14 +27,15 @@ A comprehensive .NET MAUI library for integrating Buy Me a Coffee support into y ## Features -✅ **BuyMeACoffeeButton** - Branded button with official coffee cup logo and 8 color themes -✅ **BuyMeACoffeeQrCode** - QR code generator with embedded BMC logo overlay -✅ **BuyMeACoffeeWidget** - Full-featured support widget with amount selection, name/message fields, and monthly toggle -✅ **Official Branding** - Uses authentic Buy Me a Coffee colors, logos, and styling -✅ **Cross-Platform** - Supports Android, iOS, macOS, and Windows -✅ **Customizable** - Extensive theming and styling options -✅ **Touch Optimized** - Smooth animations and hover effects -✅ **QR Code Generation** - High-quality QR codes with error correction level H +- **BuyMeACoffeeButton** - Branded button with official coffee cup logo and 8 color themes +- **BuyMeACoffeeQrCode** - QR code generator with embedded BMC logo overlay +- **BuyMeACoffeeWidget** - Full-featured support widget with amount selection, name/message fields, and monthly toggle +- **Official Branding** - Crisp SVG-based logos via MAUI asset pipeline, authentic Buy Me a Coffee colors and styling +- **Supporter Verification** - Auto-hide controls for users who have already supported you via the BMC API +- **Cross-Platform** - Supports Android, iOS, macOS, and Windows +- **Customizable** - Extensive theming and styling options +- **Touch Optimized** - Smooth animations and hover effects +- **QR Code Generation** - High-quality QR codes with error correction level H ## Installation @@ -41,19 +43,33 @@ A comprehensive .NET MAUI library for integrating Buy Me a Coffee support into y - .NET 10.0 SDK or later - .NET MAUI workload installed -- Visual Studio 2022 17.8+ or Visual Studio Code with .NET MAUI extension -### Add to Your Project +### NuGet Package -1. **Add the project reference** to your .NET MAUI application: +Add the MarketAlly NuGet source to your `NuGet.config`: ```xml - - - + + + + + + ``` -2. **Register the library** in your `MauiProgram.cs`: +Then add the package reference: + +```xml + +``` + +Or via CLI: + +```bash +dotnet add package BuyMeCofee.Maui --version 1.1.0 +``` + +### Register in MauiProgram.cs ```csharp using BuyMeCofee.Maui; @@ -66,7 +82,7 @@ public static class MauiProgram builder .UseMauiApp() .UseBuyMeACoffee(); // Add this line - + return builder.Build(); } } @@ -85,7 +101,7 @@ xmlns:bmc="clr-namespace:BuyMeCofee.Maui.Controls;assembly=BuyMeCofee.Maui" Add a button to your page: ```xml - @@ -100,7 +116,7 @@ A branded button that opens your Buy Me a Coffee page in the default browser. #### Basic Usage ```xml - ``` @@ -108,7 +124,7 @@ A branded button that opens your Buy Me a Coffee page in the default browser. #### With Custom Styling ```xml - ``` -#### Code-Behind Example - -```csharp -using BuyMeCofee.Maui.Controls; -using BuyMeCofee.Maui.Enums; - -var button = new BuyMeACoffeeButton -{ - Username = "yourname", - ButtonText = "Buy me a coffee", - Theme = BmcButtonTheme.Blue, - CornerRadius = 10, - FontSize = 16, - CupSize = 28 -}; - -layout.Children.Add(button); -``` - #### Properties | Property | Type | Default | Description | |----------|------|---------|-------------| | `Username` | `string` | `""` | Your Buy Me a Coffee username/slug | | `ButtonText` | `string` | `"Buy me a coffee"` | Button label text | -| `Theme` | `BmcButtonTheme` | `Yellow` | Color theme preset (Yellow, Black, White, Blue, Violet, Orange, Red, Green, Custom) | +| `Theme` | `BmcButtonTheme` | `Yellow` | Color theme preset | | `CustomBackgroundColor` | `Color?` | `null` | Background color (Custom theme only) | | `CustomTextColor` | `Color?` | `null` | Text color (Custom theme only) | | `CornerRadius` | `double` | `8.0` | Border corner radius | | `FontSize` | `double` | `16.0` | Button text font size | | `CupSize` | `double` | `28.0` | Coffee cup logo height | +| `SupporterEmail` | `string?` | `null` | Email to check against supporters list | +| `HideIfSupporter` | `bool` | `false` | Auto-hide if email is a verified supporter | ### BuyMeACoffeeQrCode @@ -168,7 +167,7 @@ Generates a QR code linking to your BMC profile with the coffee cup logo overlai #### Basic Usage ```xml - ``` @@ -176,7 +175,7 @@ Generates a QR code linking to your BMC profile with the coffee cup logo overlai #### With Custom Colors ```xml - ``` -#### Code-Behind Example - -```csharp -using BuyMeCofee.Maui.Controls; - -var qrCode = new BuyMeACoffeeQrCode -{ - Username = "yourname", - Size = 250, - ForegroundColor = Colors.Black, - BackgroundColor = Colors.White, - LogoSizeFraction = 0.25 -}; - -layout.Children.Add(qrCode); -``` - #### Properties | Property | Type | Default | Description | @@ -210,6 +192,8 @@ layout.Children.Add(qrCode); | `ForegroundColor` | `Color` | `Black` | QR code module color | | `BackgroundColor` | `Color` | `White` | QR code background color | | `LogoSizeFraction` | `double` | `0.25` | Logo size as fraction of QR code (0.0-0.35) | +| `SupporterEmail` | `string?` | `null` | Email to check against supporters list | +| `HideIfSupporter` | `bool` | `false` | Auto-hide if email is a verified supporter | ### BuyMeACoffeeWidget @@ -218,7 +202,7 @@ A full-featured support widget with amount entry, preset chips, name/message fie #### Basic Usage ```xml - ``` @@ -226,7 +210,7 @@ A full-featured support widget with amount entry, preset chips, name/message fie #### With Custom Configuration ```xml - -``` - ```csharp private void OnSupportRequested(object sender, SupportRequestedEventArgs e) { @@ -254,37 +230,9 @@ private void OnSupportRequested(object sender, SupportRequestedEventArgs e) Console.WriteLine($"Amount: ${e.Amount}"); Console.WriteLine($"Message: {e.Message ?? "No message"}"); Console.WriteLine($"Monthly: {e.IsMonthly}"); - - // Log analytics, show confirmation, etc. } ``` -#### Code-Behind Example - -```csharp -using BuyMeCofee.Maui.Controls; -using BuyMeCofee.Maui.Models; - -var widget = new BuyMeACoffeeWidget -{ - Username = "yourname", - DisplayName = "John Doe", - DefaultAmount = 5, - SuggestedAmounts = new[] { 25, 50, 100 }, - AccentColor = Color.FromArgb("#6C5CE7"), - ShowMonthlyOption = true, - SupportButtonText = "Support" -}; - -widget.SupportRequested += (sender, e) => -{ - // Handle support request - Debug.WriteLine($"Amount: ${e.Amount}, Monthly: {e.IsMonthly}"); -}; - -layout.Children.Add(widget); -``` - #### Properties | Property | Type | Default | Description | @@ -296,6 +244,8 @@ layout.Children.Add(widget); | `AccentColor` | `Color` | `#6C5CE7` | Support button background color | | `ShowMonthlyOption` | `bool` | `true` | Show "Make this monthly" checkbox | | `SupportButtonText` | `string` | `"Support"` | Support button label text | +| `SupporterEmail` | `string?` | `null` | Email to check against supporters list | +| `HideIfSupporter` | `bool` | `false` | Auto-hide if email is a verified supporter | #### Events @@ -308,19 +258,53 @@ layout.Children.Add(widget); ```csharp public class SupportRequestedEventArgs : EventArgs { - public string Username { get; init; } // BMC username - public int Amount { get; init; } // Support amount - public string? Name { get; init; } // Supporter name (optional) - public string? Message { get; init; } // Support message (optional) - public bool IsMonthly { get; init; } // Monthly subscription toggle + public string Username { get; init; } + public int Amount { get; init; } + public string? Name { get; init; } + public string? Message { get; init; } + public bool IsMonthly { get; init; } } ``` +## Supporter Verification + +The library can automatically check if a user has already supported you via the Buy Me a Coffee API. When verified, controls can auto-hide themselves — useful for removing donation prompts for existing supporters. + +### Setup + +Get your API access token from [Buy Me a Coffee Developer Dashboard](https://developers.buymeacoffee.com/). + +Configure the token in `MauiProgram.cs`: + +```csharp +builder.UseBuyMeACoffee(options => +{ + options.AccessToken = "your_bmc_api_token"; + options.CacheDuration = TimeSpan.FromHours(1); // default +}); +``` + +### Usage + +Add `SupporterEmail` and `HideIfSupporter` to any control: + +```xml + +``` + +When `HideIfSupporter` is `true` and the email matches a one-time supporter or active member, the control sets `IsVisible = false`. + +The service queries both `/api/v1/supporters` (one-time) and `/api/v1/subscriptions` (members) endpoints, caches results in memory, and fails silently if the API is unreachable. + ## Theming ### Button Themes -The library includes 8 official Buy Me a Coffee color themes: +The library includes 8 official Buy Me a Coffee color themes plus a custom option: ```csharp public enum BmcButtonTheme @@ -337,38 +321,15 @@ public enum BmcButtonTheme } ``` -### Theme Examples - -```xml - - - - - - - - - - - - - - -``` - ### Brand Colors Reference ```csharp public static class BmcColors { - public const string CupYellow = "#FFDD00"; // Official cup color - public const string BrandDark = "#0D0C22"; // Brand dark - public const string WidgetPurple = "#6C5CE7"; // Widget accent - public const string WhiteStroke = "#E0E0E0"; // Border color + public const string CupYellow = "#FFDD00"; + public const string BrandDark = "#0D0C22"; + public const string WidgetPurple = "#6C5CE7"; + public const string WhiteStroke = "#E0E0E0"; } ``` @@ -377,40 +338,20 @@ public static class BmcColors ### Extension Methods ```csharp -public static class BuyMeACoffeeExtensions -{ - /// - /// Registers Buy Me a Coffee controls and their dependencies (SkiaSharp). - /// Call this in your MauiProgram.cs CreateMauiApp() builder. - /// - public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder); -} +// Basic registration (no API features) +public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder); + +// With API configuration (enables supporter verification) +public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder, Action configure); ``` -### Helper Classes - -#### BmcThemeResolver +### BmcOptions ```csharp -public static class BmcThemeResolver +public class BmcOptions { - /// - /// Resolves a BmcButtonTheme enum to theme colors. - /// - /// Thrown for Custom theme - public static BmcThemeInfo Resolve(BmcButtonTheme theme); -} - -public record BmcThemeInfo(Color Background, Color TextColor, Color StrokeColor); -``` - -#### BmcBrandAssets - -```csharp -internal static class BmcBrandAssets -{ - internal static ImageSource GetCupLogo(); - internal static Stream GetCupLogoStream(); + public string? AccessToken { get; set; } + public TimeSpan CacheDuration { get; set; } = TimeSpan.FromHours(1); } ``` @@ -432,189 +373,33 @@ public static class BmcConstants | Platform | Minimum Version | Status | |----------|----------------|--------| -| **Android** | API 21 (Android 5.0) | ✅ Supported | -| **iOS** | 15.0+ | ✅ Supported | -| **macOS** (Catalyst) | 15.0+ | ✅ Supported | -| **Windows** | 10.0.17763.0+ | ✅ Supported | - -**Note:** On Linux, only Android targets are built by default. iOS and macOS Catalyst require macOS build environment. +| **Android** | API 21 (Android 5.0) | Supported | +| **iOS** | 15.0+ | Supported | +| **macOS** (Catalyst) | 15.0+ | Supported | +| **Windows** | 10.0.17763.0+ | Supported | ## Dependencies -The library requires the following NuGet packages: - ```xml ``` -These are automatically installed when you reference the project. +## Support This Project -## Examples +If this library saved you time, consider buying me a coffee: -### Complete Page Example - -```xml - - - - - - - - - - - -``` - -### Dynamic Theme Switching - -```csharp -using BuyMeCofee.Maui.Controls; -using BuyMeCofee.Maui.Enums; - -public partial class ThemePage : ContentPage -{ - private BuyMeACoffeeButton _button; - private readonly BmcButtonTheme[] _themes = - { - BmcButtonTheme.Yellow, - BmcButtonTheme.Black, - BmcButtonTheme.Blue, - BmcButtonTheme.Violet, - BmcButtonTheme.Orange, - BmcButtonTheme.Red, - BmcButtonTheme.Green - }; - private int _currentThemeIndex = 0; - - public ThemePage() - { - InitializeComponent(); - - _button = new BuyMeACoffeeButton - { - Username = "yourname", - Theme = _themes[0] - }; - - var switchButton = new Button { Text = "Change Theme" }; - switchButton.Clicked += OnSwitchTheme; - - var layout = new VerticalStackLayout - { - Spacing = 20, - Padding = 20, - Children = { _button, switchButton } - }; - - Content = layout; - } - - private void OnSwitchTheme(object sender, EventArgs e) - { - _currentThemeIndex = (_currentThemeIndex + 1) % _themes.Length; - _button.Theme = _themes[_currentThemeIndex]; - } -} -``` - -### Analytics Integration - -```csharp -using BuyMeCofee.Maui.Controls; -using BuyMeCofee.Maui.Models; - -public partial class AnalyticsPage : ContentPage -{ - public AnalyticsPage() - { - InitializeComponent(); - - var widget = new BuyMeACoffeeWidget - { - Username = "yourname", - DisplayName = "John Doe" - }; - - widget.SupportRequested += OnSupportRequested; - - Content = widget; - } - - private void OnSupportRequested(object sender, SupportRequestedEventArgs e) - { - // Log to analytics service - AnalyticsService.TrackEvent("SupportRequested", new Dictionary - { - { "username", e.Username }, - { "amount", e.Amount.ToString() }, - { "is_monthly", e.IsMonthly.ToString() }, - { "has_name", (!string.IsNullOrEmpty(e.Name)).ToString() }, - { "has_message", (!string.IsNullOrEmpty(e.Message)).ToString() } - }); - - // Show confirmation - DisplayAlert("Thank You!", - $"You're about to support with ${e.Amount}. Redirecting to Buy Me a Coffee...", - "OK"); - } -} -``` +[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/logikonline) ## Contributing Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests. -### Development Setup - -1. Clone the repository -2. Open in Visual Studio 2022 or VS Code -3. Ensure .NET MAUI workload is installed -4. Build and run the sample project - ## License This project is licensed under the MIT License. See the LICENSE file for details. --- -**Not affiliated with Buy Me a Coffee.** This is an unofficial community library. Buy Me a Coffee and its branding are trademarks of Buy Me a Coffee LLC. \ No newline at end of file +**Not affiliated with Buy Me a Coffee.** This is an unofficial community library. Buy Me a Coffee and its branding are trademarks of Buy Me a Coffee LLC.