using System.Diagnostics; using System.IO; using System.Windows.Input; using ytLive.Helpers; namespace ytLive.ViewModels; public partial class MainViewModel : ViewModelBase { private bool _isSettingsOpen; private bool _isBugOpen; private bool _isFeatureOpen; private bool _isAboutOpen; private bool _isLicensesOpen; private string _overlayTitle = string.Empty; private string _bugReportText = string.Empty; private string _bugReportEmail = string.Empty; private string _featureRequestText = string.Empty; private string _featureRequestEmail = string.Empty; private const string SupportEmail = "gramps@llamachile.shop"; // The creator-hub link targets (About overlay) — public so the hub's // integration test can assert they point at real destinations. public const string ChannelUrl = "https://youtube.com/@llamachileshop"; public const string MastodonUrl = "https://mastodon.llamachile.tube/@gramps"; public const string PremiumUrl = "https://buy.polar.sh/polar_cl_ueMy8AyAClO0o0yo19IroZwKTbD587gzUsjEW3bFapw"; public const string CoffeeUrl = "https://buymeacoffee.com/llamachiley"; public static string AppVersionLabel => $"Version {typeof(MainViewModel).Assembly.GetName().Version?.ToString(3)}"; /// The About hub's Unlock Premium button lights up only once the /// billing URL is set (Polar — polar.sh, pending TASK 10 implementation). public bool IsPremiumAvailable => PremiumUrl.Length > 0; /// Full in-app licensing/legal text, loaded on demand from the /// shipped THIRD-PARTY-NOTICES.txt (never opens the OS viewer). public string LicensesText { get; private set; } = string.Empty; // Overlay panels public bool IsSettingsOpen { get => _isSettingsOpen; private set => SetPanel(ref _isSettingsOpen, value); } public bool IsBugOpen { get => _isBugOpen; private set => SetPanel(ref _isBugOpen, value); } public bool IsFeatureOpen { get => _isFeatureOpen; private set => SetPanel(ref _isFeatureOpen, value); } public bool IsAboutOpen { get => _isAboutOpen; private set => SetPanel(ref _isAboutOpen, value); } /// About hub sub-view: the full licensing/legal texts panel /// (in-app scrolling — no Notepad, no browser tab). public bool IsLicensesOpen { get => _isLicensesOpen; set { if (SetProperty(ref _isLicensesOpen, value)) OnPropertyChanged(nameof(IsHubVisible)); } } public bool IsAnyOverlayOpen => IsSettingsOpen || IsBugOpen || IsFeatureOpen || IsAboutOpen; public string OverlayTitle { get => _overlayTitle; private set => SetProperty(ref _overlayTitle, value); } public string BugReportText { get => _bugReportText; set => SetProperty(ref _bugReportText, value); } public string BugReportEmail { get => _bugReportEmail; set => SetProperty(ref _bugReportEmail, value); } public string FeatureRequestText { get => _featureRequestText; set => SetProperty(ref _featureRequestText, value); } public string FeatureRequestEmail { get => _featureRequestEmail; set => SetProperty(ref _featureRequestEmail, value); } public ICommand OpenSettingsCommand { get; } public ICommand OpenBugCommand { get; } public ICommand OpenFeatureCommand { get; } public ICommand OpenAboutCommand { get; } public ICommand CloseOverlayCommand { get; } public ICommand SubmitBugCommand { get; } public ICommand SubmitFeatureCommand { get; } public ICommand OpenChannelCommand { get; } public ICommand OpenMastodonCommand { get; } public ICommand OpenCoffeeCommand { get; } public ICommand OpenPremiumCommand { get; } public ICommand OpenLicensesCommand { get; } private void SetPanel(ref bool field, bool value, [System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { if (SetProperty(ref field, value, propertyName)) OnPropertyChanged(nameof(IsAnyOverlayOpen)); } private void ShowOverlay(string? panel = null, string title = "") { IsSettingsOpen = panel == nameof(IsSettingsOpen); IsBugOpen = panel == nameof(IsBugOpen); IsFeatureOpen = panel == nameof(IsFeatureOpen); IsAboutOpen = panel == nameof(IsAboutOpen); OverlayTitle = title; if (panel != nameof(IsAboutOpen)) IsLicensesOpen = false; } /// Shows the in-app licensing panel, loading the full texts from /// the shipped THIRD-PARTY-NOTICES.txt on first open (never the OS viewer). /// Falls back to a "not found" note if the file is missing so the panel /// never hangs on nothing. private void ShowLicenses() { if (string.IsNullOrEmpty(LicensesText)) { try { var path = Path.Combine(AppContext.BaseDirectory, "THIRD-PARTY-NOTICES.txt"); LicensesText = File.Exists(path) ? File.ReadAllText(path) : "THIRD-PARTY-NOTICES.txt not found next to the executable."; } catch (Exception ex) { LicensesText = $"Could not read THIRD-PARTY-NOTICES.txt: {ex.Message}"; } } IsLicensesOpen = true; } private void SubmitBug() { var body = string.Join(Environment.NewLine, BugReportText.Trim(), string.Empty, $"LlamaCasty {AppVersionLabel}", string.IsNullOrWhiteSpace(BugReportEmail) ? string.Empty : $"Reply-to: {BugReportEmail.Trim()}"); ComposeEmail("[LlamaCasty Bug Report]", body); ShowOverlay(); } private void SubmitFeature() { var body = string.Join(Environment.NewLine, FeatureRequestText.Trim(), string.Empty, $"LlamaCasty {AppVersionLabel}", string.IsNullOrWhiteSpace(FeatureRequestEmail) ? string.Empty : $"Reply-to: {FeatureRequestEmail.Trim()}"); ComposeEmail("[LlamaCasty Feature Request]", body); ShowOverlay(); } private static void ComposeEmail(string subject, string body) { var uri = $"mailto:{SupportEmail}?subject={Uri.EscapeDataString(subject)}&body={Uri.EscapeDataString(body)}"; OpenUrl(uri); } private static void OpenUrl(string url) { Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); } }