Schedule: remove the per-user follow (bookmark, Following filter, AnimeFlow follow calls)

Personal tracking will come from the user's AniList lists; AnimeFlow keeps
downloads, availability and requests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-10 20:51:42 +03:00
1 parent 473c82ec0a
commit f23cc7b311
8 files changed
+22 -130

No files matched your search

+3 -11
View File
@@ -29,7 +29,6 @@ final seriesJson = {
'jellyfin_series_id': 'jf7',
'state': 'released',
'monitored': true,
'followed': true,
'episodes_total': 24,
'episodes_available': 20,
'next_air_date': null,
@@ -110,7 +109,7 @@ void main() {
'to': req.uri.queryParameters['to'],
'items': [
{...episodeJson, 'series_id': 7, 'title': 'Show', 'anidb_id': 70, 'tvdb_id': 700,
'jellyfin_series_id': 'jf7', 'followed': true},
'jellyfin_series_id': 'jf7'},
],
}, etag: 'W/"s1"');
},
@@ -126,7 +125,6 @@ void main() {
expect(it.anidbId, 70);
expect(it.tvdbId, 700);
expect(it.jellyfinSeriesId, 'jf7');
expect(it.followed, isTrue);
final e = it.episode;
expect((e.season, e.episode, e.absolute, e.title, e.anilistId), (2, 3, 15, 'Three', 222));
expect(e.airDate, DateTime.utc(2026, 10, 8, 19, 36, 32));
@@ -146,7 +144,7 @@ void main() {
expect(server.seen, isEmpty);
});
test('/series, /series/{id} with episodes, follow/unfollow', () async {
test('/series, /series/{id} with episodes', () async {
await serve({
'GET /series': (req, res) async => json(res, {'items': [seriesJson]}, etag: 'W/"a"'),
'GET /series/7': (req, res) async => json(res, {
@@ -156,24 +154,18 @@ void main() {
{'season': 1, 'episode': 1, 'status': 'failed', 'reason': 'No release'},
],
}),
'PUT /series/7/follow': (req, res) async => res.statusCode = 204,
'DELETE /series/7/follow': (req, res) async => res.statusCode = 204,
});
final c = client();
final s = (await c.series()).single;
expect(s.seriesId, 7);
expect(s.anilistId, 111);
expect(s.state, SeriesState.released);
expect(s.monitored && s.followed, isTrue);
expect(s.monitored, isTrue);
expect((s.episodesTotal, s.episodesAvailable, s.nextAirDate), (24, 20, null));
final d = await c.seriesDetail(7);
expect(d.episodes.map((e) => e.season), [0, 1]);
expect(d.episodes.last.status, EpisodeStatus.failed);
expect(d.episodes.last.reason, 'No release');
await c.setFollowed(7, true);
await c.setFollowed(7, false);
expect(server.seen.skip(2).map((r) => '${r.method} ${r.uri.path}'),
['PUT /api/app/v1/series/7/follow', 'DELETE /api/app/v1/series/7/follow']);
});
test('/search and the three request bodies', () async {
+5 -7
View File
@@ -20,7 +20,7 @@ Airing airing(int id, int ep, DateTime local) => Airing(
);
AnimeFlowScheduleItem item(int? anilistId, int ep, EpisodeStatus status,
{int season = 1, int? absolute, DateTime? air, int seriesId = 7, bool followed = false,
{int season = 1, int? absolute, DateTime? air, int seriesId = 7,
double? progress, String? reason}) =>
AnimeFlowScheduleItem(
episode: AnimeFlowEpisode(
@@ -36,7 +36,6 @@ AnimeFlowScheduleItem item(int? anilistId, int ep, EpisodeStatus status,
seriesId: seriesId,
title: 'Show',
jellyfinSeriesId: 'jf$seriesId',
followed: followed,
);
void main() {
@@ -150,7 +149,7 @@ void main() {
[airing(100, 3, DateTime(2026, 10, 1, 20)), airing(200, 3, DateTime(2026, 10, 9, 20))],
schedule: [
item(100, 3, EpisodeStatus.available, season: 1),
item(200, 3, EpisodeStatus.downloading, season: 2, progress: 0.4, followed: true),
item(200, 3, EpisodeStatus.downloading, season: 2, progress: 0.4),
],
series: [const AnimeFlowSeries(seriesId: 7, title: 'Show', anilistId: 100, jellyfinSeriesId: 'jf7')],
now: now,
@@ -158,7 +157,6 @@ void main() {
expect(out.map((e) => e.status), [ScheduleStatus.available, ScheduleStatus.downloading]);
expect(out.map((e) => e.season), [1, 2]);
expect(out[1].progress, 0.4);
expect(out[1].followed, isTrue);
expect(out.every((e) => e.tracked && e.seriesId == 7 && e.jellyfinSeriesId == 'jf7'), isTrue);
});
@@ -205,11 +203,11 @@ void main() {
final out = matchSchedule(
[airing(500, 2, DateTime(2026, 10, 7, 20)), airing(500, 3, DateTime(2026, 10, 14, 20))],
schedule: const [],
series: [const AnimeFlowSeries(seriesId: 9, title: 'S', anilistId: 500, followed: true)],
series: [const AnimeFlowSeries(seriesId: 9, title: 'S', anilistId: 500)],
now: now,
);
expect(out.map((e) => e.status), [ScheduleStatus.missing, ScheduleStatus.upcoming]);
expect(out.every((e) => e.followed && e.seriesId == 9), isTrue);
expect(out.every((e) => e.seriesId == 9), isTrue);
});
test('progress only while downloading, reason only when failed', () {
@@ -271,7 +269,7 @@ void main() {
return json(res, {
'items': [
{'season': 1, 'episode': 3, 'anilist_id': 100, 'status': 'available', 'series_id': 7,
'title': 'Show', 'followed': false},
'title': 'Show'},
],
});
case '/flow/series':
+2 -18
View File
@@ -88,7 +88,6 @@ List<ScheduleEntry> entries() => [
airing: airing(111, 'Avail Show', 3, day(0, 9)),
status: ScheduleStatus.available,
tracked: true,
followed: true,
seriesId: 7,
season: 1,
jellyfinSeriesId: 'jf7',
@@ -201,8 +200,6 @@ void main() {
});
case ('GET', '/search'):
json(res, {'items': searchHits});
case ('PUT' || 'DELETE', '/series/7/follow'):
res.statusCode = 204;
case ('POST', '/requests/1/approve' || '/requests/2/reject'):
json(res, {});
default:
@@ -298,13 +295,9 @@ void main() {
await done(tester);
});
testWidgets('filters: following, all airing, niche too, compact', (tester) async {
testWidgets('filters: all airing, niche too, compact', (tester) async {
await pumpPage(tester);
await tester.tap(find.text('Following'));
await tester.pump();
expect(card(111, 3), findsOneWidget);
expect(card(222, 5), findsNothing);
expect(find.text('Following'), findsNothing);
await tester.tap(find.text('All airing'));
await tester.pump();
expect(card(444, 1), findsOneWidget);
@@ -398,15 +391,6 @@ void main() {
await done(tester);
});
testWidgets('follow toggles through AnimeFlow', (tester) async {
final repo = await pumpPage(tester);
await tester.tap(find.byKey(const ValueKey('follow-111-3')));
await io(tester);
expect(server.seen.where((r) => r.method == 'DELETE' && r.uri.path.endsWith('/series/7/follow')), hasLength(1));
expect(repo.forced.length, greaterThanOrEqualTo(2)); // reloaded
await done(tester);
});
testWidgets('put it up resolves the Jellyfin episode', (tester) async {
final jf = FakeJf();
final accounts = JellyfinAccounts(