using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Poe2Trade.Bot; using Poe2Trade.Core; using Serilog; namespace Poe2Trade.Ui.ViewModels; public class LogEntry { public string Time { get; init; } = ""; public string Level { get; init; } = ""; public string Message { get; init; } = ""; } public partial class CellState : ObservableObject { [ObservableProperty] private bool _isOccupied; [ObservableProperty] private bool _borderTop; [ObservableProperty] private bool _borderBottom; [ObservableProperty] private bool _borderLeft; [ObservableProperty] private bool _borderRight; } public partial class MainWindowViewModel : ObservableObject { private readonly BotOrchestrator _bot; [ObservableProperty] private string _state = "Idle"; [ObservableProperty] [NotifyPropertyChangedFor(nameof(PauseButtonText))] private bool _isPaused; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(StartCommand))] [NotifyCanExecuteChangedFor(nameof(PauseCommand))] private bool _isStarted; [ObservableProperty] private string _newUrl = ""; [ObservableProperty] private string _newLinkName = ""; [ObservableProperty] private LinkMode _newLinkMode = LinkMode.Live; [ObservableProperty] private int _tradesCompleted; [ObservableProperty] private int _tradesFailed; [ObservableProperty] private int _activeLinksCount; public static LinkMode[] LinkModes { get; } = [LinkMode.Live, LinkMode.Scrap]; public MainWindowViewModel(BotOrchestrator bot) { _bot = bot; _isPaused = bot.IsPaused; for (var i = 0; i < 60; i++) InventoryCells.Add(new CellState()); bot.StatusUpdated += () => { Avalonia.Threading.Dispatcher.UIThread.Post(() => { State = bot.State; IsPaused = bot.IsPaused; var status = bot.GetStatus(); TradesCompleted = status.TradesCompleted; TradesFailed = status.TradesFailed; ActiveLinksCount = status.Links.Count(l => l.Active); OnPropertyChanged(nameof(Links)); UpdateInventoryGrid(); }); }; bot.LogMessage += (level, message) => { Avalonia.Threading.Dispatcher.UIThread.Post(() => { Logs.Add(new LogEntry { Time = DateTime.Now.ToString("HH:mm:ss"), Level = level.ToUpperInvariant(), Message = message }); if (Logs.Count > 500) Logs.RemoveAt(0); }); }; } public string PauseButtonText => IsPaused ? "Resume" : "Pause"; public List Links => _bot.Links.GetLinks(); public ObservableCollection Logs { get; } = []; public ObservableCollection InventoryCells { get; } = []; public int InventoryFreeCells => _bot.IsReady ? _bot.Inventory.Tracker.FreeCells : 60; // Sub-ViewModels for tabs public DebugViewModel? DebugVm { get; set; } public SettingsViewModel? SettingsVm { get; set; } [RelayCommand(CanExecute = nameof(CanStart))] private async Task Start() { try { await _bot.Start([]); IsStarted = true; } catch (Exception ex) { Log.Error(ex, "Failed to start bot"); Logs.Add(new LogEntry { Time = DateTime.Now.ToString("HH:mm:ss"), Level = "ERROR", Message = $"Start failed: {ex.Message}" }); } } private bool CanStart() => !IsStarted; [RelayCommand(CanExecute = nameof(CanPause))] private void Pause() { if (_bot.IsPaused) _bot.Resume(); else _bot.Pause(); } private bool CanPause() => IsStarted; [RelayCommand] private void AddLink() { if (string.IsNullOrWhiteSpace(NewUrl)) return; _bot.AddLink(NewUrl, NewLinkName, NewLinkMode); NewUrl = ""; NewLinkName = ""; } [RelayCommand] private void RemoveLink(string? id) { if (id != null) _bot.RemoveLink(id); } [RelayCommand] private void ToggleLink(string? id) { if (id == null) return; var link = _bot.Links.GetLink(id); if (link != null) _bot.ToggleLink(id, !link.Active); } private void UpdateInventoryGrid() { if (!_bot.IsReady) return; var (grid, items, _) = _bot.Inventory.GetInventoryState(); for (var r = 0; r < 5; r++) for (var c = 0; c < 12; c++) { var cell = InventoryCells[r * 12 + c]; cell.IsOccupied = grid[r, c]; cell.BorderTop = false; cell.BorderBottom = false; cell.BorderLeft = false; cell.BorderRight = false; } foreach (var item in items) { for (var r = item.Row; r < item.Row + item.H; r++) for (var c = item.Col; c < item.Col + item.W; c++) { if (r >= 5 || c >= 12) continue; var cell = InventoryCells[r * 12 + c]; if (r == item.Row) cell.BorderTop = true; if (r == item.Row + item.H - 1) cell.BorderBottom = true; if (c == item.Col) cell.BorderLeft = true; if (c == item.Col + item.W - 1) cell.BorderRight = true; } } OnPropertyChanged(nameof(InventoryFreeCells)); } }