using System.Collections.ObjectModel; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using RblxTool.Models; using RblxTool.Services; namespace RblxTool.Views; public sealed partial class SweepPage : Page { private const int MaxFeedLines = 4000; private readonly ObservableCollection _feed = new(); private JobRunner? _runner; private DispatcherTimer? _metricsTimer; public SweepPage() { InitializeComponent(); FeedList.ItemsSource = _feed; Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { _runner = AppState.Current.CreateRunner(DispatcherQueue); _runner.EventReceived += OnEvent; _runner.Completed += OnCompleted; _metricsTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(750) }; _metricsTimer.Tick += (_, _) => RefreshMetrics(); } private void OnUnloaded(object sender, RoutedEventArgs e) { _metricsTimer?.Stop(); _runner?.Stop(); } private void OnStart(object sender, RoutedEventArgs e) { if (AppState.Current.Active is null) { Append(new FeedLine(" select an account first", FeedTone.Bad)); return; } var request = new SweepRequest { MaxPrice = (ulong)Math.Max(0, MaxPriceBox.Value), Pages = (int)Math.Max(1, PagesBox.Value), Limit = (int)Math.Max(0, LimitBox.Value), ScanThreads = (int)Math.Max(1, ScanThreadsBox.Value), DetailThreads = (int)Math.Max(1, DetailThreadsBox.Value), BuyThreads = (int)Math.Max(1, BuyThreadsBox.Value), Buy = BuyToggle.IsOn, DryRun = DryRunToggle.IsOn, SkipOwned = SkipOwnedToggle.IsOn, IncludeCollectibles = CollectiblesToggle.IsOn, }; _feed.Clear(); Append(new FeedLine( $" sweeping as {AppState.Current.Active.Username} (max {request.MaxPrice} R$)", FeedTone.Accent)); var jobId = AppState.Current.Engine.StartSweep(request); _runner?.Start(jobId); StartButton.IsEnabled = false; StopButton.IsEnabled = true; _metricsTimer?.Start(); } private void OnStop(object sender, RoutedEventArgs e) { _runner?.Cancel(); Append(new FeedLine(" stopping after in flight requests settle", FeedTone.Warn)); } private void OnEvent(JobEvent item) { if (item.Type == "progress") { ScannedText.Text = item.Scanned.ToString("N0"); MatchedText.Text = item.Matched.ToString("N0"); return; } if (item.Type == "purchase" && item.Success) { PurchasedText.Text = (ulong.Parse(PurchasedText.Text.Replace(",", "")) + 1).ToString("N0"); } Append(FeedLine.From(item)); } private void OnCompleted(string result) { StartButton.IsEnabled = true; StopButton.IsEnabled = false; _metricsTimer?.Stop(); RefreshMetrics(); Append(new FeedLine(" job finished", FeedTone.Good)); } private void RefreshMetrics() { var metrics = AppState.Current.Engine.Metrics(); RequestsText.Text = metrics.Requests.ToString("N0"); LatencyText.Text = $"{metrics.AverageLatencyMs:F0} ms"; } private void Append(FeedLine line) { if (_feed.Count >= MaxFeedLines) { _feed.RemoveAt(0); } _feed.Add(line); if (_feed.Count > 0) { FeedList.ScrollIntoView(_feed[^1]); } } }