import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:worldhopper/models/opds_link.dart'; import 'package:worldhopper/models/opds_stream_link.dart'; part 'opds_entry.freezed.dart'; part 'opds_entry.g.dart'; /// Represents an entry in an OPDS feed (a publication or collection) @freezed class OPDSEntry with _$OPDSEntry { const factory OPDSEntry({ /// Unique identifier for the entry required String id, /// Entry title required String title, /// Entry summary/description String? summary, /// Entry content (HTML description) String? content, /// Publication date DateTime? published, /// Last update date DateTime? updated, /// Authors @Default([]) List authors, /// Categories/tags @Default([]) List categories, /// All links associated with this entry @Default([]) List links, /// PSE stream link (if available) OPDSStreamLink? streamLink, /// Cover image URL String? coverUrl, /// Thumbnail image URL String? thumbnailUrl, }) = _OPDSEntry; const OPDSEntry._(); /// Create OPDSEntry from JSON factory OPDSEntry.fromJson(Map json) => _$OPDSEntryFromJson(json); /// Check if this entry has a stream link (can be read page by page) bool get hasStreamLink => streamLink != null; /// Check if this entry is a navigation entry (leads to another feed) bool get isNavigation => links.any( (link) => link.rel == 'subsection' || link.rel.contains('navigation'), ); /// Get the acquisition link (for download or reading) OPDSLink? get acquisitionLink => links.firstWhere( (link) => link.rel.contains('acquisition') || link.rel.contains('open-access'), orElse: () => links.first, ); /// Get the self link OPDSLink? get selfLink => links.cast().firstWhere( (link) => link?.rel == 'self', orElse: () => null, ); /// Check if this entry is an EPUB publication bool get isEpub { final link = acquisitionLink; if (link == null) return false; return link.type?.toLowerCase().contains('epub') ?? false; } /// Check if this entry is an image stream (comic/manga) bool get isImageStream => hasStreamLink; }