feat: navigation store feat: implement OPDS entry caching system Add comprehensive caching for OPDS publications with local image storage and Dublin Core Terms metadata extraction. This enables offline viewing, fixes Library screen to show book titles and covers, and improves overall UX. - Add publications table with full metadata (title, authors, series, publisher, ISBN, language) - Implement local image cache service for covers and thumbnails - Extract DCTerms metadata (series, publisher, language, ISBN) from OPDS feeds - Link reading progress to cached publications - Update Library screen to display cached publication data and covers - Cache publications automatically when starting to read - Fix navigation stack issues by using context.push() instead of context.go() - Add explicit back button to EPUB reader with proper PopScope handling - Implement UNIQUE constraint on reading_progress to prevent duplicates - Save reading progress on screen dispose for both EPUB and image readers Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
82 lines
2.4 KiB
Dart
82 lines
2.4 KiB
Dart
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
|
part 'reading_progress.freezed.dart';
|
|
part 'reading_progress.g.dart';
|
|
|
|
/// Represents a user's reading progress for a publication
|
|
@freezed
|
|
class ReadingProgress with _$ReadingProgress {
|
|
const factory ReadingProgress({
|
|
/// Unique identifier for the progress record
|
|
required String id,
|
|
|
|
/// Publication identifier from OPDS feed
|
|
required String publicationId,
|
|
|
|
/// Server identifier
|
|
required String serverId,
|
|
|
|
/// Current page (0-indexed)
|
|
required int currentPage,
|
|
|
|
/// Total number of pages
|
|
required int totalPages,
|
|
|
|
/// EPUB location for EPUB files (CFI, chapter, or position)
|
|
String? epubLocation,
|
|
|
|
/// Last read timestamp
|
|
required DateTime lastReadAt,
|
|
|
|
/// Publication cache ID (references publications table)
|
|
String? publicationCacheId,
|
|
}) = _ReadingProgress;
|
|
|
|
const ReadingProgress._();
|
|
|
|
/// Create ReadingProgress from JSON
|
|
factory ReadingProgress.fromJson(Map<String, dynamic> json) =>
|
|
_$ReadingProgressFromJson(json);
|
|
|
|
/// Create ReadingProgress from database row
|
|
factory ReadingProgress.fromDatabase(Map<String, dynamic> row) {
|
|
return ReadingProgress(
|
|
id: row['id'] as String,
|
|
publicationId: row['publication_id'] as String,
|
|
serverId: row['server_id'] as String,
|
|
currentPage: row['current_page'] as int,
|
|
totalPages: row['total_pages'] as int,
|
|
epubLocation: row['epub_location'] as String?,
|
|
lastReadAt: DateTime.fromMillisecondsSinceEpoch(row['last_read_at'] as int),
|
|
publicationCacheId: row['publication_cache_id'] as String?,
|
|
);
|
|
}
|
|
|
|
/// Calculate reading progress percentage
|
|
double get progressPercentage {
|
|
if (totalPages == 0) return 0.0;
|
|
return (currentPage + 1) / totalPages;
|
|
}
|
|
|
|
/// Check if the publication has been started
|
|
bool get isStarted => currentPage > 0;
|
|
|
|
/// Check if the publication is completed
|
|
bool get isCompleted => currentPage >= totalPages - 1;
|
|
}
|
|
|
|
extension ReadingProgressX on ReadingProgress {
|
|
/// Convert ReadingProgress to database row
|
|
Map<String, dynamic> toDatabase() {
|
|
return {
|
|
'id': id,
|
|
'publication_id': publicationId,
|
|
'server_id': serverId,
|
|
'current_page': currentPage,
|
|
'total_pages': totalPages,
|
|
'epub_location': epubLocation,
|
|
'last_read_at': lastReadAt.millisecondsSinceEpoch,
|
|
'publication_cache_id': publicationCacheId,
|
|
};
|
|
}
|
|
}
|