using System.Collections.ObjectModel; using System.Linq; using ytLive.Helpers; using ytLive.Models; namespace ytLive.ViewModels; /// /// The left panel's alert stack (creator design, 2026-11-05): standing state on top, transient events /// beneath it, newest first, pushing the panel's content down. Only red rows belong here — the panel /// is where "something needs you" lives, so it never fills with chatter. /// Row 1 is the Steam licence indicator and nothing displaces it. It is pinned while /// higher output tiers are unavailable, and becomes dismissible — permanently — once ownership is /// verified. If ownership is later lost, it comes back as a pinned indicator, because that is a change /// of state rather than a preference. /// public partial class MainViewModel { private const string SteamLicenceAlertId = "steam-licence"; private readonly ObservableCollection _attentionAlerts = new(); private bool _steamLicenceDismissed; public ObservableCollection AttentionAlerts => _attentionAlerts; public bool HasAttentionAlerts => _attentionAlerts.Count > 0; public RelayCommand DismissAlertCommand => new(DismissAlert); public RelayCommand ActivateAlertCommand => new(ActivateAlert); /// The licence indicator's text (creator wording, 2026-11-05): the limit this build runs /// at, plus where to find out why. Not a warning — the resolution picker explains the detail, and /// clicking the row opens About. private string SteamLicenceText() { var entitled = 30; if (QualityOptions.Length > 0) { entitled = QualityOptions .Where(option => HasProOutput || !option.RequiresPro) .Select(option => option.Fps) .DefaultIfEmpty(30) .Max(); } return $"Steam ID Limit: {entitled}fps\nClick for More Info"; } /// Recomputes row 1 in place. Called at startup and whenever ownership changes. private void RefreshSteamLicenceAlert() { var existing = _attentionAlerts.FirstOrDefault(alert => alert.Id == SteamLicenceAlertId); var hidden = IsAppOwned && _steamLicenceDismissed; if (hidden) { if (existing != null) _attentionAlerts.Remove(existing); RaiseAttentionChanged(); return; } var alert = new AttentionAlert( SteamLicenceAlertId, SteamLicenceText(), IsPinned: !IsAppOwned, AttentionAlertAction.About); var index = existing == null ? -1 : _attentionAlerts.IndexOf(existing); if (index < 0) _attentionAlerts.Insert(0, alert); else _attentionAlerts[index] = alert; // same row, updated state RaiseAttentionChanged(); } /// Pushes an event alert. Newest sits directly under the licence indicator; older ones /// stack beneath, so the newest is the one nearest the eye. private AttentionAlert? PushAttentionAlert(string id, string text, long logFrom, long logTo) { var alert = new AttentionAlert(id, text, IsPinned: false, AttentionAlertAction.Logs, logFrom, logTo); var insertAt = _attentionAlerts.Count(alert => alert.Id == SteamLicenceAlertId); var existing = _attentionAlerts.FirstOrDefault(candidate => candidate.Id == id); if (existing != null) { // Same event advancing (reconnecting attempt 2 → 3): update the row, never duplicate it. _attentionAlerts[_attentionAlerts.IndexOf(existing)] = alert; } else { _attentionAlerts.Insert(insertAt, alert); } RaiseAttentionChanged(); return alert; } /// Test seam: pushes an event row without a stream session behind it. internal AttentionAlert? PushAttentionAlertForTest(string id, string text, long logFrom, long logTo) => PushAttentionAlert(id, text, logFrom, logTo); private void RemoveAttentionAlert(string id) { var existing = _attentionAlerts.FirstOrDefault(alert => alert.Id == id); if (existing == null) return; _attentionAlerts.Remove(existing); RaiseAttentionChanged(); } private void DismissAlert(AttentionAlert? alert) { if (alert == null || alert.IsPinned) return; if (alert.Id == SteamLicenceAlertId) { // Permanent for this profile: the creator said "I have seen it". Only honoured while // ownership holds — RefreshSteamLicenceAlert ignores it when Steam stops confirming. _steamLicenceDismissed = true; _layoutStore.SaveSteamLicenceIndicatorDismissed(true); } RemoveAttentionAlert(alert.Id); } private void ActivateAlert(AttentionAlert? alert) { if (alert == null) return; if (alert.Action == AttentionAlertAction.About) { ShowAbout(); return; } // Event alert: open the log and paint this event's lines, so the alert is an entry point to // the detail rather than a dead end. HighlightLogRange(alert.LogFrom, alert.LogTo); SelectLogsTab(true); } private void RaiseAttentionChanged() => OnPropertyChanged(nameof(HasAttentionAlerts)); }