223 lines
6.5 KiB
C#
223 lines
6.5 KiB
C#
using BookReader.Models;
|
|
using BookReader.Services;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using System.Collections.ObjectModel;
|
|
|
|
namespace BookReader.ViewModels;
|
|
|
|
public partial class BookshelfViewModel : BaseViewModel
|
|
{
|
|
private readonly IDatabaseService _databaseService;
|
|
private readonly IBookParserService _bookParserService;
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly INavigationService _navigationService;
|
|
private readonly ICachedImageLoadingService _imageLoadingService;
|
|
|
|
public ObservableCollection<Book> Books { get; } = new();
|
|
|
|
[ObservableProperty]
|
|
private bool _isEmpty;
|
|
|
|
[ObservableProperty]
|
|
private bool _isRefreshing;
|
|
|
|
[ObservableProperty]
|
|
private string _searchText = string.Empty;
|
|
|
|
partial void OnSearchTextChanged(string value)
|
|
{
|
|
// Автоматически выполняем поиск при изменении текста
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
// Если поле пустое - загружаем все книги
|
|
LoadBooksCommand.Execute(null);
|
|
}
|
|
}
|
|
|
|
public BookshelfViewModel(
|
|
IDatabaseService databaseService,
|
|
IBookParserService bookParserService,
|
|
ISettingsService settingsService,
|
|
INavigationService navigationService,
|
|
ICachedImageLoadingService imageLoadingService)
|
|
{
|
|
_databaseService = databaseService;
|
|
_bookParserService = bookParserService;
|
|
_settingsService = settingsService;
|
|
_navigationService = navigationService;
|
|
_imageLoadingService = imageLoadingService;
|
|
Title = "My Library";
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task LoadBooksAsync()
|
|
{
|
|
if (IsBusy) return;
|
|
IsBusy = true;
|
|
|
|
try
|
|
{
|
|
var books = await _databaseService.GetAllBooksAsync();
|
|
|
|
// Применяем фильтр поиска если есть
|
|
if (!string.IsNullOrWhiteSpace(SearchText))
|
|
{
|
|
var searchLower = SearchText.ToLowerInvariant();
|
|
books = books.Where(b =>
|
|
b.Title.ToLowerInvariant().Contains(searchLower) ||
|
|
b.Author.ToLowerInvariant().Contains(searchLower)
|
|
).ToList();
|
|
}
|
|
|
|
Books.Clear();
|
|
foreach (var book in books)
|
|
{
|
|
Books.Add(book);
|
|
}
|
|
IsEmpty = Books.Count == 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"Error loading books: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task RefreshBooksAsync()
|
|
{
|
|
if (IsRefreshing) return;
|
|
IsRefreshing = true;
|
|
|
|
try
|
|
{
|
|
await LoadBooksAsync();
|
|
}
|
|
finally
|
|
{
|
|
IsRefreshing = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task SearchAsync(object? parameter)
|
|
{
|
|
// Если параметр пустой или null, используем текущий SearchText
|
|
var searchText = parameter?.ToString() ?? SearchText;
|
|
|
|
if (string.IsNullOrWhiteSpace(searchText))
|
|
{
|
|
// Очищаем поиск и загружаем все книги
|
|
SearchText = string.Empty;
|
|
}
|
|
|
|
await LoadBooksAsync();
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task AddBookFromFileAsync()
|
|
{
|
|
try
|
|
{
|
|
var customFileTypes = new FilePickerFileType(new Dictionary<DevicePlatform, IEnumerable<string>>
|
|
{
|
|
{ DevicePlatform.Android, new[] { "application/epub+zip", "application/x-fictionbook+xml", "application/octet-stream", "*/*" } }
|
|
});
|
|
|
|
var result = await FilePicker.Default.PickAsync(new PickOptions
|
|
{
|
|
PickerTitle = "Select a book",
|
|
FileTypes = customFileTypes
|
|
});
|
|
|
|
if (result == null) return;
|
|
|
|
var extension = Path.GetExtension(result.FileName).ToLowerInvariant();
|
|
if (extension != ".epub" && extension != ".fb2")
|
|
{
|
|
await _navigationService.DisplayAlertAsync("Error", "Only EPUB and FB2 formats are supported.", "OK");
|
|
return;
|
|
}
|
|
|
|
IsBusy = true;
|
|
StatusMessage = "Adding book...";
|
|
|
|
// Copy to temp if needed and parse
|
|
string filePath;
|
|
using var stream = await result.OpenReadAsync();
|
|
var tempPath = Path.Combine(FileSystem.CacheDirectory, result.FileName);
|
|
using (var fileStream = File.Create(tempPath))
|
|
{
|
|
await stream.CopyToAsync(fileStream);
|
|
}
|
|
filePath = tempPath;
|
|
|
|
var book = await _bookParserService.ParseAndStoreBookAsync(filePath, result.FileName);
|
|
Books.Insert(0, book);
|
|
IsEmpty = false;
|
|
|
|
// Clean temp
|
|
try { File.Delete(tempPath); } catch { }
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _navigationService.DisplayAlertAsync("Error", $"Failed to add book: {ex.Message}", "OK");
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
StatusMessage = string.Empty;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task DeleteBookAsync(Book book)
|
|
{
|
|
if (book == null) return;
|
|
|
|
var confirm = await _navigationService.DisplayAlertAsync("Delete Book",
|
|
$"Are you sure you want to delete \"{book.Title}\"?", "Delete", "Cancel");
|
|
|
|
if (!confirm) return;
|
|
|
|
try
|
|
{
|
|
await _databaseService.DeleteBookAsync(book);
|
|
Books.Remove(book);
|
|
IsEmpty = Books.Count == 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _navigationService.DisplayAlertAsync("Error", $"Failed to delete book: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task OpenBookAsync(Book book)
|
|
{
|
|
if (book == null) return;
|
|
|
|
var navigationParameter = new Dictionary<string, object>
|
|
{
|
|
{ "Book", book }
|
|
};
|
|
|
|
await _navigationService.GoToAsync("reader", navigationParameter);
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task OpenSettingsAsync()
|
|
{
|
|
await _navigationService.GoToAsync("settings");
|
|
}
|
|
|
|
[RelayCommand]
|
|
public async Task OpenCalibreLibraryAsync()
|
|
{
|
|
await _navigationService.GoToAsync("calibre");
|
|
}
|
|
} |