feat: prepare Pro licensing and Android API 36

This commit is contained in:
livio committed 2026-09-28 09:50:58 +02:00
1 parent b600412285
commit 11a91863a3
25 files changed
+2447 -62

No files matched your search

+152
View File
@@ -0,0 +1,152 @@
namespace LMEMappeCatastaliApp.Services;
public sealed class EntitlementService(ManualLicenseService manualLicenses, IPlayBillingService playBilling)
{
public const string ProductId = "lme_pro";
public const string MonthlyBasePlanId = "monthly";
public const string YearlyBasePlanId = "yearly";
private const string GoogleProActivePreference = "entitlement_google_pro_active";
private const string GoogleExpiresAtPreference = "entitlement_google_pro_expires_at";
private const string GoogleLastVerifiedAtPreference = "entitlement_google_pro_last_verified_at";
private static readonly TimeSpan OfflineGracePeriod = TimeSpan.FromDays(7);
private static readonly HashSet<string> ProFeatures = new(StringComparer.Ordinal)
{
AppFeatures.ProjectArchive,
AppFeatures.Export,
AppFeatures.CloudSync,
AppFeatures.OfflineMaps,
AppFeatures.AdvancedGeometry,
AppFeatures.GpsTrail
};
public bool IsGoogleProActive { get; private set; }
public DateTimeOffset? GoogleExpiresAt { get; private set; }
public DateTimeOffset? GoogleLastVerifiedAt { get; private set; }
public ManualLicenseInfo? ManualLicense => manualLicenses.Current;
public string? ManualLicenseError => manualLicenses.LastError;
public bool HasGoogleProAccess
{
get
{
var now = DateTimeOffset.UtcNow;
if (!IsGoogleProActive || GoogleExpiresAt is { } expiresAt && expiresAt <= now)
{
return false;
}
return GoogleLastVerifiedAt is null
|| now - GoogleLastVerifiedAt <= OfflineGracePeriod;
}
}
public bool HasManualProAccess => manualLicenses.HasActiveLicense;
public bool HasProAccess => HasGoogleProAccess || HasManualProAccess;
public string PlanLabel => HasProAccess
? HasGoogleProAccess && HasManualProAccess ? "LME Pro (Google + manuale)"
: HasGoogleProAccess ? "LME Pro (Google Play)"
: "LME Pro (licenza manuale)"
: "Base";
public bool RequiresPro(string feature) => ProFeatures.Contains(feature);
public bool CanUse(string feature) => !RequiresPro(feature) || HasProAccess;
public async Task LoadAsync()
{
IsGoogleProActive = Preferences.Default.Get(GoogleProActivePreference, false);
GoogleExpiresAt = ReadDate(GoogleExpiresAtPreference);
GoogleLastVerifiedAt = ReadDate(GoogleLastVerifiedAtPreference);
await manualLicenses.LoadAsync();
}
public void ApplyVerifiedGooglePro(DateTimeOffset? expiresAt)
{
IsGoogleProActive = true;
GoogleExpiresAt = expiresAt;
GoogleLastVerifiedAt = DateTimeOffset.UtcNow;
SaveGoogleState();
}
public void ApplyGoogleBase()
{
IsGoogleProActive = false;
GoogleExpiresAt = null;
GoogleLastVerifiedAt = DateTimeOffset.UtcNow;
SaveGoogleState();
}
public Task<ManualLicenseActivationResult> ActivateManualLicenseAsync(string? token) =>
manualLicenses.ActivateAsync(token);
public Task RemoveManualLicenseAsync() => manualLicenses.RemoveAsync();
public async Task<PlayBillingResult> RestorePurchasesAsync()
{
var result = await playBilling.RestoreAsync();
ApplyPlayBillingResult(result);
return result;
}
public async Task<PlayBillingResult> PurchaseGoogleProAsync(string basePlanId)
{
var result = await playBilling.PurchaseAsync(basePlanId);
ApplyPlayBillingResult(result);
return result;
}
private void ApplyPlayBillingResult(PlayBillingResult result)
{
if (!result.Completed)
{
return;
}
if (result.HasActiveSubscription)
{
ApplyVerifiedGooglePro(null);
}
else
{
ApplyGoogleBase();
}
}
private void SaveGoogleState()
{
Preferences.Default.Set(GoogleProActivePreference, IsGoogleProActive);
WriteDate(GoogleExpiresAtPreference, GoogleExpiresAt);
WriteDate(GoogleLastVerifiedAtPreference, GoogleLastVerifiedAt);
}
private static DateTimeOffset? ReadDate(string key)
{
var value = Preferences.Default.Get(key, string.Empty);
return DateTimeOffset.TryParse(value, out var parsed) ? parsed : null;
}
private static void WriteDate(string key, DateTimeOffset? value)
{
if (value is null)
{
Preferences.Default.Remove(key);
return;
}
Preferences.Default.Set(key, value.Value.ToString("O"));
}
}
public static class AppFeatures
{
public const string CadastreMap = "cadastre-map";
public const string BasicSearch = "basic-search";
public const string DrawingMeasurements = "drawing-measurements";
public const string ProjectArchive = "project-archive";
public const string Export = "export";
public const string CloudSync = "cloud-sync";
public const string OfflineMaps = "offline-maps";
public const string AdvancedGeometry = "advanced-geometry";
public const string GpsTrail = "gps-trail";
}