diff --git a/Marechai.App/App.xaml.cs b/Marechai.App/App.xaml.cs index 8c8ced55..de28191c 100644 --- a/Marechai.App/App.xaml.cs +++ b/Marechai.App/App.xaml.cs @@ -189,6 +189,7 @@ public partial class App : PrismApplication containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); // Register ViewModels explicitly — RegisterForNavigation creates conditional // (keyed) registrations that the ViewModelLocator can't resolve by type alone @@ -201,6 +202,9 @@ public partial class App : PrismApplication containerRegistry.Register(); containerRegistry.Register(); containerRegistry.Register(); + containerRegistry.Register(); + containerRegistry.Register(); + containerRegistry.Register(); containerRegistry.Register(); containerRegistry.Register(); containerRegistry.Register(); @@ -245,6 +249,9 @@ public partial class App : PrismApplication containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); containerRegistry.RegisterForNavigation(); diff --git a/Marechai.App/Presentation/Models/BookListItem.cs b/Marechai.App/Presentation/Models/BookListItem.cs new file mode 100644 index 00000000..8bbff4e2 --- /dev/null +++ b/Marechai.App/Presentation/Models/BookListItem.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; + +namespace Marechai.App.Presentation.Models; + +[Bindable] +public partial class BookListItem : ObservableObject +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public int? Year { get; set; } + public string? Authors { get; set; } + public Guid? CoverGuid { get; set; } + + [ObservableProperty] + private ImageSource? _coverThumbnailSource; +} diff --git a/Marechai.App/Presentation/ViewModels/BookViewViewModel.cs b/Marechai.App/Presentation/ViewModels/BookViewViewModel.cs new file mode 100644 index 00000000..7dd2a9cf --- /dev/null +++ b/Marechai.App/Presentation/ViewModels/BookViewViewModel.cs @@ -0,0 +1,385 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Input; +using Marechai.App.Navigation; +using Marechai.App.Presentation.Views; +using Marechai.App.Services; +using Marechai.App.Services.Caching; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; + +namespace Marechai.App.Presentation.ViewModels; + +[Bindable] +public partial class BookViewViewModel : ObservableObject, IRegionAware +{ + private readonly BookCoverCache _coverCache; + private readonly BooksService _booksService; + private readonly ImageSourceFactory _imageSourceFactory; + private readonly IStringLocalizer _localizer; + private readonly ILogger _logger; + private readonly IRegionManager _regionManager; + + private string? _navigationSource; + private long _currentBookId; + private long? _sourceNavigationBookId; + + [ObservableProperty] + private string _bookTitle = string.Empty; + + [ObservableProperty] + private string? _nativeTitle; + + [ObservableProperty] + private string? _publishedDisplay; + + [ObservableProperty] + private string? _country; + + [ObservableProperty] + private string? _isbn; + + [ObservableProperty] + private short? _pages; + + [ObservableProperty] + private int? _edition; + + [ObservableProperty] + private ImageSource? _coverImageSource; + + [ObservableProperty] + private bool _hasCover; + + [ObservableProperty] + private string? _synopsisText; + + [ObservableProperty] + private string? _previousBookTitle; + + [ObservableProperty] + private long? _previousBookId; + + [ObservableProperty] + private string? _sourceBookTitle; + + [ObservableProperty] + private long? _sourceBookId; + + [ObservableProperty] + private string _errorMessage = string.Empty; + + [ObservableProperty] + private bool _hasError; + + [ObservableProperty] + private bool _isDataLoaded; + + [ObservableProperty] + private bool _isLoading; + + // Visibility flags + [ObservableProperty] + private Visibility _showNativeTitle = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showPublished = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showCountry = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showIsbn = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showPages = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showEdition = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showSynopsis = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showPeople = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showCompanies = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showMachines = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showMachineFamilies = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showPreviousBook = Visibility.Collapsed; + + [ObservableProperty] + private Visibility _showSourceBook = Visibility.Collapsed; + + public BookViewViewModel(ILogger logger, IRegionManager regionManager, + BooksService booksService, BookCoverCache coverCache, + IStringLocalizer localizer, ImageSourceFactory imageSourceFactory) + { + _logger = logger; + _regionManager = regionManager; + _booksService = booksService; + _coverCache = coverCache; + _localizer = localizer; + _imageSourceFactory = imageSourceFactory; + } + + public ObservableCollection People { get; } = []; + public ObservableCollection Companies { get; } = []; + public ObservableCollection Machines { get; } = []; + public ObservableCollection MachineFamilies { get; } = []; + + public bool IsNavigationTarget(NavigationContext navigationContext) => false; + + public void OnNavigatedFrom(NavigationContext navigationContext) { } + + public void OnNavigatedTo(NavigationContext navigationContext) + { + if(navigationContext.Parameters.TryGetValue(NavParamKeys.NavigationSource, out string? source)) + _navigationSource = source; + + if(navigationContext.Parameters.TryGetValue("SourceNavigationBookId", out long sourceBookNavId)) + _sourceNavigationBookId = sourceBookNavId; + + if(navigationContext.Parameters.TryGetValue(NavParamKeys.BookId, out long bookId)) + { + _currentBookId = bookId; + _ = LoadBookAsync(bookId); + } + } + + [RelayCommand] + public Task GoBack() + { + switch(_navigationSource) + { + case nameof(BooksListViewModel): + _regionManager.RequestNavigate(RegionNames.Content, nameof(BooksListPage)); + + break; + + case nameof(BookViewViewModel): + { + // Going back from a book-to-book navigation — return to the source book + if(_sourceNavigationBookId.HasValue) + { + var parameters = new NavigationParameters + { + { NavParamKeys.BookId, _sourceNavigationBookId.Value }, + { NavParamKeys.NavigationSource, nameof(BooksListViewModel) } + }; + + _regionManager.RequestNavigate(RegionNames.Content, nameof(BookViewPage), parameters); + } + else + _regionManager.RequestNavigate(RegionNames.Content, nameof(BooksListPage)); + + break; + } + + default: + _regionManager.RequestNavigate(RegionNames.Content, nameof(BooksPage)); + + break; + } + + return Task.CompletedTask; + } + + [RelayCommand] + public Task NavigateToPreviousBook() + { + if(PreviousBookId is null) return Task.CompletedTask; + + var parameters = new NavigationParameters + { + { NavParamKeys.BookId, PreviousBookId.Value }, + { NavParamKeys.NavigationSource, nameof(BookViewViewModel) }, + { "SourceNavigationBookId", _currentBookId } + }; + + _regionManager.RequestNavigate(RegionNames.Content, nameof(BookViewPage), parameters); + + return Task.CompletedTask; + } + + [RelayCommand] + public Task NavigateToSourceBook() + { + if(SourceBookId is null) return Task.CompletedTask; + + var parameters = new NavigationParameters + { + { NavParamKeys.BookId, SourceBookId.Value }, + { NavParamKeys.NavigationSource, nameof(BookViewViewModel) }, + { "SourceNavigationBookId", _currentBookId } + }; + + _regionManager.RequestNavigate(RegionNames.Content, nameof(BookViewPage), parameters); + + return Task.CompletedTask; + } + + [RelayCommand] + public Task LoadData() + { + HasError = false; + ErrorMessage = string.Empty; + + return Task.CompletedTask; + } + + public async Task LoadBookAsync(long bookId) + { + try + { + IsLoading = true; + IsDataLoaded = false; + HasError = false; + ErrorMessage = string.Empty; + People.Clear(); + Companies.Clear(); + Machines.Clear(); + MachineFamilies.Clear(); + + BookDto? book = await _booksService.GetBookAsync(bookId); + + if(book is null) + { + HasError = true; + ErrorMessage = _localizer["Book not found"]; + IsLoading = false; + + return; + } + + // Populate basic information (no SortTitle) + BookTitle = book.Title ?? string.Empty; + NativeTitle = book.NativeTitle; + Country = book.Country; + Isbn = book.Isbn; + Pages = (short?)book.Pages; + Edition = book.Edition; + + if(book.Published.HasValue) + PublishedDisplay = book.Published.Value.ToString("MMMM d, yyyy"); + + // Load cover image + HasCover = book.CoverGuid.HasValue; + + if(book.CoverGuid.HasValue) + _ = LoadCoverAsync(book.CoverGuid.Value); + + // Load synopsis + DocumentSynopsisDto? synopsis = await _booksService.GetBookSynopsisAsync(bookId); + + if(synopsis is not null) + SynopsisText = synopsis.Text; + + // Load people (by role) + List people = await _booksService.GetPeopleByBookAsync(bookId); + + foreach(PersonByBookDto person in people) + { + string name = person.DisplayName ?? person.Alias ?? $"{person.Name} {person.Surname}".Trim(); + string? roleStr = person.Role; + string display = !string.IsNullOrEmpty(roleStr) ? $"{name} ({roleStr})" : name; + People.Add(display); + } + + // Load companies (by role) + List companies = await _booksService.GetCompaniesByBookAsync(bookId); + + foreach(CompanyByBookDto company in companies) + { + string name = company.Company ?? string.Empty; + string? roleStr = company.Role; + string display = !string.IsNullOrEmpty(roleStr) ? $"{name} ({roleStr})" : name; + Companies.Add(display); + } + + // Load machines + List machines = await _booksService.GetMachinesByBookAsync(bookId); + + foreach(BookByMachineDto machine in machines) + Machines.Add(machine.Machine ?? string.Empty); + + // Load machine families + List families = await _booksService.GetMachineFamiliesByBookAsync(bookId); + + foreach(BookByMachineFamilyDto family in families) + MachineFamilies.Add(family.MachineFamily ?? string.Empty); + + // Load previous book title + PreviousBookId = book.PreviousId; + + if(book.PreviousId.HasValue) + { + BookDto? prev = await _booksService.GetBookAsync(book.PreviousId.Value); + PreviousBookTitle = prev?.Title; + } + + // Load source book title + SourceBookId = book.SourceId; + + if(book.SourceId.HasValue) + { + BookDto? src = await _booksService.GetBookAsync(book.SourceId.Value); + SourceBookTitle = src?.Title; + } + + UpdateVisibilities(); + IsDataLoaded = true; + IsLoading = false; + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading book {BookId}", bookId); + HasError = true; + ErrorMessage = ex.Message; + IsLoading = false; + } + } + + private void UpdateVisibilities() + { + ShowNativeTitle = !string.IsNullOrEmpty(NativeTitle) ? Visibility.Visible : Visibility.Collapsed; + ShowPublished = !string.IsNullOrEmpty(PublishedDisplay) ? Visibility.Visible : Visibility.Collapsed; + ShowCountry = !string.IsNullOrEmpty(Country) ? Visibility.Visible : Visibility.Collapsed; + ShowIsbn = !string.IsNullOrEmpty(Isbn) ? Visibility.Visible : Visibility.Collapsed; + ShowPages = Pages.HasValue ? Visibility.Visible : Visibility.Collapsed; + ShowEdition = Edition.HasValue ? Visibility.Visible : Visibility.Collapsed; + ShowSynopsis = !string.IsNullOrEmpty(SynopsisText) ? Visibility.Visible : Visibility.Collapsed; + ShowPeople = People.Count > 0 ? Visibility.Visible : Visibility.Collapsed; + ShowCompanies = Companies.Count > 0 ? Visibility.Visible : Visibility.Collapsed; + ShowMachines = Machines.Count > 0 ? Visibility.Visible : Visibility.Collapsed; + ShowMachineFamilies = MachineFamilies.Count > 0 ? Visibility.Visible : Visibility.Collapsed; + ShowPreviousBook = PreviousBookId.HasValue ? Visibility.Visible : Visibility.Collapsed; + ShowSourceBook = SourceBookId.HasValue ? Visibility.Visible : Visibility.Collapsed; + } + + private async Task LoadCoverAsync(Guid coverGuid) + { + try + { + Stream stream = await _coverCache.GetCoverAsync(coverGuid); + CoverImageSource = await _imageSourceFactory.CreateBitmapImageSourceAsync(stream); + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading cover for book {BookId}", _currentBookId); + } + } +} diff --git a/Marechai.App/Presentation/ViewModels/BooksListViewModel.cs b/Marechai.App/Presentation/ViewModels/BooksListViewModel.cs new file mode 100644 index 00000000..aa764fa6 --- /dev/null +++ b/Marechai.App/Presentation/ViewModels/BooksListViewModel.cs @@ -0,0 +1,224 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using Marechai.App.Navigation; +using Marechai.App.Presentation.Models; +using Marechai.App.Presentation.Views; +using Marechai.App.Services; +using Marechai.App.Services.Caching; +using Microsoft.UI.Xaml.Data; + +namespace Marechai.App.Presentation.ViewModels; + +[Bindable] +public partial class BooksListViewModel : ObservableObject +{ + private readonly BookCoverCache _coverCache; + private readonly BooksService _booksService; + private readonly IBooksListFilterContext _filterContext; + private readonly ImageSourceFactory _imageSourceFactory; + private readonly IStringLocalizer _localizer; + private readonly ILogger _logger; + private readonly IRegionManager _regionManager; + + [ObservableProperty] + private ObservableCollection _booksList = []; + + [ObservableProperty] + private string _errorMessage = string.Empty; + + [ObservableProperty] + private string _filterDescription = string.Empty; + + [ObservableProperty] + private bool _hasError; + + [ObservableProperty] + private bool _isDataLoaded; + + [ObservableProperty] + private bool _isLoading; + + [ObservableProperty] + private string _pageTitle = string.Empty; + + public BooksListViewModel(BooksService booksService, IStringLocalizer localizer, + ILogger logger, IRegionManager regionManager, + IBooksListFilterContext filterContext, + BookCoverCache coverCache, + ImageSourceFactory imageSourceFactory) + { + _booksService = booksService; + _localizer = localizer; + _logger = logger; + _regionManager = regionManager; + _filterContext = filterContext; + _coverCache = coverCache; + _imageSourceFactory = imageSourceFactory; + LoadData = new AsyncRelayCommand(LoadDataAsync); + GoBackCommand = new AsyncRelayCommand(GoBackAsync); + NavigateToBookCommand = new AsyncRelayCommand(NavigateToBookAsync); + } + + public IAsyncRelayCommand LoadData { get; } + public ICommand GoBackCommand { get; } + public IAsyncRelayCommand NavigateToBookCommand { get; } + + public BookListFilterType FilterType + { + get => _filterContext.FilterType; + set => _filterContext.FilterType = value; + } + + public string FilterValue + { + get => _filterContext.FilterValue; + set => _filterContext.FilterValue = value; + } + + private async Task LoadDataAsync() + { + try + { + IsLoading = true; + ErrorMessage = string.Empty; + HasError = false; + IsDataLoaded = false; + BooksList.Clear(); + + UpdateFilterDescription(); + + await LoadBooksFromApiAsync(); + + if(BooksList.Count == 0) + { + ErrorMessage = _localizer["No books found for this filter"].Value; + HasError = true; + } + else + IsDataLoaded = true; + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading books: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to load books. Please try again later."].Value; + HasError = true; + } + finally + { + IsLoading = false; + } + } + + private void UpdateFilterDescription() + { + switch(FilterType) + { + case BookListFilterType.All: + PageTitle = _localizer["All Books"]; + FilterDescription = _localizer["Browsing all books in the database"]; + + break; + + case BookListFilterType.Letter: + if(!string.IsNullOrEmpty(FilterValue) && FilterValue.Length == 1) + { + PageTitle = $"{_localizer["Books Starting with"]} {FilterValue}"; + FilterDescription = $"{_localizer["Showing books that start with"]} {FilterValue}"; + } + + break; + + case BookListFilterType.Year: + if(!string.IsNullOrEmpty(FilterValue) && int.TryParse(FilterValue, out int year)) + { + PageTitle = $"{_localizer["Books from"]} {year}"; + FilterDescription = $"{_localizer["Showing books published in"]} {year}"; + } + + break; + } + } + + private async Task LoadBooksFromApiAsync() + { + try + { + List books = FilterType switch + { + BookListFilterType.Letter when FilterValue.Length == 1 => + await _booksService.GetBooksByLetterAsync(FilterValue[0]), + + BookListFilterType.Year when int.TryParse(FilterValue, out int year) => + await _booksService.GetBooksByYearAsync(year), + + _ => await _booksService.GetAllBooksAsync() + }; + + foreach(BookDto book in books) + { + long id = book.Id ?? 0; + int? year = book.Published?.Year; + + var item = new BookListItem + { + Id = id, + Title = book.Title ?? string.Empty, + Year = year, + CoverGuid = book.CoverGuid + }; + + // Fire-and-forget cover thumbnail loading + if(book.CoverGuid.HasValue) + _ = LoadCoverThumbnailAsync(item, book.CoverGuid.Value); + + BooksList.Add(item); + } + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading books from API"); + } + } + + private async Task LoadCoverThumbnailAsync(BookListItem item, Guid coverGuid) + { + try + { + Stream stream = await _coverCache.GetThumbnailAsync(coverGuid); + item.CoverThumbnailSource = await _imageSourceFactory.CreateBitmapImageSourceAsync(stream); + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading cover thumbnail for book {BookId}", item.Id); + } + } + + private Task GoBackAsync() + { + _regionManager.RequestNavigate(RegionNames.Content, nameof(BooksPage)); + + return Task.CompletedTask; + } + + private Task NavigateToBookAsync(BookListItem? book) + { + if(book is null) return Task.CompletedTask; + + var parameters = new NavigationParameters + { + { NavParamKeys.BookId, book.Id }, + { NavParamKeys.NavigationSource, nameof(BooksListViewModel) } + }; + + _regionManager.RequestNavigate(RegionNames.Content, nameof(BookViewPage), parameters); + + return Task.CompletedTask; + } +} diff --git a/Marechai.App/Presentation/ViewModels/BooksViewModel.cs b/Marechai.App/Presentation/ViewModels/BooksViewModel.cs new file mode 100644 index 00000000..95156bdc --- /dev/null +++ b/Marechai.App/Presentation/ViewModels/BooksViewModel.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using System.Windows.Input; +using Marechai.App.Navigation; +using Marechai.App.Presentation.Views; +using Marechai.App.Services; + +namespace Marechai.App.Presentation.ViewModels; + +public partial class BooksViewModel : ObservableObject +{ + private readonly BooksService _booksService; + private readonly IBooksListFilterContext _filterContext; + private readonly IStringLocalizer _localizer; + private readonly ILogger _logger; + private readonly IRegionManager _regionManager; + + [ObservableProperty] + private int _bookCount; + + [ObservableProperty] + private string _bookCountText = string.Empty; + + [ObservableProperty] + private string _errorMessage = string.Empty; + + [ObservableProperty] + private bool _hasError; + + [ObservableProperty] + private bool _isDataLoaded; + + [ObservableProperty] + private bool _isLoading; + + [ObservableProperty] + private ObservableCollection _lettersList = []; + + [ObservableProperty] + private int _maximumYear; + + [ObservableProperty] + private int _minimumYear; + + [ObservableProperty] + private string _yearsGridTitle = string.Empty; + + [ObservableProperty] + private ObservableCollection _yearsList = []; + + public BooksViewModel(BooksService booksService, IStringLocalizer localizer, + ILogger logger, IRegionManager regionManager, + IBooksListFilterContext filterContext) + { + _booksService = booksService; + _localizer = localizer; + _logger = logger; + _regionManager = regionManager; + _filterContext = filterContext; + LoadData = new AsyncRelayCommand(LoadDataAsync); + GoBackCommand = new AsyncRelayCommand(GoBackAsync); + NavigateByLetterCommand = new AsyncRelayCommand(NavigateByLetterAsync); + NavigateByYearCommand = new AsyncRelayCommand(NavigateByYearAsync); + NavigateAllBooksCommand = new AsyncRelayCommand(NavigateAllBooksAsync); + Title = _localizer["Books"]; + + InitializeLetters(); + } + + public IAsyncRelayCommand LoadData { get; } + public ICommand GoBackCommand { get; } + public IAsyncRelayCommand NavigateByLetterCommand { get; } + public IAsyncRelayCommand NavigateByYearCommand { get; } + public IAsyncRelayCommand NavigateAllBooksCommand { get; } + public string Title { get; } + + private void InitializeLetters() + { + LettersList.Clear(); + + for(var c = 'A'; c <= 'Z'; c++) LettersList.Add(c); + } + + private async Task LoadDataAsync() + { + try + { + IsLoading = true; + ErrorMessage = string.Empty; + HasError = false; + IsDataLoaded = false; + YearsList.Clear(); + + Task countTask = _booksService.GetBooksCountAsync(); + Task minYearTask = _booksService.GetMinimumYearAsync(); + Task maxYearTask = _booksService.GetMaximumYearAsync(); + await Task.WhenAll(countTask, minYearTask, maxYearTask); + + BookCount = countTask.Result; + MinimumYear = minYearTask.Result; + MaximumYear = maxYearTask.Result; + + BookCountText = _localizer["Books in the database"]; + + if(MinimumYear > 0 && MaximumYear > 0) + { + for(int year = MinimumYear; year <= MaximumYear; year++) YearsList.Add(year); + + YearsGridTitle = string.Format(_localizer["Browse by Year ({0} - {1})"], MinimumYear, MaximumYear); + } + + if(BookCount == 0) + { + ErrorMessage = _localizer["No books found"].Value; + HasError = true; + } + else + IsDataLoaded = true; + } + catch(Exception ex) + { + _logger.LogError("Error loading books data: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to load books data. Please try again later."].Value; + HasError = true; + } + finally + { + IsLoading = false; + } + } + + private async Task GoBackAsync() + { + _regionManager.RequestNavigate(RegionNames.Content, nameof(NewsPage)); + } + + private Task NavigateByLetterAsync(char letter) + { + try + { + _logger.LogInformation("Navigating to books by letter: {Letter}", letter); + _filterContext.FilterType = BookListFilterType.Letter; + _filterContext.FilterValue = letter.ToString(); + _regionManager.RequestNavigate(RegionNames.Content, nameof(BooksListPage)); + } + catch(Exception ex) + { + _logger.LogError("Error navigating to letter books: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to navigate. Please try again."].Value; + HasError = true; + } + + return Task.CompletedTask; + } + + private Task NavigateByYearAsync(int year) + { + try + { + _logger.LogInformation("Navigating to books by year: {Year}", year); + _filterContext.FilterType = BookListFilterType.Year; + _filterContext.FilterValue = year.ToString(); + _regionManager.RequestNavigate(RegionNames.Content, nameof(BooksListPage)); + } + catch(Exception ex) + { + _logger.LogError("Error navigating to year books: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to navigate. Please try again."].Value; + HasError = true; + } + + return Task.CompletedTask; + } + + private Task NavigateAllBooksAsync() + { + try + { + _logger.LogInformation("Navigating to all books"); + _filterContext.FilterType = BookListFilterType.All; + _filterContext.FilterValue = string.Empty; + _regionManager.RequestNavigate(RegionNames.Content, nameof(BooksListPage)); + } + catch(Exception ex) + { + _logger.LogError("Error navigating to all books: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to navigate. Please try again."].Value; + HasError = true; + } + + return Task.CompletedTask; + } +} diff --git a/Marechai.App/Presentation/ViewModels/MainViewModel.cs b/Marechai.App/Presentation/ViewModels/MainViewModel.cs index 3d5e4d00..2319b3ef 100644 --- a/Marechai.App/Presentation/ViewModels/MainViewModel.cs +++ b/Marechai.App/Presentation/ViewModels/MainViewModel.cs @@ -90,7 +90,7 @@ public partial class MainViewModel : ObservableObject // Initialize commands NavigateToNewsCommand = new RelayCommand(() => NavigateTo(nameof(NewsPage))); - NavigateToBooksCommand = new RelayCommand(() => NavigateTo("books")); + NavigateToBooksCommand = new RelayCommand(() => NavigateTo(nameof(BooksPage))); NavigateToCompaniesCommand = new RelayCommand(() => NavigateTo(nameof(CompaniesPage))); NavigateToComputersCommand = new RelayCommand(() => NavigateTo(nameof(ComputersPage))); NavigateToConsolesCommand = new RelayCommand(() => NavigateTo(nameof(ConsolesPage))); diff --git a/Marechai.App/Presentation/Views/BookViewPage.xaml b/Marechai.App/Presentation/Views/BookViewPage.xaml new file mode 100644 index 00000000..e8e2138b --- /dev/null +++ b/Marechai.App/Presentation/Views/BookViewPage.xaml @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Marechai.App/Presentation/Views/BooksListPage.xaml.cs b/Marechai.App/Presentation/Views/BooksListPage.xaml.cs new file mode 100644 index 00000000..100351b2 --- /dev/null +++ b/Marechai.App/Presentation/Views/BooksListPage.xaml.cs @@ -0,0 +1,20 @@ +using Marechai.App.Presentation.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Marechai.App.Presentation.Views; + +public sealed partial class BooksListPage : Page +{ + public BooksListPage() + { + InitializeComponent(); + DataContextChanged += BooksListPage_DataContextChanged; + } + + private void BooksListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if(args.NewValue is BooksListViewModel vm) + vm.LoadData.Execute(null); + } +} diff --git a/Marechai.App/Presentation/Views/BooksPage.xaml b/Marechai.App/Presentation/Views/BooksPage.xaml new file mode 100644 index 00000000..75521791 --- /dev/null +++ b/Marechai.App/Presentation/Views/BooksPage.xaml @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +