From 7b21eca37b701a52def1d4aa613a11d19a391e61 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 8 Sep 2025 14:09:16 +0600 Subject: [PATCH] fix(playback): play not fetching full playlist if playlist is too long --- .../use_action_callbacks.dart | 7 ++ .../utils/family_paginated.dart | 79 +++++++++++++------ .../metadata_plugin/utils/paginated.dart | 40 +++++++--- 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/lib/components/track_presentation/use_action_callbacks.dart b/lib/components/track_presentation/use_action_callbacks.dart index 22f60ded..6707dd36 100644 --- a/lib/components/track_presentation/use_action_callbacks.dart +++ b/lib/components/track_presentation/use_action_callbacks.dart @@ -13,6 +13,7 @@ import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/logger/logger.dart'; typedef UseActionCallbacks = ({ bool isActive, @@ -82,6 +83,9 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) { allTracks.sublist(initialTracks.length), ); } + } catch (e, stack) { + AppLogger.reportError(e, stack); + rethrow; } finally { isLoading.value = false; } @@ -134,6 +138,9 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) { allTracks.sublist(initialTracks.length), ); } + } catch (e, stack) { + AppLogger.reportError(e, stack); + rethrow; } finally { if (context.mounted) { isLoading.value = false; diff --git a/lib/provider/metadata_plugin/utils/family_paginated.dart b/lib/provider/metadata_plugin/utils/family_paginated.dart index e00740bc..b798dc8e 100644 --- a/lib/provider/metadata_plugin/utils/family_paginated.dart +++ b/lib/provider/metadata_plugin/utils/family_paginated.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:spotube/services/logger/logger.dart'; abstract class FamilyPaginatedAsyncNotifier extends FamilyAsyncNotifier, A> @@ -27,7 +29,8 @@ abstract class FamilyPaginatedAsyncNotifier final items = newState.items.isEmpty ? [] : newState.items.cast(); state = AsyncData(newState.copyWith(items: [...oldItems, ...items])); - } catch (e) { + } catch (e, stack) { + AppLogger.reportError(e, stack); state = AsyncData(oldState!); } } @@ -38,17 +41,32 @@ abstract class FamilyPaginatedAsyncNotifier bool hasMore = true; while (hasMore) { - await update((state) async { - final newState = await fetch( - state.nextOffset!, - state.limit, - ); + final newState = await fetch( + state.value!.nextOffset!, + max(state.value!.limit, 100), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, state.value!.limit), + ) + .catchError( + (e) async { + await Future.delayed(const Duration(milliseconds: 500)); + return fetch(state.value!.nextOffset!, state.value!.limit); + }, + ); - hasMore = newState.hasMore; - final oldItems = state.items.isEmpty ? [] : state.items.cast(); - final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]); - }); + hasMore = newState.hasMore; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + state = AsyncData( + newState.copyWith(items: [...oldItems, ...items]), + ); } return state.value!.items.cast(); @@ -78,7 +96,8 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier ...newState.items.cast(), ]), ); - } catch (e) { + } catch (e, stack) { + AppLogger.reportError(e, stack); state = AsyncData(oldState!); } } @@ -89,18 +108,32 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier bool hasMore = true; while (hasMore) { - await update((state) async { - final newState = await fetch( - state.nextOffset!, - state.limit, - ); + final newState = await fetch( + state.value!.nextOffset!, + max(state.value!.limit, 100), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, state.value!.limit), + ) + .catchError( + (e) async { + await Future.delayed(const Duration(milliseconds: 500)); + return fetch(state.value!.nextOffset!, state.value!.limit); + }, + ); - hasMore = newState.hasMore; - return newState.copyWith(items: [ - ...state.items.cast(), - ...newState.items.cast(), - ]); - }); + hasMore = newState.hasMore; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + state = AsyncData( + newState.copyWith(items: [...oldItems, ...items]), + ); } return state.value!.items.cast(); diff --git a/lib/provider/metadata_plugin/utils/paginated.dart b/lib/provider/metadata_plugin/utils/paginated.dart index e9c7eded..4c77441a 100644 --- a/lib/provider/metadata_plugin/utils/paginated.dart +++ b/lib/provider/metadata_plugin/utils/paginated.dart @@ -1,10 +1,12 @@ import 'dart:async'; +import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; // ignore: implementation_imports import 'package:riverpod/src/async_notifier.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:spotube/services/logger/logger.dart'; mixin PaginatedAsyncNotifierMixin // ignore: invalid_use_of_internal_member @@ -28,7 +30,8 @@ mixin PaginatedAsyncNotifierMixin final items = newState.items.isEmpty ? [] : newState.items.cast(); state = AsyncData(newState.copyWith(items: [...oldItems, ...items])); - } catch (e) { + } catch (e, stack) { + AppLogger.reportError(e, stack); state = AsyncData(oldState!); } } @@ -39,17 +42,32 @@ mixin PaginatedAsyncNotifierMixin bool hasMore = true; while (hasMore) { - await update((state) async { - final newState = await fetch( - state.nextOffset!, - state.limit, - ); + final newState = await fetch( + state.value!.nextOffset!, + max(state.value!.limit, 100), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, state.value!.limit), + ) + .catchError( + (e) async { + await Future.delayed(const Duration(milliseconds: 500)); + return fetch(state.value!.nextOffset!, state.value!.limit); + }, + ); - hasMore = newState.hasMore; - final oldItems = state.items.isEmpty ? [] : state.items.cast(); - final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]); - }); + hasMore = newState.hasMore; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + state = AsyncData( + newState.copyWith(items: [...oldItems, ...items]), + ); } return state.value!.items.cast();