edit_codex
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using Android.Graphics.Fonts;
|
||||
using BookReader.Models;
|
||||
using BookReader.Models;
|
||||
using BookReader.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -11,7 +10,13 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IDatabaseService _databaseService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly INavigationService _navigationService;
|
||||
|
||||
private double _lastPersistedProgress = -1;
|
||||
private string _lastPersistedCfi = string.Empty;
|
||||
private string _lastPersistedChapter = string.Empty;
|
||||
private int _lastPersistedCurrentPage = -1;
|
||||
private int _lastPersistedTotalPages = -1;
|
||||
private DateTime _lastPersistedAt = DateTime.MinValue;
|
||||
|
||||
[ObservableProperty]
|
||||
private Book? _book;
|
||||
@@ -28,6 +33,12 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
[ObservableProperty]
|
||||
private string _fontFamily = "serif";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _readerTheme = Constants.Reader.DefaultTheme;
|
||||
|
||||
[ObservableProperty]
|
||||
private double _brightness = Constants.Reader.DefaultBrightness;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<string> _chapters = new();
|
||||
|
||||
@@ -44,19 +55,18 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
private int _currentPage = 1;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _totalPages = 1;
|
||||
private int _totalPages = 100;
|
||||
|
||||
// Это свойство будет обновляться автоматически при изменении любого из полей выше
|
||||
public string ChapterProgressText => $"{ChapterCurrentPage} из {ChapterTotalPages}";
|
||||
public string ChapterProgressText => ChapterTotalPages > 1
|
||||
? $"Глава: {ChapterCurrentPage} из {ChapterTotalPages}"
|
||||
: "Позиция внутри главы появится после перелистывания";
|
||||
|
||||
// Это свойство показывает процент прогресса
|
||||
public string ProgressText => $"{CurrentPage}%";
|
||||
public string ProgressText => TotalPages == 100
|
||||
? $"{CurrentPage}%"
|
||||
: $"Стр. {CurrentPage} из {TotalPages}";
|
||||
|
||||
// Чтобы ChapterProgressText уведомлял интерфейс, добавим частичные методы (особенность Toolkit)
|
||||
partial void OnChapterCurrentPageChanged(int value) => OnPropertyChanged(nameof(ChapterProgressText));
|
||||
partial void OnChapterTotalPagesChanged(int value) => OnPropertyChanged(nameof(ChapterProgressText));
|
||||
|
||||
// Чтобы ProgressText уведомлял интерфейс, добавим частичные методы (особенность Toolkit)
|
||||
partial void OnCurrentPageChanged(int value) => OnPropertyChanged(nameof(ProgressText));
|
||||
partial void OnTotalPagesChanged(int value) => OnPropertyChanged(nameof(ProgressText));
|
||||
|
||||
@@ -78,42 +88,40 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 36, 40
|
||||
};
|
||||
|
||||
// Events for the view to subscribe to
|
||||
public event Action<string>? OnJavaScriptRequested;
|
||||
public event Action? OnBookReady;
|
||||
|
||||
public ReaderViewModel(
|
||||
IDatabaseService databaseService,
|
||||
ISettingsService settingsService,
|
||||
INavigationService navigationService)
|
||||
ISettingsService settingsService)
|
||||
{
|
||||
_databaseService = databaseService;
|
||||
_settingsService = settingsService;
|
||||
_navigationService = navigationService;
|
||||
_fontSize = Constants.Reader.DefaultFontSize;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Валидация: книга должна быть загружена
|
||||
if (Book == null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("[ReaderViewModel] Book is null, cannot initialize");
|
||||
return;
|
||||
}
|
||||
|
||||
// Валидация: файл книги должен существовать
|
||||
if (!File.Exists(Book.FilePath))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[ReaderViewModel] Book file not found: {Book.FilePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
var savedFontSize = await _settingsService.GetIntAsync(SettingsKeys.DefaultFontSize, Constants.Reader.DefaultFontSize);
|
||||
var savedFontFamily = await _settingsService.GetAsync(SettingsKeys.DefaultFontFamily, "serif");
|
||||
FontSize = await _settingsService.GetIntAsync(SettingsKeys.DefaultFontSize, Constants.Reader.DefaultFontSize);
|
||||
FontFamily = await _settingsService.GetAsync(SettingsKeys.DefaultFontFamily, "serif");
|
||||
ReaderTheme = await _settingsService.GetAsync(SettingsKeys.Theme, Constants.Reader.DefaultTheme);
|
||||
Brightness = await _settingsService.GetDoubleAsync(SettingsKeys.Brightness, Constants.Reader.DefaultBrightness);
|
||||
|
||||
FontSize = savedFontSize;
|
||||
FontFamily = savedFontFamily;
|
||||
CurrentPage = Book.CurrentPage > 0 ? Book.CurrentPage : 1;
|
||||
TotalPages = Book.TotalPages > 0 ? Book.TotalPages : 100;
|
||||
|
||||
RememberPersistedProgress(Book.ReadingProgress, Book.LastCfi, Book.LastChapter, Book.CurrentPage, Book.TotalPages);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -121,7 +129,9 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
{
|
||||
IsMenuVisible = !IsMenuVisible;
|
||||
if (!IsMenuVisible)
|
||||
{
|
||||
IsChapterListVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -149,14 +159,34 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
public void ChangeFontFamily(string family)
|
||||
{
|
||||
FontFamily = family;
|
||||
OnJavaScriptRequested?.Invoke($"setFontFamily('{family}')");
|
||||
OnJavaScriptRequested?.Invoke($"setFontFamily('{EscapeJs(family)}')");
|
||||
_ = _settingsService.SetAsync(SettingsKeys.DefaultFontFamily, family);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ChangeReaderTheme(string theme)
|
||||
{
|
||||
ReaderTheme = theme;
|
||||
OnJavaScriptRequested?.Invoke($"setReaderTheme('{EscapeJs(theme)}')");
|
||||
_ = _settingsService.SetAsync(SettingsKeys.Theme, theme);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ChangeBrightness(double brightness)
|
||||
{
|
||||
Brightness = Math.Clamp(brightness, Constants.Reader.MinBrightness, Constants.Reader.MaxBrightness);
|
||||
OnJavaScriptRequested?.Invoke($"setBrightness({Brightness.ToString(System.Globalization.CultureInfo.InvariantCulture)})");
|
||||
_ = _settingsService.SetDoubleAsync(SettingsKeys.Brightness, Brightness);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void GoToChapter(string chapter)
|
||||
{
|
||||
if (string.IsNullOrEmpty(chapter)) return;
|
||||
if (string.IsNullOrEmpty(chapter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnJavaScriptRequested?.Invoke($"goToChapter('{EscapeJs(chapter)}')");
|
||||
IsChapterListVisible = false;
|
||||
IsMenuVisible = false;
|
||||
@@ -169,11 +199,12 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
System.Diagnostics.Debug.WriteLine("[ReaderViewModel] Cannot save locations: Book is null");
|
||||
return;
|
||||
}
|
||||
|
||||
Book.Locations = locations;
|
||||
await _databaseService.UpdateBookAsync(Book);
|
||||
}
|
||||
|
||||
public async Task SaveProgressAsync(double progress, string? cfi, string? chapter, int currentPage, int totalPages)
|
||||
public async Task SaveProgressAsync(double progress, string? cfi, string? chapter, int currentPage, int totalPages, bool force = false)
|
||||
{
|
||||
if (Book == null)
|
||||
{
|
||||
@@ -181,8 +212,13 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
// Важно: если CFI пустой, не перезаписываем старый прогресс (защита от багов JS)
|
||||
if (string.IsNullOrEmpty(cfi) && progress <= 0) return;
|
||||
if (string.IsNullOrEmpty(cfi) && progress <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentPage = currentPage > 0 ? currentPage : CurrentPage;
|
||||
TotalPages = totalPages > 0 ? totalPages : TotalPages;
|
||||
|
||||
Book.ReadingProgress = progress;
|
||||
Book.LastCfi = cfi;
|
||||
@@ -191,6 +227,23 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
Book.TotalPages = totalPages;
|
||||
Book.LastRead = DateTime.UtcNow;
|
||||
|
||||
var hasMeaningfulChange = HasMeaningfulProgressChange(progress, cfi, chapter, currentPage, totalPages);
|
||||
if (!force)
|
||||
{
|
||||
var throttle = TimeSpan.FromSeconds(Constants.Reader.ProgressSaveThrottleSeconds);
|
||||
var shouldPersistNow = hasMeaningfulChange &&
|
||||
(DateTime.UtcNow - _lastPersistedAt >= throttle || Math.Abs(progress - _lastPersistedProgress) >= 0.02 || currentPage != _lastPersistedCurrentPage);
|
||||
|
||||
if (!shouldPersistNow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!hasMeaningfulChange)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _databaseService.UpdateBookAsync(Book);
|
||||
|
||||
await _databaseService.SaveProgressAsync(new ReadingProgress
|
||||
@@ -201,30 +254,35 @@ public partial class ReaderViewModel : BaseViewModel
|
||||
CurrentPage = currentPage,
|
||||
ChapterTitle = chapter
|
||||
});
|
||||
|
||||
RememberPersistedProgress(progress, cfi, chapter, currentPage, totalPages);
|
||||
}
|
||||
|
||||
public string GetBookFilePath()
|
||||
public string? GetLastCfi() => Book?.LastCfi;
|
||||
|
||||
public string? GetLocations() => Book?.Locations;
|
||||
|
||||
private bool HasMeaningfulProgressChange(double progress, string? cfi, string? chapter, int currentPage, int totalPages)
|
||||
{
|
||||
return Book?.FilePath ?? string.Empty;
|
||||
return Math.Abs(progress - _lastPersistedProgress) >= 0.005 ||
|
||||
string.Equals(cfi ?? string.Empty, _lastPersistedCfi, StringComparison.Ordinal) == false ||
|
||||
string.Equals(chapter ?? string.Empty, _lastPersistedChapter, StringComparison.Ordinal) == false ||
|
||||
currentPage != _lastPersistedCurrentPage ||
|
||||
totalPages != _lastPersistedTotalPages;
|
||||
}
|
||||
|
||||
public string GetBookFormat()
|
||||
private void RememberPersistedProgress(double progress, string? cfi, string? chapter, int currentPage, int totalPages)
|
||||
{
|
||||
return Book?.Format ?? "epub";
|
||||
}
|
||||
|
||||
public string? GetLastCfi()
|
||||
{
|
||||
return Book?.LastCfi;
|
||||
}
|
||||
|
||||
public string? GetLocations()
|
||||
{
|
||||
return Book?.Locations;
|
||||
_lastPersistedProgress = progress;
|
||||
_lastPersistedCfi = cfi ?? string.Empty;
|
||||
_lastPersistedChapter = chapter ?? string.Empty;
|
||||
_lastPersistedCurrentPage = currentPage;
|
||||
_lastPersistedTotalPages = totalPages;
|
||||
_lastPersistedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private static string EscapeJs(string value)
|
||||
{
|
||||
return value.Replace("\\", "\\\\").Replace("'", "\\'").Replace("\n", "\\n").Replace("\r", "\\r");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user