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.1 KiB
Dart
82 lines
2.1 KiB
Dart
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
import 'package:worldhopper/models/enhanced_metadata.dart';
|
|
import 'package:worldhopper/models/opds_entry.dart';
|
|
import 'package:worldhopper/models/opds_link.dart';
|
|
|
|
part 'opds_feed.freezed.dart';
|
|
part 'opds_feed.g.dart';
|
|
|
|
/// Represents an OPDS feed (navigation or acquisition)
|
|
@freezed
|
|
class OPDSFeed with _$OPDSFeed {
|
|
const factory OPDSFeed({
|
|
/// Feed ID
|
|
required String id,
|
|
|
|
/// Feed title
|
|
required String title,
|
|
|
|
/// Feed subtitle
|
|
String? subtitle,
|
|
|
|
/// Feed icon URL
|
|
String? icon,
|
|
|
|
/// Last update timestamp
|
|
DateTime? updated,
|
|
|
|
/// Feed author
|
|
String? author,
|
|
|
|
/// All links in the feed
|
|
@Default([]) List<OPDSLink> links,
|
|
|
|
/// All entries in the feed
|
|
@Default([]) List<OPDSEntry> entries,
|
|
|
|
/// Total results (for paginated feeds)
|
|
int? totalResults,
|
|
|
|
/// Items per page (for paginated feeds)
|
|
int? itemsPerPage,
|
|
|
|
/// Start index (for paginated feeds)
|
|
int? startIndex,
|
|
}) = _OPDSFeed;
|
|
|
|
const OPDSFeed._();
|
|
|
|
/// Create OPDSFeed from JSON
|
|
factory OPDSFeed.fromJson(Map<String, dynamic> json) =>
|
|
_$OPDSFeedFromJson(json);
|
|
|
|
/// Check if this is a navigation feed
|
|
bool get isNavigationFeed => entries.any((entry) => entry.isNavigation);
|
|
|
|
/// Check if this is an acquisition feed (contains publications)
|
|
bool get isAcquisitionFeed => !isNavigationFeed;
|
|
|
|
/// Get the start (root) link
|
|
OPDSLink? get startLink => links.cast<OPDSLink?>().firstWhere(
|
|
(link) => link?.rel == 'start',
|
|
orElse: () => null,
|
|
);
|
|
|
|
/// Get the self link
|
|
OPDSLink? get selfLink => links.cast<OPDSLink?>().firstWhere(
|
|
(link) => link?.rel == 'self',
|
|
orElse: () => null,
|
|
);
|
|
|
|
/// Get the next page link
|
|
OPDSLink? get nextLink => links.cast<OPDSLink?>().firstWhere(
|
|
(link) => link?.rel == 'next',
|
|
orElse: () => null,
|
|
);
|
|
|
|
/// Get the previous page link
|
|
OPDSLink? get previousLink => links.cast<OPDSLink?>().firstWhere(
|
|
(link) => link?.rel == 'previous' || link?.rel == 'prev',
|
|
orElse: () => null,
|
|
);
|
|
}
|