From 299c08248c4025be646b8b89213ccec8a97fd75b Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 16 Apr 2026 02:06:33 +0100 Subject: [PATCH] refactor: remove PhotoAnalysisView and SingleItemView (replaced by NewListingView) --- EbayListingTool/Views/PhotoAnalysisView.xaml | 682 ----------------- .../Views/PhotoAnalysisView.xaml.cs | 710 ------------------ EbayListingTool/Views/SingleItemView.xaml | 566 -------------- EbayListingTool/Views/SingleItemView.xaml.cs | 694 ----------------- 4 files changed, 2652 deletions(-) delete mode 100644 EbayListingTool/Views/PhotoAnalysisView.xaml delete mode 100644 EbayListingTool/Views/PhotoAnalysisView.xaml.cs delete mode 100644 EbayListingTool/Views/SingleItemView.xaml delete mode 100644 EbayListingTool/Views/SingleItemView.xaml.cs diff --git a/EbayListingTool/Views/PhotoAnalysisView.xaml b/EbayListingTool/Views/PhotoAnalysisView.xaml deleted file mode 100644 index 7da77e3..0000000 --- a/EbayListingTool/Views/PhotoAnalysisView.xaml +++ /dev/null @@ -1,682 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/EbayListingTool/Views/SingleItemView.xaml.cs b/EbayListingTool/Views/SingleItemView.xaml.cs deleted file mode 100644 index bce2b3d..0000000 --- a/EbayListingTool/Views/SingleItemView.xaml.cs +++ /dev/null @@ -1,694 +0,0 @@ -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media.Imaging; -using EbayListingTool.Models; -using EbayListingTool.Services; -using Microsoft.Win32; - -namespace EbayListingTool.Views; - -public partial class SingleItemView : UserControl -{ - private EbayListingService? _listingService; - private EbayCategoryService? _categoryService; - private AiAssistantService? _aiService; - private EbayAuthService? _auth; - - private ListingDraft _draft = new(); - private System.Threading.CancellationTokenSource? _categoryCts; - private bool _suppressCategoryLookup; - private string _suggestedPriceValue = ""; - - // Photo drag-reorder - private Point _dragStartPoint; - private bool _isDragging; - - public SingleItemView() - { - InitializeComponent(); - PostcodeBox.TextChanged += (s, e) => _draft.Postcode = PostcodeBox.Text; - } - - private void UserControl_Loaded(object sender, RoutedEventArgs e) - { - // Re-run the count bar calculations now that the layout has rendered - // and the track Border has a non-zero ActualWidth. - TitleBox_TextChanged(this, null!); - DescriptionBox_TextChanged(this, null!); - } - - public void Initialise(EbayListingService listingService, EbayCategoryService categoryService, - AiAssistantService aiService, EbayAuthService auth) - { - _listingService = listingService; - _categoryService = categoryService; - _aiService = aiService; - _auth = auth; - - PostcodeBox.Text = App.Configuration["Ebay:DefaultPostcode"] ?? ""; - } - - /// Pre-fills the form from a Photo Analysis result. - public async void PopulateFromAnalysis(PhotoAnalysisResult result, IReadOnlyList imagePaths, decimal price) - { - // Q6: reset form directly — calling NewListing_Click shows a confirmation dialog which - // is unexpected when arriving here automatically from the Photo Analysis tab. - _draft = new ListingDraft { Postcode = PostcodeBox.Text }; - TitleBox.Text = ""; - DescriptionBox.Text = ""; - CategoryBox.Text = ""; - CategoryIdLabel.Text = "(no category)"; - PriceBox.Value = 0; - QuantityBox.Value = 1; - ConditionBox.SelectedIndex = 3; // Used - FormatBox.SelectedIndex = 0; - PhotosPanel.Children.Clear(); - UpdatePhotoPanel(); - SuccessPanel.Visibility = Visibility.Collapsed; - PriceSuggestionPanel.Visibility = Visibility.Collapsed; - - TitleBox.Text = result.Title; - DescriptionBox.Text = result.Description; - PriceBox.Value = (double)price; - - // Auto-fill the top eBay category from the analysis keyword; user can override - await AutoFillCategoryAsync(result.CategoryKeyword); - - // Q1: load all photos from analysis - var validPaths = imagePaths.Where(p => !string.IsNullOrEmpty(p) && File.Exists(p)).ToArray(); - if (validPaths.Length > 0) - AddPhotos(validPaths); - } - - // ---- Title ---- - - private void TitleBox_TextChanged(object sender, TextChangedEventArgs e) - { - _draft.Title = TitleBox.Text; - var len = TitleBox.Text.Length; - TitleCount.Text = $"{len} / 80"; - - var overLimit = len > 75; - TitleCount.Foreground = overLimit - ? System.Windows.Media.Brushes.OrangeRed - : (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Gray5"); - - // Update the progress bar fill width proportionally - var trackBorder = TitleCountBar.Parent as Border; - double trackWidth = trackBorder?.ActualWidth ?? 0; - if (trackWidth > 0) - TitleCountBar.Width = trackWidth * (len / 80.0); - - TitleCountBar.Background = overLimit - ? System.Windows.Media.Brushes.OrangeRed - : (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Accent"); - } - - private async void AiTitle_Click(object sender, RoutedEventArgs e) - { - if (_aiService == null) return; - var condition = GetSelectedCondition().ToString(); - var current = TitleBox.Text; - - SetTitleSpinner(true); - SetBusy(true, "Generating title..."); - try - { - var title = await _aiService.GenerateTitleAsync(current, condition); - TitleBox.Text = title.Trim().TrimEnd('.').Trim('"'); - - // Auto-fill category from the generated title if not already set - if (string.IsNullOrWhiteSpace(_draft.CategoryId)) - await AutoFillCategoryAsync(TitleBox.Text); - } - catch (Exception ex) - { - ShowError("AI Title", ex.Message); - } - finally { SetBusy(false); SetTitleSpinner(false); } - } - - // ---- Category ---- - - private async void CategoryBox_TextChanged(object sender, TextChangedEventArgs e) - { - if (_suppressCategoryLookup) return; - - _categoryCts?.Cancel(); - _categoryCts?.Dispose(); - _categoryCts = new System.Threading.CancellationTokenSource(); - var cts = _categoryCts; - - if (CategoryBox.Text.Length < 3) - { - CategorySuggestionsList.Visibility = Visibility.Collapsed; - return; - } - - try - { - await Task.Delay(400, cts.Token); - } - catch (OperationCanceledException) - { - return; - } - - if (cts.IsCancellationRequested) return; - - try - { - var suggestions = await _categoryService!.GetCategorySuggestionsAsync(CategoryBox.Text); - if (cts.IsCancellationRequested) return; - - Dispatcher.Invoke(() => - { - CategorySuggestionsList.ItemsSource = suggestions; - CategorySuggestionsList.Visibility = suggestions.Count > 0 - ? Visibility.Visible : Visibility.Collapsed; - }); - } - catch (OperationCanceledException) { /* superseded by newer keystroke */ } - catch { /* ignore transient network errors */ } - } - - private void DescriptionBox_TextChanged(object sender, TextChangedEventArgs e) - { - _draft.Description = DescriptionBox.Text; - var len = DescriptionBox.Text.Length; - var softCap = 2000; - DescCount.Text = $"{len} / {softCap}"; - - var overLimit = len > softCap; - DescCount.Foreground = overLimit - ? System.Windows.Media.Brushes.OrangeRed - : (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Gray5"); - - var trackBorder = DescCountBar.Parent as Border; - double trackWidth = trackBorder?.ActualWidth ?? 0; - if (trackWidth > 0) - DescCountBar.Width = Math.Min(trackWidth, trackWidth * (len / (double)softCap)); - - DescCountBar.Background = overLimit - ? System.Windows.Media.Brushes.OrangeRed - : new System.Windows.Media.SolidColorBrush( - System.Windows.Media.Color.FromRgb(0xF5, 0x9E, 0x0B)); // amber - } - - private void CategoryBox_KeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Escape) - { - CategorySuggestionsList.Visibility = Visibility.Collapsed; - e.Handled = true; - } - } - - private void CategorySuggestionsList_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (CategorySuggestionsList.SelectedItem is CategorySuggestion cat) - { - _draft.CategoryId = cat.CategoryId; - _draft.CategoryName = cat.CategoryName; - CategoryBox.Text = cat.CategoryName; - CategoryIdLabel.Text = $"ID: {cat.CategoryId}"; - CategorySuggestionsList.Visibility = Visibility.Collapsed; - } - } - - /// - /// Fetches the top eBay category suggestion for and auto-fills - /// the category fields. The suggestions list is shown so the user can override. - /// - private async Task AutoFillCategoryAsync(string keyword) - { - if (_categoryService == null || string.IsNullOrWhiteSpace(keyword)) return; - - try - { - var suggestions = await _categoryService.GetCategorySuggestionsAsync(keyword); - if (suggestions.Count == 0) return; - - var top = suggestions[0]; - _suppressCategoryLookup = true; - try - { - _draft.CategoryId = top.CategoryId; - _draft.CategoryName = top.CategoryName; - CategoryBox.Text = top.CategoryName; - CategoryIdLabel.Text = $"ID: {top.CategoryId}"; - } - finally { _suppressCategoryLookup = false; } - - // Show the full list so user can see alternatives and override - CategorySuggestionsList.ItemsSource = suggestions; - CategorySuggestionsList.Visibility = suggestions.Count > 1 - ? Visibility.Visible : Visibility.Collapsed; - } - catch { /* non-critical — leave category blank if lookup fails */ } - } - - // ---- Condition ---- - - private void ConditionBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - _draft.Condition = GetSelectedCondition(); - } - - private ItemCondition GetSelectedCondition() - { - var tag = (ConditionBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "Used"; - return tag switch - { - "New" => ItemCondition.New, - "OpenBox" => ItemCondition.OpenBox, - "Refurbished" => ItemCondition.Refurbished, - "ForParts" => ItemCondition.ForPartsOrNotWorking, - _ => ItemCondition.Used - }; - } - - // ---- Description ---- - - private async void AiDescription_Click(object sender, RoutedEventArgs e) - { - if (_aiService == null) return; - SetDescSpinner(true); - SetBusy(true, "Writing description..."); - try - { - var description = await _aiService.WriteDescriptionAsync( - TitleBox.Text, GetSelectedCondition().ToString(), DescriptionBox.Text); - DescriptionBox.Text = description; - } - catch (Exception ex) { ShowError("AI Description", ex.Message); } - finally { SetBusy(false); SetDescSpinner(false); } - } - - // ---- Price ---- - - private async void AiPrice_Click(object sender, RoutedEventArgs e) - { - if (_aiService == null) return; - SetPriceSpinner(true); - SetBusy(true, "Researching price..."); - try - { - var result = await _aiService.SuggestPriceAsync( - TitleBox.Text, GetSelectedCondition().ToString()); - PriceSuggestionText.Text = result; - - // Extract price line for "Use this price" - var lines = result.Split('\n', StringSplitOptions.RemoveEmptyEntries); - var priceLine = lines.FirstOrDefault(l => l.StartsWith("PRICE:", StringComparison.OrdinalIgnoreCase)); - _suggestedPriceValue = priceLine?.Replace("PRICE:", "", StringComparison.OrdinalIgnoreCase).Trim() ?? ""; - - PriceSuggestionPanel.Visibility = Visibility.Visible; - } - catch (Exception ex) { ShowError("AI Price", ex.Message); } - finally { SetBusy(false); SetPriceSpinner(false); } - } - - private void UseSuggestedPrice_Click(object sender, RoutedEventArgs e) - { - if (decimal.TryParse(_suggestedPriceValue, out var price)) - PriceBox.Value = (double)price; - } - - // ---- Photos ---- - - private void Photos_DragOver(object sender, DragEventArgs e) - { - e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) - ? DragDropEffects.Copy : DragDropEffects.None; - e.Handled = true; - } - - private void Photos_Drop(object sender, DragEventArgs e) - { - if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return; - var files = (string[])e.Data.GetData(DataFormats.FileDrop); - // Remove highlight when drop completes - DropZone.BorderBrush = (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Gray7"); - DropZone.Background = (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Gray10"); - AddPhotos(files); - } - - private void DropZone_DragEnter(object sender, DragEventArgs e) - { - if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return; - DropZone.BorderBrush = (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Accent"); - DropZone.Background = new System.Windows.Media.SolidColorBrush( - System.Windows.Media.Color.FromArgb(20, 0x5C, 0x6B, 0xC0)); // subtle indigo tint - } - - private void DropZone_DragLeave(object sender, DragEventArgs e) - { - DropZone.BorderBrush = (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Gray7"); - DropZone.Background = (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Gray10"); - } - - private void BrowsePhotos_Click(object sender, RoutedEventArgs e) - { - var dlg = new OpenFileDialog - { - Title = "Select photos", - Filter = "Images|*.jpg;*.jpeg;*.png;*.gif;*.bmp|All files|*.*", - Multiselect = true - }; - if (dlg.ShowDialog() == true) - AddPhotos(dlg.FileNames); - } - - private void AddPhotos(string[] paths) - { - var imageExts = new HashSet(StringComparer.OrdinalIgnoreCase) - { ".jpg", ".jpeg", ".png", ".gif", ".bmp" }; - - foreach (var path in paths) - { - if (!imageExts.Contains(Path.GetExtension(path))) continue; - if (_draft.PhotoPaths.Count >= 12) break; - if (_draft.PhotoPaths.Contains(path)) continue; - _draft.PhotoPaths.Add(path); - } - - RebuildPhotoThumbnails(); - } - - /// - /// Clears and recreates all photo thumbnails from . - /// Called after any add, remove, or reorder operation so the panel always matches the list. - /// - private void RebuildPhotoThumbnails() - { - PhotosPanel.Children.Clear(); - for (int i = 0; i < _draft.PhotoPaths.Count; i++) - AddPhotoThumbnail(_draft.PhotoPaths[i], i); - UpdatePhotoPanel(); - } - - private void AddPhotoThumbnail(string path, int index) - { - try - { - var bmp = new BitmapImage(); - bmp.BeginInit(); - bmp.UriSource = new Uri(path, UriKind.Absolute); - bmp.DecodePixelWidth = 128; - bmp.CacheOption = BitmapCacheOption.OnLoad; - bmp.EndInit(); - bmp.Freeze(); - - var img = new System.Windows.Controls.Image - { - Width = 72, Height = 72, - Stretch = System.Windows.Media.Stretch.UniformToFill, - Source = bmp, - ToolTip = Path.GetFileName(path) - }; - img.Clip = new System.Windows.Media.RectangleGeometry(new Rect(0, 0, 72, 72), 4, 4); - - // Remove button - var removeBtn = new Button - { - Width = 18, Height = 18, - Cursor = Cursors.Hand, - ToolTip = "Remove photo", - HorizontalAlignment = HorizontalAlignment.Right, - VerticalAlignment = VerticalAlignment.Top, - Margin = new Thickness(0, 2, 2, 0), - Padding = new Thickness(0), - Background = new System.Windows.Media.SolidColorBrush( - System.Windows.Media.Color.FromArgb(200, 30, 30, 30)), - Foreground = System.Windows.Media.Brushes.White, - BorderThickness = new Thickness(0), - FontSize = 11, FontWeight = FontWeights.Bold, - Content = "✕", - Opacity = 0 - }; - removeBtn.Click += (s, e) => - { - e.Handled = true; // don't bubble and trigger drag - _draft.PhotoPaths.Remove(path); - RebuildPhotoThumbnails(); - }; - - // "Cover" badge on the first photo — it becomes the eBay gallery hero image - Border? coverBadge = null; - if (index == 0) - { - coverBadge = new Border - { - CornerRadius = new CornerRadius(3), - Background = new System.Windows.Media.SolidColorBrush( - System.Windows.Media.Color.FromArgb(210, 60, 90, 200)), - Padding = new Thickness(3, 1, 3, 1), - Margin = new Thickness(2, 2, 0, 0), - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - IsHitTestVisible = false, // don't block drag - Child = new TextBlock - { - Text = "Cover", - FontSize = 8, - FontWeight = FontWeights.SemiBold, - Foreground = System.Windows.Media.Brushes.White - } - }; - } - - var container = new Grid - { - Width = 72, Height = 72, - Margin = new Thickness(4), - Cursor = Cursors.SizeAll, // signal draggability - AllowDrop = true, - Tag = path // stable identifier used by drop handler - }; - container.Children.Add(img); - if (coverBadge != null) container.Children.Add(coverBadge); - container.Children.Add(removeBtn); - - // Hover: reveal remove button - container.MouseEnter += (s, e) => removeBtn.Opacity = 1; - container.MouseLeave += (s, e) => removeBtn.Opacity = 0; - - // Drag initiation - container.MouseLeftButtonDown += (s, e) => - { - _dragStartPoint = e.GetPosition(null); - }; - container.MouseMove += (s, e) => - { - if (e.LeftButton != MouseButtonState.Pressed || _isDragging) return; - var pos = e.GetPosition(null); - if (Math.Abs(pos.X - _dragStartPoint.X) > SystemParameters.MinimumHorizontalDragDistance || - Math.Abs(pos.Y - _dragStartPoint.Y) > SystemParameters.MinimumVerticalDragDistance) - { - _isDragging = true; - DragDrop.DoDragDrop(container, path, DragDropEffects.Move); - _isDragging = false; - } - }; - - // Drop target - container.DragOver += (s, e) => - { - if (e.Data.GetDataPresent(typeof(string)) && - (string)e.Data.GetData(typeof(string)) != path) - { - e.Effects = DragDropEffects.Move; - container.Opacity = 0.45; // dim to signal insertion point - } - else - { - e.Effects = DragDropEffects.None; - } - e.Handled = true; - }; - container.DragLeave += (s, e) => container.Opacity = 1.0; - container.Drop += (s, e) => - { - container.Opacity = 1.0; - if (!e.Data.GetDataPresent(typeof(string))) return; - - var sourcePath = (string)e.Data.GetData(typeof(string)); - var targetPath = (string)container.Tag; - if (sourcePath == targetPath) return; - - var sourceIdx = _draft.PhotoPaths.IndexOf(sourcePath); - var targetIdx = _draft.PhotoPaths.IndexOf(targetPath); - if (sourceIdx < 0 || targetIdx < 0) return; - - _draft.PhotoPaths.RemoveAt(sourceIdx); - _draft.PhotoPaths.Insert(targetIdx, sourcePath); - - RebuildPhotoThumbnails(); - e.Handled = true; - }; - - PhotosPanel.Children.Add(container); - } - catch { /* skip unreadable files */ } - } - - private void UpdatePhotoPanel() - { - var count = _draft.PhotoPaths.Count; - DropHint.Visibility = count == 0 ? Visibility.Visible : Visibility.Collapsed; - PhotoCountBadge.Text = count.ToString(); - // Tint the badge red when at the limit - PhotoCountBadge.Foreground = count >= 12 - ? System.Windows.Media.Brushes.OrangeRed - : (System.Windows.Media.Brush)FindResource("MahApps.Brushes.Gray2"); - } - - private void ClearPhotos_Click(object sender, RoutedEventArgs e) - { - _draft.PhotoPaths.Clear(); - RebuildPhotoThumbnails(); - } - - // ---- Post / Save ---- - - private async void PostListing_Click(object sender, RoutedEventArgs e) - { - if (!ValidateDraft()) return; - - _draft.Title = TitleBox.Text.Trim(); - _draft.Description = DescriptionBox.Text.Trim(); - _draft.Price = (decimal)(PriceBox.Value ?? 0); - _draft.Quantity = (int)(QuantityBox.Value ?? 1); - _draft.Condition = GetSelectedCondition(); - _draft.Format = FormatBox.SelectedIndex == 0 ? ListingFormat.FixedPrice : ListingFormat.Auction; - _draft.Postcode = PostcodeBox.Text; - - SetPostSpinner(true); - SetBusy(true, "Posting to eBay..."); - PostBtn.IsEnabled = false; - - try - { - var url = await _listingService!.PostListingAsync(_draft); - ListingUrlText.Text = url; - SuccessPanel.Visibility = Visibility.Visible; - GetWindow()?.SetStatus($"Listed: {_draft.Title}"); - } - catch (Exception ex) - { - ShowError("Post Failed", ex.Message); - } - finally - { - SetBusy(false); - SetPostSpinner(false); - PostBtn.IsEnabled = true; - } - } - - private void ListingUrl_Click(object sender, MouseButtonEventArgs e) - { - if (!string.IsNullOrEmpty(_draft.EbayListingUrl)) - Process.Start(new ProcessStartInfo(_draft.EbayListingUrl) { UseShellExecute = true }); - } - - private void CopyUrl_Click(object sender, RoutedEventArgs e) - { - var url = ListingUrlText.Text; - if (!string.IsNullOrEmpty(url)) - System.Windows.Clipboard.SetText(url); - } - - private void CopyTitle_Click(object sender, RoutedEventArgs e) - { - if (!string.IsNullOrEmpty(_draft.Title)) - System.Windows.Clipboard.SetText(_draft.Title); - } - - private void SaveDraft_Click(object sender, RoutedEventArgs e) - { - // Drafts: future feature — for now just confirm save - MessageBox.Show("Draft saved (local save to be implemented in a future update).", - "Save Draft", MessageBoxButton.OK, MessageBoxImage.Information); - } - - private void NewListing_Click(object sender, RoutedEventArgs e) - { - if (!string.IsNullOrWhiteSpace(TitleBox.Text)) - { - var result = MessageBox.Show( - "Start a new listing? Current details will be lost.", - "New Listing", - MessageBoxButton.OKCancel, - MessageBoxImage.Question); - if (result != MessageBoxResult.OK) return; - } - - _draft = new ListingDraft { Postcode = PostcodeBox.Text }; - TitleBox.Text = ""; - DescriptionBox.Text = ""; - CategoryBox.Text = ""; - CategoryIdLabel.Text = "(no category)"; - PriceBox.Value = 0; - QuantityBox.Value = 1; - ConditionBox.SelectedIndex = 3; // Used - FormatBox.SelectedIndex = 0; - PhotosPanel.Children.Clear(); - UpdatePhotoPanel(); - SuccessPanel.Visibility = Visibility.Collapsed; - PriceSuggestionPanel.Visibility = Visibility.Collapsed; - } - - // ---- Helpers ---- - - private bool ValidateDraft() - { - if (string.IsNullOrWhiteSpace(TitleBox.Text)) - { ShowError("Validation", "Please enter a title."); return false; } - if (TitleBox.Text.Length > 80) - { ShowError("Validation", "Title must be 80 characters or fewer."); return false; } - if (string.IsNullOrEmpty(_draft.CategoryId)) - { ShowError("Validation", "Please select a category."); return false; } - if ((PriceBox.Value ?? 0) <= 0) - { ShowError("Validation", "Please enter a price greater than zero."); return false; } - return true; - } - - private void SetBusy(bool busy, string message = "") - { - IsEnabled = !busy; - GetWindow()?.SetStatus(busy ? message : "Ready"); - } - - private void SetPostSpinner(bool spinning) - { - PostSpinner.Visibility = spinning ? Visibility.Visible : Visibility.Collapsed; - PostIcon.Visibility = spinning ? Visibility.Collapsed : Visibility.Visible; - } - - private void SetTitleSpinner(bool spinning) - { - TitleSpinner.Visibility = spinning ? Visibility.Visible : Visibility.Collapsed; - TitleAiIcon.Visibility = spinning ? Visibility.Collapsed : Visibility.Visible; - } - - private void SetDescSpinner(bool spinning) - { - DescSpinner.Visibility = spinning ? Visibility.Visible : Visibility.Collapsed; - DescAiIcon.Visibility = spinning ? Visibility.Collapsed : Visibility.Visible; - } - - private void SetPriceSpinner(bool spinning) - { - PriceSpinner.Visibility = spinning ? Visibility.Visible : Visibility.Collapsed; - PriceAiIcon.Visibility = spinning ? Visibility.Collapsed : Visibility.Visible; - } - - private void ShowError(string title, string message) - => MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Warning); - - private MainWindow? GetWindow() => Window.GetWindow(this) as MainWindow; -}