using ytLive.Helpers; using ytLive.Services; namespace ytLive.ViewModels; public partial class MainViewModel { // TASK 10 — Polar license validation private string _licenseKey = string.Empty; private string _licenseMessage = string.Empty; private bool _isLicenseValidating; private bool _isLicenseEntryOpen; private static readonly TimeSpan OfflineGracePeriod = TimeSpan.FromDays(14); /// True when the user holds a valid LlamaCasty license (Polar). When /// false, the branding flash runs on every live stream. private bool _isPremium; public bool IsPremium { get => _isPremium; private set { if (!SetProperty(ref _isPremium, value)) return; // Flip the branding flash when premium state changes. BrandFlashEnabled = !value; } } // TASK 10 — license key entry public string LicenseKey { get => _licenseKey; set => SetProperty(ref _licenseKey, value); } public string LicenseMessage { get => _licenseMessage; private set => SetProperty(ref _licenseMessage, value); } public bool IsLicenseValidating { get => _isLicenseValidating; private set => SetProperty(ref _isLicenseValidating, value); } public bool IsLicenseEntryOpen { get => _isLicenseEntryOpen; set { if (SetProperty(ref _isLicenseEntryOpen, value)) OnPropertyChanged(nameof(IsHubVisible)); } } /// The About hub is visible only when neither the licenses panel /// nor the license entry panel is open. public bool IsHubVisible => !IsLicensesOpen && !IsLicenseEntryOpen; // ─── TASK 10 — Polar license validation ─── /// At startup, load the cached license state and re-validate online /// if a key exists. If offline and within the 14-day grace period, the cached /// premium state is trusted. If the grace period is exceeded, premium lapses. private async Task ValidateLicenseAtStartupAsync() { var savedKey = _layoutStore.LoadLicenseKey(); if (string.IsNullOrWhiteSpace(savedKey)) { IsPremium = false; return; } LicenseKey = savedKey; var validatedAt = _layoutStore.LoadLicenseValidatedAt(); var cachedPremium = _layoutStore.LoadIsPremium(); // If the last validation was within the grace period, trust the cache // while we attempt an online re-validation in the background. if (validatedAt.HasValue && DateTime.UtcNow - validatedAt.Value < OfflineGracePeriod) { IsPremium = cachedPremium; } // Try an online check — if it succeeds, it overrides the cache. try { var service = new PolarLicenseService(); var result = await service.ValidateAsync(savedKey); if (result.IsValid) { IsPremium = true; _layoutStore.SaveIsPremium(true); _layoutStore.SaveLicenseValidatedAt(DateTime.UtcNow); } else { // Key revoked or expired — clear premium. IsPremium = false; _layoutStore.SaveIsPremium(false); _layoutStore.SaveLicenseValidatedAt(DateTime.UtcNow); AppLog.Write($"License validation failed: {result.Error}"); _notifications.Warning("Premium is no longer active", $"{result.Error} — watermark branding returns until the license is renewed."); } } catch (Exception ex) { // Offline or network error — keep the cached state, log it. AppLog.Write($"License re-validation skipped (offline?): {ex.Message}"); if (!validatedAt.HasValue || DateTime.UtcNow - validatedAt.Value >= OfflineGracePeriod) { IsPremium = false; _layoutStore.SaveIsPremium(false); } } } /// Called from the Activate button in the About overlay. Validates /// the entered key, persists it, and flips premium state. private async Task ActivateLicenseAsync() { if (string.IsNullOrWhiteSpace(LicenseKey)) return; IsLicenseValidating = true; LicenseMessage = "Validating\u2026"; try { var service = new PolarLicenseService(); var result = await service.ValidateAsync(LicenseKey); if (result.IsValid) { IsPremium = true; _layoutStore.SaveLicenseKey(LicenseKey.Trim()); _layoutStore.SaveIsPremium(true); _layoutStore.SaveLicenseValidatedAt(DateTime.UtcNow); LicenseMessage = "Premium activated! The watermark has been removed."; AppLog.Write("License activated successfully"); } else { IsPremium = false; _layoutStore.SaveLicenseKey(null); _layoutStore.SaveIsPremium(false); LicenseMessage = result.Error ?? "Invalid license key."; AppLog.Write($"License activation failed: {result.Error}"); } } catch (Exception ex) { LicenseMessage = $"Activation error: {ex.Message}"; AppLog.Write($"License activation error: {ex}"); } finally { IsLicenseValidating = false; } } }