feat(ci): add supporter verification and auto-hide functionality
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.
This commit is contained in:
15
BuyMeCofee.Maui/BmcConfiguration.cs
Normal file
15
BuyMeCofee.Maui/BmcConfiguration.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using BuyMeCofee.Maui.Services;
|
||||
|
||||
namespace BuyMeCofee.Maui;
|
||||
|
||||
/// <summary>
|
||||
/// Static configuration holder for the Buy Me a Coffee library.
|
||||
/// Populated by <see cref="BuyMeACoffeeExtensions.UseBuyMeACoffee"/>.
|
||||
/// </summary>
|
||||
public static class BmcConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The supporter lookup service. Null if no AccessToken was configured.
|
||||
/// </summary>
|
||||
public static BmcSupporterService? SupporterService { get; internal set; }
|
||||
}
|
||||
18
BuyMeCofee.Maui/BmcOptions.cs
Normal file
18
BuyMeCofee.Maui/BmcOptions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace BuyMeCofee.Maui;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for the Buy Me a Coffee library.
|
||||
/// </summary>
|
||||
public class BmcOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Your Buy Me a Coffee API access token (from https://developers.buymeacoffee.com).
|
||||
/// Required for supporter verification. This is the creator's token.
|
||||
/// </summary>
|
||||
public string? AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How long to cache the supporter list before re-fetching. Default: 1 hour.
|
||||
/// </summary>
|
||||
public TimeSpan CacheDuration { get; set; } = TimeSpan.FromHours(1);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// builder.UseBuyMeACoffee(options =>
|
||||
/// {
|
||||
/// options.AccessToken = "your-bmc-api-token";
|
||||
/// options.CacheDuration = TimeSpan.FromHours(2);
|
||||
/// });
|
||||
/// </example>
|
||||
public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder, Action<BmcOptions> configure)
|
||||
{
|
||||
builder.UseSkiaSharp();
|
||||
|
||||
var options = new BmcOptions();
|
||||
configure(options);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.AccessToken))
|
||||
{
|
||||
BmcConfiguration.SupporterService = new BmcSupporterService(
|
||||
options.AccessToken, options.CacheDuration);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>maui;buymeacoffee;bmc;donation;tip;controls</PackageTags>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<Version>1.1.0</Version>
|
||||
<PackageReleaseNotes>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.</PackageReleaseNotes>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||
@@ -47,6 +49,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MauiImage Include="Resources\Images\bmc_logo.svg" />
|
||||
<EmbeddedResource Include="Resources\Images\bmc_logo.png"
|
||||
LogicalName="BuyMeCofee.Maui.Resources.Images.bmc_logo.png" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <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!;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>Logo size as fraction of QR code size (0.0 - 0.35). Default: 0.25</summary>
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <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
|
||||
|
||||
/// <summary>Raised when the user taps the Support button, before opening the browser.</summary>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cup logo as an ImageSource for MAUI Image controls.
|
||||
/// Uses the MAUI asset pipeline (SVG → crisp multi-DPI PNG).
|
||||
/// </summary>
|
||||
internal static ImageSource GetCupLogo()
|
||||
{
|
||||
var assembly = typeof(BmcBrandAssets).Assembly;
|
||||
return ImageSource.FromStream(() => assembly.GetManifestResourceStream(LogoResourceName)!);
|
||||
return ImageSource.FromFile(LogoFileName);
|
||||
}
|
||||
|
||||
internal static Stream GetCupLogoStream()
|
||||
/// <summary>
|
||||
/// Returns the cup logo as a raw stream for SkiaSharp rendering (QR code overlay).
|
||||
/// Falls back to embedded PNG resource.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
26
BuyMeCofee.Maui/Helpers/BmcSupporterCheck.cs
Normal file
26
BuyMeCofee.Maui/Helpers/BmcSupporterCheck.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BuyMeCofee.Maui.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Shared logic for controls to check supporter status and auto-hide.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
16
BuyMeCofee.Maui/Resources/Images/bmc_logo.svg
Normal file
16
BuyMeCofee.Maui/Resources/Images/bmc_logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.0 KiB |
93
BuyMeCofee.Maui/Services/BmcSupporterService.cs
Normal file
93
BuyMeCofee.Maui/Services/BmcSupporterService.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace BuyMeCofee.Maui.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Checks the Buy Me a Coffee API to determine whether a given email
|
||||
/// belongs to a supporter (one-time or member). Results are cached.
|
||||
/// </summary>
|
||||
public class BmcSupporterService
|
||||
{
|
||||
private const string ApiBase = "https://developers.buymeacoffee.com/api/v1/";
|
||||
|
||||
private readonly HttpClient _http;
|
||||
private readonly TimeSpan _cacheDuration;
|
||||
private HashSet<string>? _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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given email matches any one-time supporter
|
||||
/// or active/inactive member on the creator's BMC account.
|
||||
/// </summary>
|
||||
public async Task<bool> IsSupporterAsync(string email)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(email)) return false;
|
||||
|
||||
var emails = await GetAllSupporterEmailsAsync();
|
||||
return emails.Contains(email.Trim());
|
||||
}
|
||||
|
||||
private async Task<HashSet<string>> GetAllSupporterEmailsAsync()
|
||||
{
|
||||
if (_cachedEmails != null && DateTime.UtcNow < _cacheExpiry)
|
||||
return _cachedEmails;
|
||||
|
||||
var emails = new HashSet<string>(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<string> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
413
README.md
413
README.md
@@ -3,7 +3,7 @@
|
||||
[](https://dotnet.microsoft.com/apps/maui)
|
||||
[](https://dotnet.microsoft.com/apps/maui)
|
||||
[](LICENSE)
|
||||
[](https://www.nuget.org)
|
||||
[](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
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BuyMeCofee.Maui\BuyMeCofee.Maui.csproj" />
|
||||
</ItemGroup>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="MarketAlly" value="https://git.marketally.com/api/packages/misc/nuget/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
2. **Register the library** in your `MauiProgram.cs`:
|
||||
Then add the package reference:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="BuyMeCofee.Maui" Version="1.1.0" />
|
||||
```
|
||||
|
||||
Or via CLI:
|
||||
|
||||
```bash
|
||||
dotnet add package BuyMeCofee.Maui --version 1.1.0
|
||||
```
|
||||
|
||||
### Register in MauiProgram.cs
|
||||
|
||||
```csharp
|
||||
using BuyMeCofee.Maui;
|
||||
@@ -129,37 +145,20 @@ A branded button that opens your Buy Me a Coffee page in the default browser.
|
||||
ButtonText="Donate" />
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
@@ -184,23 +183,6 @@ Generates a QR code linking to your BMC profile with the coffee cup logo overlai
|
||||
LogoSizeFraction="0.3" />
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
@@ -239,14 +223,6 @@ A full-featured support widget with amount entry, preset chips, name/message fie
|
||||
|
||||
#### Handling Support Events
|
||||
|
||||
```xml
|
||||
<bmc:BuyMeACoffeeWidget
|
||||
x:Name="supportWidget"
|
||||
Username="yourname"
|
||||
DisplayName="John Doe"
|
||||
SupportRequested="OnSupportRequested" />
|
||||
```
|
||||
|
||||
```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
|
||||
<bmc:BuyMeACoffeeButton
|
||||
Username="yourname"
|
||||
Theme="Yellow"
|
||||
SupporterEmail="{Binding CurrentUserEmail}"
|
||||
HideIfSupporter="True" />
|
||||
```
|
||||
|
||||
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
|
||||
<!-- Official Yellow Theme -->
|
||||
<bmc:BuyMeACoffeeButton Username="yourname" Theme="Yellow" />
|
||||
|
||||
<!-- Dark Theme -->
|
||||
<bmc:BuyMeACoffeeButton Username="yourname" Theme="Black" />
|
||||
|
||||
<!-- Light Theme with Border -->
|
||||
<bmc:BuyMeACoffeeButton Username="yourname" Theme="White" />
|
||||
|
||||
<!-- Violet Theme -->
|
||||
<bmc:BuyMeACoffeeButton Username="yourname" Theme="Violet" />
|
||||
|
||||
<!-- Custom Theme -->
|
||||
<bmc:BuyMeACoffeeButton
|
||||
Username="yourname"
|
||||
Theme="Custom"
|
||||
CustomBackgroundColor="#FF1744"
|
||||
CustomTextColor="White" />
|
||||
```
|
||||
|
||||
### 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers Buy Me a Coffee controls and their dependencies (SkiaSharp).
|
||||
/// Call this in your MauiProgram.cs CreateMauiApp() builder.
|
||||
/// </summary>
|
||||
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<BmcOptions> configure);
|
||||
```
|
||||
|
||||
### Helper Classes
|
||||
|
||||
#### BmcThemeResolver
|
||||
### BmcOptions
|
||||
|
||||
```csharp
|
||||
public static class BmcThemeResolver
|
||||
public class BmcOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a BmcButtonTheme enum to theme colors.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown for Custom theme</exception>
|
||||
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,185 +373,29 @@ 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
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="3.116.1" />
|
||||
```
|
||||
|
||||
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
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:bmc="clr-namespace:BuyMeCofee.Maui.Controls;assembly=BuyMeCofee.Maui"
|
||||
x:Class="MyApp.SupportPage"
|
||||
Title="Support">
|
||||
|
||||
<ScrollView>
|
||||
<VerticalStackLayout Spacing="20" Padding="20">
|
||||
|
||||
<!-- Button Examples -->
|
||||
<Label Text="Button Themes" FontSize="20" FontAttributes="Bold" />
|
||||
|
||||
<bmc:BuyMeACoffeeButton
|
||||
Username="yourname"
|
||||
Theme="Yellow"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<bmc:BuyMeACoffeeButton
|
||||
Username="yourname"
|
||||
Theme="Violet"
|
||||
ButtonText="Support My Work"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<!-- QR Code -->
|
||||
<Label Text="QR Code" FontSize="20" FontAttributes="Bold" Margin="0,20,0,0" />
|
||||
|
||||
<bmc:BuyMeACoffeeQrCode
|
||||
Username="yourname"
|
||||
Size="250"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
<!-- Widget -->
|
||||
<Label Text="Support Widget" FontSize="20" FontAttributes="Bold" Margin="0,20,0,0" />
|
||||
|
||||
<bmc:BuyMeACoffeeWidget
|
||||
Username="yourname"
|
||||
DisplayName="John Doe"
|
||||
DefaultAmount="10"
|
||||
SuggestedAmounts="10,25,50"
|
||||
SupportRequested="OnSupportRequested"
|
||||
HorizontalOptions="Center" />
|
||||
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
|
||||
</ContentPage>
|
||||
```
|
||||
|
||||
### 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<string, string>
|
||||
{
|
||||
{ "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");
|
||||
}
|
||||
}
|
||||
```
|
||||
[](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.
|
||||
|
||||
Reference in New Issue
Block a user