worldhopper/lib/providers/series_navigation_provider.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

54 lines
1.6 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:worldhopper/models/entry.dart';
import 'package:worldhopper/providers/feed_provider.dart';
// Request object for resolving the next entry in a series feed
class NextInSeriesRequest {
final String serverId;
final String feedUrl;
final String currentEntryId;
const NextInSeriesRequest({
required this.serverId,
required this.feedUrl,
required this.currentEntryId,
});
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NextInSeriesRequest &&
runtimeType == other.runtimeType &&
serverId == other.serverId &&
feedUrl == other.feedUrl &&
currentEntryId == other.currentEntryId;
@override
int get hashCode =>
serverId.hashCode ^ feedUrl.hashCode ^ currentEntryId.hashCode;
}
/// Provider that fetches a feed and returns the next entry after the current one.
/// Returns null if the current entry is the last in the feed or not found.
final nextInSeriesProvider = FutureProvider.family<Entry?, NextInSeriesRequest>(
(ref, request) async {
final repository = ref.watch(feedRepositoryProvider);
final feed = await repository.fetchFeed(
request.serverId,
request.feedUrl,
);
final entries = feed.entries;
for (var i = 0; i < entries.length; i++) {
if (entries[i].id == request.currentEntryId) {
if (i + 1 < entries.length) {
return entries[i + 1];
}
// TODO: If this is the last entry on a paginated page,
// fetch feed.nextLink to find the next entry.
return null;
}
}
return null;
},
);