worldhopper/lib/models/reading_progress.dart
Felipe M. 71651a6b21
All checks were successful
ci/woodpecker/pr/pr Pipeline was successful
feat: koreader sync
2026-02-15 13:00:48 +01:00

103 lines
3.1 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,
/// Feed URL this entry was opened from (for "next in series" navigation)
String? seriesFeedUrl,
/// KOReader sync document hash (partial MD5 of the EPUB file)
String? syncHash,
}) = _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?,
seriesFeedUrl: row['series_feed_url'] as String?,
syncHash: row['sync_hash'] as String?,
);
}
/// Calculate reading progress percentage
double get progressPercentage {
if (totalPages == 0) return 0.0;
// EPUB progress stores currentPage as a 0-100 percentage value directly,
// while OPDS-PS stores 0-indexed page numbers.
if (epubLocation != null) {
return currentPage / totalPages;
}
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 {
if (epubLocation != null) {
return currentPage >= totalPages;
}
return 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,
'series_feed_url': seriesFeedUrl,
'sync_hash': syncHash,
};
}
}