qwen edit
This commit is contained in:
@@ -4,16 +4,44 @@ namespace BookReader;
|
|||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
private readonly IDatabaseService _databaseService;
|
||||||
|
private bool _isInitialized;
|
||||||
|
|
||||||
public App(IDatabaseService databaseService)
|
public App(IDatabaseService databaseService)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_databaseService = databaseService;
|
||||||
// Initialize database
|
|
||||||
Task.Run(async () => await databaseService.InitializeAsync()).Wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Window CreateWindow(IActivationState? activationState)
|
protected override Window CreateWindow(IActivationState? activationState)
|
||||||
{
|
{
|
||||||
return new Window(new AppShell());
|
return new Window(new AppShell());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnStart()
|
||||||
|
{
|
||||||
|
base.OnStart();
|
||||||
|
await InitializeDatabaseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnResume()
|
||||||
|
{
|
||||||
|
base.OnResume();
|
||||||
|
await InitializeDatabaseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitializeDatabaseAsync()
|
||||||
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _databaseService.InitializeAsync();
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[App] Database initialization error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="VersOne.Epub" Version="3.3.5" />
|
<PackageReference Include="VersOne.Epub" Version="3.3.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.3" />
|
||||||
|
<PackageReference Include="Polly" Version="8.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
24
BookReader/BookReader.sln
Normal file
24
BookReader/BookReader.sln
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookReader", "BookReader.csproj", "{D0445465-3484-0365-406D-0D3744906997}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D0445465-3484-0365-406D-0D3744906997}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D0445465-3484-0365-406D-0D3744906997}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D0445465-3484-0365-406D-0D3744906997}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D0445465-3484-0365-406D-0D3744906997}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {49109510-50D7-44BA-B746-6E5BC1E08F6B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -14,9 +14,13 @@ public static class SettingsKeys
|
|||||||
{
|
{
|
||||||
public const string CalibreUrl = "CalibreUrl";
|
public const string CalibreUrl = "CalibreUrl";
|
||||||
public const string CalibreUsername = "CalibreUsername";
|
public const string CalibreUsername = "CalibreUsername";
|
||||||
public const string CalibrePassword = "CalibrePassword";
|
|
||||||
public const string DefaultFontSize = "DefaultFontSize";
|
public const string DefaultFontSize = "DefaultFontSize";
|
||||||
public const string DefaultFontFamily = "DefaultFontFamily";
|
public const string DefaultFontFamily = "DefaultFontFamily";
|
||||||
public const string Theme = "Theme";
|
public const string Theme = "Theme";
|
||||||
public const string Brightness = "Brightness";
|
public const string Brightness = "Brightness";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SecureStorageKeys
|
||||||
|
{
|
||||||
|
public const string CalibrePassword = "calibre_password_secure";
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
// 5. Ограничиваем значения для стабильности epub.js
|
// 5. Ограничиваем значения для стабильности epub.js
|
||||||
// Минимум 400 (чтобы не плодить тысячи локаций на маленьких текстах)
|
// Минимум 400 (чтобы не плодить тысячи локаций на маленьких текстах)
|
||||||
// Максимум 1500 (чтобы избежать "залипания" на больших экранах)
|
// Максимум 1500 (чтобы избежать "залипания" на больших экранах)
|
||||||
const finalSize = Math.max(400, Math.min(1500, charactersPerScreen));
|
const finalSize = Math.max(400, Math.min(1000, charactersPerScreen));
|
||||||
|
|
||||||
debugLog(`Расчет локации: Экран ${width}x${height}, Шрифт ${fontSize}px => Размер локации: ${finalSize}`);
|
debugLog(`Расчет локации: Экран ${width}x${height}, Шрифт ${fontSize}px => Размер локации: ${finalSize}`);
|
||||||
|
|
||||||
@@ -324,7 +324,6 @@
|
|||||||
state.totalPages = state.book.locations.length();
|
state.totalPages = state.book.locations.length();
|
||||||
sendMessage('bookReady', { totalPages: state.totalPages });
|
sendMessage('bookReady', { totalPages: state.totalPages });
|
||||||
} else {
|
} else {
|
||||||
debugLog("Кэша нет, считаем в фоне...");
|
|
||||||
// Используем setTimeout, чтобы не блокировать поток отрисовки
|
// Используем setTimeout, чтобы не блокировать поток отрисовки
|
||||||
const dynamicSize = calculateOptimalLocationSize();
|
const dynamicSize = calculateOptimalLocationSize();
|
||||||
// Запускаем генерацию с динамическим размером
|
// Запускаем генерацию с динамическим размером
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using BookReader.Models;
|
using BookReader.Models;
|
||||||
|
using Polly;
|
||||||
|
using Polly.Retry;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
|
|
||||||
namespace BookReader.Services;
|
namespace BookReader.Services;
|
||||||
@@ -7,10 +9,24 @@ public class DatabaseService : IDatabaseService
|
|||||||
{
|
{
|
||||||
private SQLiteAsyncConnection? _database;
|
private SQLiteAsyncConnection? _database;
|
||||||
private readonly string _dbPath;
|
private readonly string _dbPath;
|
||||||
|
private readonly AsyncRetryPolicy _retryPolicy;
|
||||||
|
|
||||||
public DatabaseService()
|
public DatabaseService()
|
||||||
{
|
{
|
||||||
_dbPath = Path.Combine(FileSystem.AppDataDirectory, "bookreader.db3");
|
_dbPath = Path.Combine(FileSystem.AppDataDirectory, "bookreader.db3");
|
||||||
|
|
||||||
|
// Polly retry policy: 3 попытки с экспоненциальной задержкой
|
||||||
|
_retryPolicy = Policy
|
||||||
|
.Handle<SQLiteException>()
|
||||||
|
.Or<Exception>(ex => ex.Message.Contains("database is locked") || ex.Message.Contains("busy"))
|
||||||
|
.WaitAndRetryAsync(
|
||||||
|
retryCount: 3,
|
||||||
|
sleepDurationProvider: attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt)),
|
||||||
|
onRetry: (exception, timeSpan, retryNumber, context) =>
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[Database] Retry {retryNumber} after {timeSpan.TotalMilliseconds}ms: {exception.Message}");
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
public async Task InitializeAsync()
|
||||||
@@ -19,9 +35,12 @@ public class DatabaseService : IDatabaseService
|
|||||||
|
|
||||||
_database = new SQLiteAsyncConnection(_dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.SharedCache);
|
_database = new SQLiteAsyncConnection(_dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.SharedCache);
|
||||||
|
|
||||||
await _database.CreateTableAsync<Book>();
|
await _retryPolicy.ExecuteAsync(async () =>
|
||||||
await _database.CreateTableAsync<AppSettings>();
|
{
|
||||||
await _database.CreateTableAsync<ReadingProgress>();
|
await _database!.CreateTableAsync<Book>();
|
||||||
|
await _database!.CreateTableAsync<AppSettings>();
|
||||||
|
await _database!.CreateTableAsync<ReadingProgress>();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureInitializedAsync()
|
private async Task EnsureInitializedAsync()
|
||||||
@@ -34,27 +53,33 @@ public class DatabaseService : IDatabaseService
|
|||||||
public async Task<List<Book>> GetAllBooksAsync()
|
public async Task<List<Book>> GetAllBooksAsync()
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
return await _database!.Table<Book>().OrderByDescending(b => b.LastRead).ToListAsync();
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
|
await _database!.Table<Book>().OrderByDescending(b => b.LastRead).ToListAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Book?> GetBookByIdAsync(int id)
|
public async Task<Book?> GetBookByIdAsync(int id)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
return await _database!.Table<Book>().Where(b => b.Id == id).FirstOrDefaultAsync();
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
|
await _database!.Table<Book>().Where(b => b.Id == id).FirstOrDefaultAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> SaveBookAsync(Book book)
|
public async Task<int> SaveBookAsync(Book book)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
if (book.Id != 0)
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
return await _database!.UpdateAsync(book);
|
{
|
||||||
return await _database!.InsertAsync(book);
|
if (book.Id != 0)
|
||||||
|
return await _database!.UpdateAsync(book);
|
||||||
|
return await _database!.InsertAsync(book);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> UpdateBookAsync(Book book)
|
public async Task<int> UpdateBookAsync(Book book)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
return await _database!.UpdateAsync(book);
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
|
await _database!.UpdateAsync(book));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> DeleteBookAsync(Book book)
|
public async Task<int> DeleteBookAsync(Book book)
|
||||||
@@ -68,55 +93,70 @@ public class DatabaseService : IDatabaseService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete progress records
|
// Delete progress records
|
||||||
await _database!.Table<ReadingProgress>().DeleteAsync(p => p.BookId == book.Id);
|
await _retryPolicy.ExecuteAsync(async () =>
|
||||||
|
await _database!.Table<ReadingProgress>().DeleteAsync(p => p.BookId == book.Id));
|
||||||
|
|
||||||
return await _database!.DeleteAsync(book);
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
|
await _database!.DeleteAsync(book));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
public async Task<string?> GetSettingAsync(string key)
|
public async Task<string?> GetSettingAsync(string key)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
var setting = await _database!.Table<AppSettings>().Where(s => s.Key == key).FirstOrDefaultAsync();
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
return setting?.Value;
|
{
|
||||||
|
var setting = await _database!.Table<AppSettings>().Where(s => s.Key == key).FirstOrDefaultAsync();
|
||||||
|
return setting?.Value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetSettingAsync(string key, string value)
|
public async Task SetSettingAsync(string key, string value)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
var existing = await _database!.Table<AppSettings>().Where(s => s.Key == key).FirstOrDefaultAsync();
|
await _retryPolicy.ExecuteAsync(async () =>
|
||||||
if (existing != null)
|
|
||||||
{
|
{
|
||||||
existing.Value = value;
|
var existing = await _database!.Table<AppSettings>().Where(s => s.Key == key).FirstOrDefaultAsync();
|
||||||
await _database.UpdateAsync(existing);
|
if (existing != null)
|
||||||
}
|
{
|
||||||
else
|
existing.Value = value;
|
||||||
{
|
await _database.UpdateAsync(existing);
|
||||||
await _database.InsertAsync(new AppSettings { Key = key, Value = value });
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
await _database.InsertAsync(new AppSettings { Key = key, Value = value });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<string, string>> GetAllSettingsAsync()
|
public async Task<Dictionary<string, string>> GetAllSettingsAsync()
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
var settings = await _database!.Table<AppSettings>().ToListAsync();
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
return settings.ToDictionary(s => s.Key, s => s.Value);
|
{
|
||||||
|
var settings = await _database!.Table<AppSettings>().ToListAsync();
|
||||||
|
return settings.ToDictionary(s => s.Key, s => s.Value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reading Progress
|
// Reading Progress
|
||||||
public async Task SaveProgressAsync(ReadingProgress progress)
|
public async Task SaveProgressAsync(ReadingProgress progress)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
progress.Timestamp = DateTime.UtcNow;
|
await _retryPolicy.ExecuteAsync(async () =>
|
||||||
await _database!.InsertAsync(progress);
|
{
|
||||||
|
progress.Timestamp = DateTime.UtcNow;
|
||||||
|
await _database!.InsertAsync(progress);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ReadingProgress?> GetLatestProgressAsync(int bookId)
|
public async Task<ReadingProgress?> GetLatestProgressAsync(int bookId)
|
||||||
{
|
{
|
||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
return await _database!.Table<ReadingProgress>()
|
return await _retryPolicy.ExecuteAsync(async () =>
|
||||||
.Where(p => p.BookId == bookId)
|
await _database!.Table<ReadingProgress>()
|
||||||
.OrderByDescending(p => p.Timestamp)
|
.Where(p => p.BookId == bookId)
|
||||||
.FirstOrDefaultAsync();
|
.OrderByDescending(p => p.Timestamp)
|
||||||
|
.FirstOrDefaultAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,4 +7,9 @@ public interface ISettingsService
|
|||||||
Task<int> GetIntAsync(string key, int defaultValue = 0);
|
Task<int> GetIntAsync(string key, int defaultValue = 0);
|
||||||
Task SetIntAsync(string key, int value);
|
Task SetIntAsync(string key, int value);
|
||||||
Task<Dictionary<string, string>> GetAllAsync();
|
Task<Dictionary<string, string>> GetAllAsync();
|
||||||
|
|
||||||
|
// Secure storage for sensitive data
|
||||||
|
Task SetSecurePasswordAsync(string password);
|
||||||
|
Task<string> GetSecurePasswordAsync();
|
||||||
|
Task ClearSecurePasswordAsync();
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace BookReader.Services;
|
using BookReader.Models;
|
||||||
|
|
||||||
|
namespace BookReader.Services;
|
||||||
|
|
||||||
public class SettingsService : ISettingsService
|
public class SettingsService : ISettingsService
|
||||||
{
|
{
|
||||||
@@ -37,4 +39,20 @@ public class SettingsService : ISettingsService
|
|||||||
{
|
{
|
||||||
return await _databaseService.GetAllSettingsAsync();
|
return await _databaseService.GetAllSettingsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetSecurePasswordAsync(string password)
|
||||||
|
{
|
||||||
|
await SecureStorage.Default.SetAsync(SecureStorageKeys.CalibrePassword, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetSecurePasswordAsync()
|
||||||
|
{
|
||||||
|
return await SecureStorage.Default.GetAsync(SecureStorageKeys.CalibrePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearSecurePasswordAsync()
|
||||||
|
{
|
||||||
|
SecureStorage.Default.Remove(SecureStorageKeys.CalibrePassword);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ public partial class CalibreLibraryViewModel : BaseViewModel
|
|||||||
{
|
{
|
||||||
var url = await _settingsService.GetAsync(SettingsKeys.CalibreUrl);
|
var url = await _settingsService.GetAsync(SettingsKeys.CalibreUrl);
|
||||||
var username = await _settingsService.GetAsync(SettingsKeys.CalibreUsername);
|
var username = await _settingsService.GetAsync(SettingsKeys.CalibreUsername);
|
||||||
var password = await _settingsService.GetAsync(SettingsKeys.CalibrePassword);
|
var password = await _settingsService.GetSecurePasswordAsync();
|
||||||
|
|
||||||
IsConfigured = !string.IsNullOrWhiteSpace(url);
|
IsConfigured = !string.IsNullOrWhiteSpace(url);
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,20 @@ public partial class ReaderViewModel : BaseViewModel
|
|||||||
|
|
||||||
public async Task InitializeAsync()
|
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, 18);
|
var savedFontSize = await _settingsService.GetIntAsync(SettingsKeys.DefaultFontSize, 18);
|
||||||
var savedFontFamily = await _settingsService.GetAsync(SettingsKeys.DefaultFontFamily, "serif");
|
var savedFontFamily = await _settingsService.GetAsync(SettingsKeys.DefaultFontFamily, "serif");
|
||||||
|
|
||||||
@@ -145,15 +159,22 @@ public partial class ReaderViewModel : BaseViewModel
|
|||||||
|
|
||||||
public async Task SaveLocationsAsync(string locations)
|
public async Task SaveLocationsAsync(string locations)
|
||||||
{
|
{
|
||||||
if (Book == null) return;
|
if (Book == null)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("[ReaderViewModel] Cannot save locations: Book is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Book.Locations = locations;
|
Book.Locations = locations;
|
||||||
// Сохраняем в базу данных
|
|
||||||
await _databaseService.UpdateBookAsync(Book);
|
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)
|
||||||
{
|
{
|
||||||
if (Book == null) return;
|
if (Book == null)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("[ReaderViewModel] Cannot save progress: Book is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Важно: если CFI пустой, не перезаписываем старый прогресс (защита от багов JS)
|
// Важно: если CFI пустой, не перезаписываем старый прогресс (защита от багов JS)
|
||||||
if (string.IsNullOrEmpty(cfi) && progress <= 0) return;
|
if (string.IsNullOrEmpty(cfi) && progress <= 0) return;
|
||||||
@@ -165,7 +186,6 @@ public partial class ReaderViewModel : BaseViewModel
|
|||||||
Book.TotalPages = totalPages;
|
Book.TotalPages = totalPages;
|
||||||
Book.LastRead = DateTime.UtcNow;
|
Book.LastRead = DateTime.UtcNow;
|
||||||
|
|
||||||
// Сохраняем в базу данных
|
|
||||||
await _databaseService.UpdateBookAsync(Book);
|
await _databaseService.UpdateBookAsync(Book);
|
||||||
|
|
||||||
await _databaseService.SaveProgressAsync(new ReadingProgress
|
await _databaseService.SaveProgressAsync(new ReadingProgress
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public partial class SettingsViewModel : BaseViewModel
|
|||||||
{
|
{
|
||||||
CalibreUrl = await _settingsService.GetAsync(SettingsKeys.CalibreUrl);
|
CalibreUrl = await _settingsService.GetAsync(SettingsKeys.CalibreUrl);
|
||||||
CalibreUsername = await _settingsService.GetAsync(SettingsKeys.CalibreUsername);
|
CalibreUsername = await _settingsService.GetAsync(SettingsKeys.CalibreUsername);
|
||||||
CalibrePassword = await _settingsService.GetAsync(SettingsKeys.CalibrePassword);
|
CalibrePassword = await _settingsService.GetSecurePasswordAsync();
|
||||||
DefaultFontSize = await _settingsService.GetIntAsync(SettingsKeys.DefaultFontSize, 18);
|
DefaultFontSize = await _settingsService.GetIntAsync(SettingsKeys.DefaultFontSize, 18);
|
||||||
DefaultFontFamily = await _settingsService.GetAsync(SettingsKeys.DefaultFontFamily, "serif");
|
DefaultFontFamily = await _settingsService.GetAsync(SettingsKeys.DefaultFontFamily, "serif");
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ public partial class SettingsViewModel : BaseViewModel
|
|||||||
{
|
{
|
||||||
await _settingsService.SetAsync(SettingsKeys.CalibreUrl, CalibreUrl);
|
await _settingsService.SetAsync(SettingsKeys.CalibreUrl, CalibreUrl);
|
||||||
await _settingsService.SetAsync(SettingsKeys.CalibreUsername, CalibreUsername);
|
await _settingsService.SetAsync(SettingsKeys.CalibreUsername, CalibreUsername);
|
||||||
await _settingsService.SetAsync(SettingsKeys.CalibrePassword, CalibrePassword);
|
await _settingsService.SetSecurePasswordAsync(CalibrePassword);
|
||||||
await _settingsService.SetIntAsync(SettingsKeys.DefaultFontSize, DefaultFontSize);
|
await _settingsService.SetIntAsync(SettingsKeys.DefaultFontSize, DefaultFontSize);
|
||||||
await _settingsService.SetAsync(SettingsKeys.DefaultFontFamily, DefaultFontFamily);
|
await _settingsService.SetAsync(SettingsKeys.DefaultFontFamily, DefaultFontFamily);
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public partial class ReaderPage : ContentPage
|
|||||||
protected override async void OnDisappearing()
|
protected override async void OnDisappearing()
|
||||||
{
|
{
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
|
_viewModel.OnJavaScriptRequested -= OnJavaScriptRequested;
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
await SaveCurrentProgress();
|
await SaveCurrentProgress();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user