Fix fewest-viewers sort via GQL VIEWER_COUNT_ASC.
Helix directories are viewer-desc only; local reverse of top pages wrongly showed high-viewer streams for ascending sort. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
47150d0ab3
commit
5a787e0134
4 changed files with 68 additions and 44 deletions
|
|
@ -260,12 +260,6 @@ abstract class ListStoreBase with Store {
|
|||
try {
|
||||
final languages = _languageFilter;
|
||||
final isAsc = sortOrder == StreamViewerSort.viewersAsc;
|
||||
// Asc needs a deeper Helix scan so client-side re-sort can surface
|
||||
// lower-viewer streams (Helix itself is always viewer-desc).
|
||||
final minCount = isAsc ? 80 : 50;
|
||||
final maxPages = isAsc
|
||||
? (_streamsCursor == null ? 20 : 10)
|
||||
: (_streamsCursor == null ? 12 : 8);
|
||||
|
||||
final StreamsTwitch newStreams;
|
||||
switch (listType) {
|
||||
|
|
@ -276,36 +270,56 @@ abstract class ListStoreBase with Store {
|
|||
);
|
||||
break;
|
||||
case ListType.top:
|
||||
newStreams = await twitchApi.getDirectoryStreams(
|
||||
languages: languages,
|
||||
cursor: _streamsCursor,
|
||||
minCount: minCount,
|
||||
maxPages: maxPages,
|
||||
);
|
||||
if (isAsc && twitchGqlApi != null) {
|
||||
// Helix is always viewer-desc — fewest must be requested via GQL.
|
||||
newStreams = await twitchGqlApi!.getStreams(
|
||||
sort: StreamViewerSort.viewersAsc,
|
||||
languages: languages.isEmpty ? null : languages,
|
||||
cursor: _streamsCursor,
|
||||
);
|
||||
} else {
|
||||
newStreams = await twitchApi.getDirectoryStreams(
|
||||
languages: languages,
|
||||
cursor: _streamsCursor,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ListType.category:
|
||||
newStreams = await twitchApi.getDirectoryStreams(
|
||||
languages: languages,
|
||||
gameId: categoryId!,
|
||||
cursor: _streamsCursor,
|
||||
minCount: minCount,
|
||||
maxPages: maxPages,
|
||||
);
|
||||
if (isAsc && twitchGqlApi != null) {
|
||||
newStreams = await twitchGqlApi!.getGameStreams(
|
||||
gameId: categoryId!,
|
||||
sort: StreamViewerSort.viewersAsc,
|
||||
languages: languages.isEmpty ? null : languages,
|
||||
cursor: _streamsCursor,
|
||||
);
|
||||
} else {
|
||||
newStreams = await twitchApi.getDirectoryStreams(
|
||||
languages: languages,
|
||||
gameId: categoryId!,
|
||||
cursor: _streamsCursor,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ListType.tag:
|
||||
// Helix + client tag filter (GQL freeformTags can't paginate without
|
||||
// a non-bot Client-Integrity token).
|
||||
newStreams = await twitchApi.getDirectoryStreams(
|
||||
languages: languages,
|
||||
freeformTag: freeformTag,
|
||||
cursor: _streamsCursor,
|
||||
minCount: minCount,
|
||||
maxPages: maxPages,
|
||||
);
|
||||
if (isAsc && twitchGqlApi != null) {
|
||||
newStreams = await twitchGqlApi!.getStreams(
|
||||
freeformTags: [freeformTag!],
|
||||
sort: StreamViewerSort.viewersAsc,
|
||||
languages: languages.isEmpty ? null : languages,
|
||||
cursor: _streamsCursor,
|
||||
);
|
||||
} else {
|
||||
// Desc: Helix + tag filter (GQL page-2 needs Client-Integrity).
|
||||
newStreams = await twitchApi.getDirectoryStreams(
|
||||
languages: languages,
|
||||
freeformTag: freeformTag,
|
||||
cursor: _streamsCursor,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Empty continuation ⇒ end of directory.
|
||||
// Empty continuation ⇒ end of directory (incl. GQL integrity dead-ends).
|
||||
final isContinuation = _streamsCursor != null;
|
||||
if (isContinuation && newStreams.data.isEmpty) {
|
||||
_streamsCursor = null;
|
||||
|
|
@ -313,18 +327,25 @@ abstract class ListStoreBase with Store {
|
|||
return;
|
||||
}
|
||||
|
||||
// Keep API order. Only sanitize each ASC page — Twitch ASC can briefly
|
||||
// mis-order within a page; never re-sort a Helix desc directory locally.
|
||||
final pageData = isAsc
|
||||
? (List<StreamTwitch>.of(newStreams.data)..sort(
|
||||
(a, b) => a.viewerCount.compareTo(b.viewerCount),
|
||||
))
|
||||
: newStreams.data;
|
||||
|
||||
if (_streamsCursor == null) {
|
||||
_allStreams = newStreams.data.asObservable();
|
||||
_allStreams = pageData.asObservable();
|
||||
} else {
|
||||
final existingIds = _allStreams.map((s) => s.userId).toSet();
|
||||
_allStreams.addAll(
|
||||
newStreams.data.where((s) => !existingIds.contains(s.userId)),
|
||||
pageData.where((s) => !existingIds.contains(s.userId)),
|
||||
);
|
||||
}
|
||||
final next = newStreams.pagination['cursor'];
|
||||
_streamsCursor = (next != null && next.isNotEmpty) ? next : null;
|
||||
|
||||
_applyViewerSort();
|
||||
_error = null;
|
||||
} on SocketException {
|
||||
_error = 'Unable to connect to Twitch';
|
||||
|
|
@ -351,15 +372,6 @@ abstract class ListStoreBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
void _applyViewerSort() {
|
||||
final sorted = _allStreams.toList();
|
||||
sorted.sort((a, b) {
|
||||
final cmp = a.viewerCount.compareTo(b.viewerCount);
|
||||
return sortOrder == StreamViewerSort.viewersAsc ? cmp : -cmp;
|
||||
});
|
||||
_allStreams = sorted.asObservable();
|
||||
}
|
||||
|
||||
/// Persists viewer sort and reloads via the settings reaction.
|
||||
@action
|
||||
Future<void> setSortOrder(StreamViewerSort order) async {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue