worldhopper/lib/models/stream_link.dart
Felipe M. 58a562abe8
feat: refactor server software abstraction with Kavita REST API integration
Rename all OPDS-prefixed models to generic names (Server, Feed, Entry,
Link, StreamLink), expand the ServerSoftware interface to cover all
server interactions, and implement a full Kavita REST API client that
replaces the OPDS delegation.

- Rename OPDS* models to generic names across ~60 files
- Add database migrations 13 (opds_id → entry_id) and 14 (unify credentials)
- Create OPDSServerSoftware wrapping existing OPDS services
- Create KavitaApiClient for direct Kavita REST API calls
- Create KavitaFeedMapper to convert Kavita JSON to Feed/Entry models
- Rewrite KavitaServerSoftware to use native API (no OPDS delegation)
- Unify server credentials (remove softwareUsername/softwarePassword)
- Simplify add/edit server UI to single auth section
- Add test connection button to server add/edit screens
- Add progress indicator to PublicationCard using local and server data
- Eliminate softwareType branching in reader screens
- Add EntryProgress and fetchEntryProgress to ServerSoftware interface
- Fix Entry.acquisitionLink crash on empty links
- Add 59 new tests covering models, services, and providers
2026-04-06 17:46:48 +02:00

50 lines
1.5 KiB
Dart

import 'package:freezed_annotation/freezed_annotation.dart';
part 'stream_link.freezed.dart';
part 'stream_link.g.dart';
/// Represents an OPDS-PSE stream link for page-by-page reading
@freezed
class StreamLink with _$StreamLink {
const factory StreamLink({
/// URL template with {pageNumber} and optionally {maxWidth} placeholders
/// This should be an absolute URL (resolved against server base URL during parsing)
required String href,
/// Image MIME type (image/jpeg, image/png, image/gif)
required String type,
/// Total number of pages
required int pageCount,
/// Last read page (0-indexed), null if never read
int? lastRead,
/// Last read timestamp
DateTime? lastReadDate,
}) = _StreamLink;
const StreamLink._();
/// Create StreamLink from JSON
factory StreamLink.fromJson(Map<String, dynamic> json) =>
_$StreamLinkFromJson(json);
/// Check if the URL template supports maxWidth parameter
bool get supportsMaxWidth => href.contains('{maxWidth}');
/// Generate the URL for a specific page
String getPageUrl(int pageNumber, {int? maxWidth}) {
var url = href.replaceAll('{pageNumber}', pageNumber.toString());
if (supportsMaxWidth && maxWidth != null) {
url = url.replaceAll('{maxWidth}', maxWidth.toString());
} else if (supportsMaxWidth) {
// Remove maxWidth parameter if not provided
url = url.replaceAll(RegExp(r'[?&]maxWidth=\{maxWidth\}'), '');
url = url.replaceAll(RegExp(r'[?&]size=\{maxWidth\}'), '');
}
return url;
}
}