Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a719eb7975 | |||
| be1fad6083 | |||
| 66c8ec8ecf |
14
.gitsecrets-ignore
Normal file
14
.gitsecrets-ignore
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# GitSecrets Ignore File
|
||||||
|
# This file tracks false positives identified by AI evaluation or manually marked.
|
||||||
|
# Each line is a JSON object with the following fields:
|
||||||
|
# - contentHash: SHA256 hash prefix of the secret content
|
||||||
|
# - patternId: The pattern that detected this secret
|
||||||
|
# - filePath: Relative path where the secret was found
|
||||||
|
# - reason: Why this was marked as a false positive
|
||||||
|
# - confidence: AI confidence level (if from AI evaluation)
|
||||||
|
# - addedAt: Timestamp when this entry was added
|
||||||
|
#
|
||||||
|
# You can safely commit this file to share false positive markers with your team.
|
||||||
|
# To remove an entry, simply delete the corresponding line.
|
||||||
|
|
||||||
|
{"contentHash":"57ca47aa82486958","patternId":"password-assignment","filePath":"..\\..\\..\\AppData\\Local\\GitCaddy\\app-4.7.1\\BuyMeCofee.Maui\\BuyMeACoffeeExtensions.cs","reason":"Manually marked as false positive","addedAt":1772602630533}
|
||||||
12
.gitsecretsconfig
Normal file
12
.gitsecretsconfig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"analyzedAt": 1772602627117,
|
||||||
|
"project": {
|
||||||
|
"type": "unknown"
|
||||||
|
},
|
||||||
|
"vaultSdk": {
|
||||||
|
"installed": false,
|
||||||
|
"usageLocations": []
|
||||||
|
},
|
||||||
|
"recommendedApproach": "unknown"
|
||||||
|
}
|
||||||
51
.notes/series-1772600279146-8a63f2.json
Normal file
51
.notes/series-1772600279146-8a63f2.json
Normal file
File diff suppressed because one or more lines are too long
55
.notes/series-1772600391995-ae1f6e.json
Normal file
55
.notes/series-1772600391995-ae1f6e.json
Normal file
File diff suppressed because one or more lines are too long
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;
|
using SkiaSharp.Views.Maui.Controls.Hosting;
|
||||||
|
|
||||||
namespace BuyMeCofee.Maui;
|
namespace BuyMeCofee.Maui;
|
||||||
@@ -13,4 +14,32 @@ public static class BuyMeACoffeeExtensions
|
|||||||
builder.UseSkiaSharp();
|
builder.UseSkiaSharp();
|
||||||
return builder;
|
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>
|
<RepositoryType>git</RepositoryType>
|
||||||
<PackageTags>maui;buymeacoffee;bmc;donation;tip;controls</PackageTags>
|
<PackageTags>maui;buymeacoffee;bmc;donation;tip;controls</PackageTags>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<Version>1.1.2</Version>
|
||||||
|
<PackageReleaseNotes>v1.1.2: Fix cup logo not showing in consuming projects (load from embedded high-res PNG instead of MAUI asset pipeline). Added CustomLogoSource property on Button and Widget to allow developers to override the default logo.</PackageReleaseNotes>
|
||||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||||
|
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||||
@@ -47,6 +49,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<MauiImage Include="Resources\Images\bmc_logo.svg" />
|
||||||
<EmbeddedResource Include="Resources\Images\bmc_logo.png"
|
<EmbeddedResource Include="Resources\Images\bmc_logo.png"
|
||||||
LogicalName="BuyMeCofee.Maui.Resources.Images.bmc_logo.png" />
|
LogicalName="BuyMeCofee.Maui.Resources.Images.bmc_logo.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -45,6 +45,18 @@ public class BuyMeACoffeeButton : ContentView
|
|||||||
BindableProperty.Create(nameof(CupSize), typeof(double), typeof(BuyMeACoffeeButton),
|
BindableProperty.Create(nameof(CupSize), typeof(double), typeof(BuyMeACoffeeButton),
|
||||||
28.0, propertyChanged: OnVisualPropertyChanged);
|
28.0, propertyChanged: OnVisualPropertyChanged);
|
||||||
|
|
||||||
|
public static readonly BindableProperty CustomLogoSourceProperty =
|
||||||
|
BindableProperty.Create(nameof(CustomLogoSource), typeof(ImageSource), typeof(BuyMeACoffeeButton),
|
||||||
|
null, 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
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -105,6 +117,32 @@ public class BuyMeACoffeeButton : ContentView
|
|||||||
set => SetValue(CupSizeProperty, value);
|
set => SetValue(CupSizeProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Optional custom logo ImageSource. When set, replaces the default BMC cup logo.</summary>
|
||||||
|
public ImageSource? CustomLogoSource
|
||||||
|
{
|
||||||
|
get => (ImageSource?)GetValue(CustomLogoSourceProperty);
|
||||||
|
set => SetValue(CustomLogoSourceProperty, 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
|
#endregion
|
||||||
|
|
||||||
private Border _border = null!;
|
private Border _border = null!;
|
||||||
@@ -202,6 +240,7 @@ public class BuyMeACoffeeButton : ContentView
|
|||||||
_textLabel.Text = ButtonText;
|
_textLabel.Text = ButtonText;
|
||||||
_textLabel.FontSize = FontSize;
|
_textLabel.FontSize = FontSize;
|
||||||
_cupImage.HeightRequest = CupSize;
|
_cupImage.HeightRequest = CupSize;
|
||||||
|
_cupImage.Source = CustomLogoSource ?? BmcBrandAssets.GetCupLogo();
|
||||||
|
|
||||||
if (_border.StrokeShape is RoundRectangle rr)
|
if (_border.StrokeShape is RoundRectangle rr)
|
||||||
rr.CornerRadius = new Microsoft.Maui.CornerRadius(CornerRadius);
|
rr.CornerRadius = new Microsoft.Maui.CornerRadius(CornerRadius);
|
||||||
@@ -230,4 +269,10 @@ public class BuyMeACoffeeButton : ContentView
|
|||||||
if (bindable is BuyMeACoffeeButton button)
|
if (bindable is BuyMeACoffeeButton button)
|
||||||
button.ApplyTheme();
|
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),
|
BindableProperty.Create(nameof(LogoSizeFraction), typeof(double), typeof(BuyMeACoffeeQrCode),
|
||||||
0.25, propertyChanged: OnVisualPropertyChanged);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -67,6 +75,25 @@ public class BuyMeACoffeeQrCode : ContentView
|
|||||||
set => SetValue(BackgroundColorProperty, value);
|
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>
|
/// <summary>Logo size as fraction of QR code size (0.0 - 0.35). Default: 0.25</summary>
|
||||||
public double LogoSizeFraction
|
public double LogoSizeFraction
|
||||||
{
|
{
|
||||||
@@ -99,6 +126,11 @@ public class BuyMeACoffeeQrCode : ContentView
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stream = BmcBrandAssets.GetCupLogoStream();
|
using var stream = BmcBrandAssets.GetCupLogoStream();
|
||||||
|
if (stream is null)
|
||||||
|
{
|
||||||
|
_logoBitmap = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
_logoBitmap = SKBitmap.Decode(stream);
|
_logoBitmap = SKBitmap.Decode(stream);
|
||||||
}
|
}
|
||||||
catch
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,18 @@ public class BuyMeACoffeeWidget : ContentView
|
|||||||
BindableProperty.Create(nameof(SupportButtonText), typeof(string), typeof(BuyMeACoffeeWidget),
|
BindableProperty.Create(nameof(SupportButtonText), typeof(string), typeof(BuyMeACoffeeWidget),
|
||||||
BmcConstants.DefaultSupportButtonText);
|
BmcConstants.DefaultSupportButtonText);
|
||||||
|
|
||||||
|
public static readonly BindableProperty CustomLogoSourceProperty =
|
||||||
|
BindableProperty.Create(nameof(CustomLogoSource), typeof(ImageSource), typeof(BuyMeACoffeeWidget),
|
||||||
|
null, propertyChanged: OnLogoChanged);
|
||||||
|
|
||||||
|
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
|
#endregion
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -94,6 +106,32 @@ public class BuyMeACoffeeWidget : ContentView
|
|||||||
set => SetValue(SupportButtonTextProperty, value);
|
set => SetValue(SupportButtonTextProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Optional custom logo ImageSource. When set, replaces the default BMC cup logo in the footer.</summary>
|
||||||
|
public ImageSource? CustomLogoSource
|
||||||
|
{
|
||||||
|
get => (ImageSource?)GetValue(CustomLogoSourceProperty);
|
||||||
|
set => SetValue(CustomLogoSourceProperty, 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
|
#endregion
|
||||||
|
|
||||||
/// <summary>Raised when the user taps the Support button, before opening the browser.</summary>
|
/// <summary>Raised when the user taps the Support button, before opening the browser.</summary>
|
||||||
@@ -110,6 +148,7 @@ public class BuyMeACoffeeWidget : ContentView
|
|||||||
private Border _supportButton = null!;
|
private Border _supportButton = null!;
|
||||||
private Label _supportButtonLabel = null!;
|
private Label _supportButtonLabel = null!;
|
||||||
private Label _footerLabel = null!;
|
private Label _footerLabel = null!;
|
||||||
|
private Image _footerCup = null!;
|
||||||
|
|
||||||
private int _currentAmount;
|
private int _currentAmount;
|
||||||
|
|
||||||
@@ -247,9 +286,9 @@ public class BuyMeACoffeeWidget : ContentView
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
var footerCup = new Image
|
_footerCup = new Image
|
||||||
{
|
{
|
||||||
Source = BmcBrandAssets.GetCupLogo(),
|
Source = CustomLogoSource ?? BmcBrandAssets.GetCupLogo(),
|
||||||
HeightRequest = 16,
|
HeightRequest = 16,
|
||||||
Aspect = Aspect.AspectFit,
|
Aspect = Aspect.AspectFit,
|
||||||
VerticalOptions = LayoutOptions.Center,
|
VerticalOptions = LayoutOptions.Center,
|
||||||
@@ -266,7 +305,7 @@ public class BuyMeACoffeeWidget : ContentView
|
|||||||
{
|
{
|
||||||
Spacing = 4,
|
Spacing = 4,
|
||||||
HorizontalOptions = LayoutOptions.Center,
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
Children = { footerCup, _footerLabel }
|
Children = { _footerCup, _footerLabel }
|
||||||
};
|
};
|
||||||
var footerTap = new TapGestureRecognizer();
|
var footerTap = new TapGestureRecognizer();
|
||||||
footerTap.Tapped += OnFooterTapped;
|
footerTap.Tapped += OnFooterTapped;
|
||||||
@@ -438,5 +477,17 @@ public class BuyMeACoffeeWidget : ContentView
|
|||||||
widget._monthlyRow.IsVisible = (bool)newValue;
|
widget._monthlyRow.IsVisible = (bool)newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnLogoChanged(BindableObject bindable, object oldValue, object newValue)
|
||||||
|
{
|
||||||
|
if (bindable is BuyMeACoffeeWidget widget)
|
||||||
|
widget._footerCup.Source = (ImageSource?)newValue ?? BmcBrandAssets.GetCupLogo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnSupporterPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
||||||
|
{
|
||||||
|
if (bindable is BuyMeACoffeeWidget widget)
|
||||||
|
BmcSupporterCheck.CheckAndHide(widget, widget.SupporterEmail, widget.HideIfSupporter);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,34 @@ namespace BuyMeCofee.Maui.Helpers;
|
|||||||
|
|
||||||
internal static class BmcBrandAssets
|
internal static class BmcBrandAssets
|
||||||
{
|
{
|
||||||
private const string LogoResourceName = "BuyMeCofee.Maui.Resources.Images.bmc_logo.png";
|
// Embedded PNG resource name — travels with the NuGet package assembly
|
||||||
|
private const string EmbeddedLogoName = "BuyMeCofee.Maui.Resources.Images.bmc_logo.png";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cup logo as an ImageSource for MAUI Image controls.
|
||||||
|
/// Loads from embedded PNG resource so it works in consuming NuGet projects.
|
||||||
|
/// </summary>
|
||||||
internal static ImageSource GetCupLogo()
|
internal static ImageSource GetCupLogo()
|
||||||
{
|
{
|
||||||
var assembly = typeof(BmcBrandAssets).Assembly;
|
return ImageSource.FromStream(() =>
|
||||||
return ImageSource.FromStream(() => assembly.GetManifestResourceStream(LogoResourceName)!);
|
{
|
||||||
|
var assembly = typeof(BmcBrandAssets).Assembly;
|
||||||
|
return assembly.GetManifestResourceStream(EmbeddedLogoName)!;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 95 KiB |
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
433
README.md
433
README.md
@@ -3,7 +3,7 @@
|
|||||||
[](https://dotnet.microsoft.com/apps/maui)
|
[](https://dotnet.microsoft.com/apps/maui)
|
||||||
[](https://dotnet.microsoft.com/apps/maui)
|
[](https://dotnet.microsoft.com/apps/maui)
|
||||||
[](LICENSE)
|
[](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.
|
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)
|
- [BuyMeACoffeeButton](#buymeacoffeebutton)
|
||||||
- [BuyMeACoffeeQrCode](#buymeacoffeeqrcode)
|
- [BuyMeACoffeeQrCode](#buymeacoffeeqrcode)
|
||||||
- [BuyMeACoffeeWidget](#buymeacoffeewidget)
|
- [BuyMeACoffeeWidget](#buymeacoffeewidget)
|
||||||
|
- [Supporter Verification](#supporter-verification)
|
||||||
- [Theming](#theming)
|
- [Theming](#theming)
|
||||||
- [API Reference](#api-reference)
|
- [API Reference](#api-reference)
|
||||||
- [Platform Support](#platform-support)
|
- [Platform Support](#platform-support)
|
||||||
@@ -26,14 +27,15 @@ A comprehensive .NET MAUI library for integrating Buy Me a Coffee support into y
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
✅ **BuyMeACoffeeButton** - Branded button with official coffee cup logo and 8 color themes
|
- **BuyMeACoffeeButton** - Branded button with official coffee cup logo and 8 color themes
|
||||||
✅ **BuyMeACoffeeQrCode** - QR code generator with embedded BMC logo overlay
|
- **BuyMeACoffeeQrCode** - QR code generator with embedded BMC logo overlay
|
||||||
✅ **BuyMeACoffeeWidget** - Full-featured support widget with amount selection, name/message fields, and monthly toggle
|
- **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
|
- **Official Branding** - Crisp SVG-based logos via MAUI asset pipeline, authentic Buy Me a Coffee colors and styling
|
||||||
✅ **Cross-Platform** - Supports Android, iOS, macOS, and Windows
|
- **Supporter Verification** - Auto-hide controls for users who have already supported you via the BMC API
|
||||||
✅ **Customizable** - Extensive theming and styling options
|
- **Cross-Platform** - Supports Android, iOS, macOS, and Windows
|
||||||
✅ **Touch Optimized** - Smooth animations and hover effects
|
- **Customizable** - Extensive theming and styling options
|
||||||
✅ **QR Code Generation** - High-quality QR codes with error correction level H
|
- **Touch Optimized** - Smooth animations and hover effects
|
||||||
|
- **QR Code Generation** - High-quality QR codes with error correction level H
|
||||||
|
|
||||||
## Installation
|
## 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 10.0 SDK or later
|
||||||
- .NET MAUI workload installed
|
- .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
|
```xml
|
||||||
<ItemGroup>
|
<configuration>
|
||||||
<ProjectReference Include="..\BuyMeCofee.Maui\BuyMeCofee.Maui.csproj" />
|
<packageSources>
|
||||||
</ItemGroup>
|
<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
|
```csharp
|
||||||
using BuyMeCofee.Maui;
|
using BuyMeCofee.Maui;
|
||||||
@@ -66,7 +82,7 @@ public static class MauiProgram
|
|||||||
builder
|
builder
|
||||||
.UseMauiApp<App>()
|
.UseMauiApp<App>()
|
||||||
.UseBuyMeACoffee(); // Add this line
|
.UseBuyMeACoffee(); // Add this line
|
||||||
|
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +101,7 @@ xmlns:bmc="clr-namespace:BuyMeCofee.Maui.Controls;assembly=BuyMeCofee.Maui"
|
|||||||
Add a button to your page:
|
Add a button to your page:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeButton
|
<bmc:BuyMeACoffeeButton
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
ButtonText="Buy me a coffee"
|
ButtonText="Buy me a coffee"
|
||||||
Theme="Yellow" />
|
Theme="Yellow" />
|
||||||
@@ -100,7 +116,7 @@ A branded button that opens your Buy Me a Coffee page in the default browser.
|
|||||||
#### Basic Usage
|
#### Basic Usage
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeButton
|
<bmc:BuyMeACoffeeButton
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
Theme="Yellow" />
|
Theme="Yellow" />
|
||||||
```
|
```
|
||||||
@@ -108,7 +124,7 @@ A branded button that opens your Buy Me a Coffee page in the default browser.
|
|||||||
#### With Custom Styling
|
#### With Custom Styling
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeButton
|
<bmc:BuyMeACoffeeButton
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
ButtonText="Support My Work"
|
ButtonText="Support My Work"
|
||||||
Theme="Violet"
|
Theme="Violet"
|
||||||
@@ -121,7 +137,7 @@ A branded button that opens your Buy Me a Coffee page in the default browser.
|
|||||||
#### Custom Theme
|
#### Custom Theme
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeButton
|
<bmc:BuyMeACoffeeButton
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
Theme="Custom"
|
Theme="Custom"
|
||||||
CustomBackgroundColor="#FF6B6B"
|
CustomBackgroundColor="#FF6B6B"
|
||||||
@@ -129,37 +145,20 @@ A branded button that opens your Buy Me a Coffee page in the default browser.
|
|||||||
ButtonText="Donate" />
|
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
|
#### Properties
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
|----------|------|---------|-------------|
|
|----------|------|---------|-------------|
|
||||||
| `Username` | `string` | `""` | Your Buy Me a Coffee username/slug |
|
| `Username` | `string` | `""` | Your Buy Me a Coffee username/slug |
|
||||||
| `ButtonText` | `string` | `"Buy me a coffee"` | Button label text |
|
| `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) |
|
| `CustomBackgroundColor` | `Color?` | `null` | Background color (Custom theme only) |
|
||||||
| `CustomTextColor` | `Color?` | `null` | Text color (Custom theme only) |
|
| `CustomTextColor` | `Color?` | `null` | Text color (Custom theme only) |
|
||||||
| `CornerRadius` | `double` | `8.0` | Border corner radius |
|
| `CornerRadius` | `double` | `8.0` | Border corner radius |
|
||||||
| `FontSize` | `double` | `16.0` | Button text font size |
|
| `FontSize` | `double` | `16.0` | Button text font size |
|
||||||
| `CupSize` | `double` | `28.0` | Coffee cup logo height |
|
| `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
|
### BuyMeACoffeeQrCode
|
||||||
|
|
||||||
@@ -168,7 +167,7 @@ Generates a QR code linking to your BMC profile with the coffee cup logo overlai
|
|||||||
#### Basic Usage
|
#### Basic Usage
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeQrCode
|
<bmc:BuyMeACoffeeQrCode
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
Size="250" />
|
Size="250" />
|
||||||
```
|
```
|
||||||
@@ -176,7 +175,7 @@ Generates a QR code linking to your BMC profile with the coffee cup logo overlai
|
|||||||
#### With Custom Colors
|
#### With Custom Colors
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeQrCode
|
<bmc:BuyMeACoffeeQrCode
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
Size="300"
|
Size="300"
|
||||||
ForegroundColor="DarkBlue"
|
ForegroundColor="DarkBlue"
|
||||||
@@ -184,23 +183,6 @@ Generates a QR code linking to your BMC profile with the coffee cup logo overlai
|
|||||||
LogoSizeFraction="0.3" />
|
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
|
#### Properties
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
@@ -210,6 +192,8 @@ layout.Children.Add(qrCode);
|
|||||||
| `ForegroundColor` | `Color` | `Black` | QR code module color |
|
| `ForegroundColor` | `Color` | `Black` | QR code module color |
|
||||||
| `BackgroundColor` | `Color` | `White` | QR code background color |
|
| `BackgroundColor` | `Color` | `White` | QR code background color |
|
||||||
| `LogoSizeFraction` | `double` | `0.25` | Logo size as fraction of QR code (0.0-0.35) |
|
| `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
|
### BuyMeACoffeeWidget
|
||||||
|
|
||||||
@@ -218,7 +202,7 @@ A full-featured support widget with amount entry, preset chips, name/message fie
|
|||||||
#### Basic Usage
|
#### Basic Usage
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeWidget
|
<bmc:BuyMeACoffeeWidget
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
DisplayName="John Doe" />
|
DisplayName="John Doe" />
|
||||||
```
|
```
|
||||||
@@ -226,7 +210,7 @@ A full-featured support widget with amount entry, preset chips, name/message fie
|
|||||||
#### With Custom Configuration
|
#### With Custom Configuration
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<bmc:BuyMeACoffeeWidget
|
<bmc:BuyMeACoffeeWidget
|
||||||
Username="yourname"
|
Username="yourname"
|
||||||
DisplayName="John Doe"
|
DisplayName="John Doe"
|
||||||
DefaultAmount="10"
|
DefaultAmount="10"
|
||||||
@@ -239,14 +223,6 @@ A full-featured support widget with amount entry, preset chips, name/message fie
|
|||||||
|
|
||||||
#### Handling Support Events
|
#### Handling Support Events
|
||||||
|
|
||||||
```xml
|
|
||||||
<bmc:BuyMeACoffeeWidget
|
|
||||||
x:Name="supportWidget"
|
|
||||||
Username="yourname"
|
|
||||||
DisplayName="John Doe"
|
|
||||||
SupportRequested="OnSupportRequested" />
|
|
||||||
```
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
private void OnSupportRequested(object sender, SupportRequestedEventArgs e)
|
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($"Amount: ${e.Amount}");
|
||||||
Console.WriteLine($"Message: {e.Message ?? "No message"}");
|
Console.WriteLine($"Message: {e.Message ?? "No message"}");
|
||||||
Console.WriteLine($"Monthly: {e.IsMonthly}");
|
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
|
#### Properties
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
@@ -296,6 +244,8 @@ layout.Children.Add(widget);
|
|||||||
| `AccentColor` | `Color` | `#6C5CE7` | Support button background color |
|
| `AccentColor` | `Color` | `#6C5CE7` | Support button background color |
|
||||||
| `ShowMonthlyOption` | `bool` | `true` | Show "Make this monthly" checkbox |
|
| `ShowMonthlyOption` | `bool` | `true` | Show "Make this monthly" checkbox |
|
||||||
| `SupportButtonText` | `string` | `"Support"` | Support button label text |
|
| `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
|
#### Events
|
||||||
|
|
||||||
@@ -308,19 +258,53 @@ layout.Children.Add(widget);
|
|||||||
```csharp
|
```csharp
|
||||||
public class SupportRequestedEventArgs : EventArgs
|
public class SupportRequestedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public string Username { get; init; } // BMC username
|
public string Username { get; init; }
|
||||||
public int Amount { get; init; } // Support amount
|
public int Amount { get; init; }
|
||||||
public string? Name { get; init; } // Supporter name (optional)
|
public string? Name { get; init; }
|
||||||
public string? Message { get; init; } // Support message (optional)
|
public string? Message { get; init; }
|
||||||
public bool IsMonthly { get; init; } // Monthly subscription toggle
|
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
|
## Theming
|
||||||
|
|
||||||
### Button Themes
|
### 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
|
```csharp
|
||||||
public enum BmcButtonTheme
|
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
|
### Brand Colors Reference
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public static class BmcColors
|
public static class BmcColors
|
||||||
{
|
{
|
||||||
public const string CupYellow = "#FFDD00"; // Official cup color
|
public const string CupYellow = "#FFDD00";
|
||||||
public const string BrandDark = "#0D0C22"; // Brand dark
|
public const string BrandDark = "#0D0C22";
|
||||||
public const string WidgetPurple = "#6C5CE7"; // Widget accent
|
public const string WidgetPurple = "#6C5CE7";
|
||||||
public const string WhiteStroke = "#E0E0E0"; // Border color
|
public const string WhiteStroke = "#E0E0E0";
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -377,40 +338,20 @@ public static class BmcColors
|
|||||||
### Extension Methods
|
### Extension Methods
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public static class BuyMeACoffeeExtensions
|
// Basic registration (no API features)
|
||||||
{
|
public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder);
|
||||||
/// <summary>
|
|
||||||
/// Registers Buy Me a Coffee controls and their dependencies (SkiaSharp).
|
// With API configuration (enables supporter verification)
|
||||||
/// Call this in your MauiProgram.cs CreateMauiApp() builder.
|
public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder, Action<BmcOptions> configure);
|
||||||
/// </summary>
|
|
||||||
public static MauiAppBuilder UseBuyMeACoffee(this MauiAppBuilder builder);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Helper Classes
|
### BmcOptions
|
||||||
|
|
||||||
#### BmcThemeResolver
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public static class BmcThemeResolver
|
public class BmcOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
public string? AccessToken { get; set; }
|
||||||
/// Resolves a BmcButtonTheme enum to theme colors.
|
public TimeSpan CacheDuration { get; set; } = TimeSpan.FromHours(1);
|
||||||
/// </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();
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -432,189 +373,33 @@ public static class BmcConstants
|
|||||||
|
|
||||||
| Platform | Minimum Version | Status |
|
| Platform | Minimum Version | Status |
|
||||||
|----------|----------------|--------|
|
|----------|----------------|--------|
|
||||||
| **Android** | API 21 (Android 5.0) | ✅ Supported |
|
| **Android** | API 21 (Android 5.0) | Supported |
|
||||||
| **iOS** | 15.0+ | ✅ Supported |
|
| **iOS** | 15.0+ | Supported |
|
||||||
| **macOS** (Catalyst) | 15.0+ | ✅ Supported |
|
| **macOS** (Catalyst) | 15.0+ | Supported |
|
||||||
| **Windows** | 10.0.17763.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.
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
The library requires the following NuGet packages:
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="3.116.1" />
|
<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
|
[](https://buymeacoffee.com/logikonline)
|
||||||
|
|
||||||
```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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
|
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
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License. See the LICENSE file for details.
|
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.
|
**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.
|
||||||
|
|||||||
Reference in New Issue
Block a user