diff --git a/app/src/main/java/org/jellyfin/androidtv/data/querying/StdItemQuery.kt b/app/src/main/java/org/jellyfin/androidtv/data/querying/StdItemQuery.kt deleted file mode 100644 index 4af50fdcff..0000000000 --- a/app/src/main/java/org/jellyfin/androidtv/data/querying/StdItemQuery.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.jellyfin.androidtv.data.querying - -import org.jellyfin.androidtv.auth.repository.UserRepository -import org.jellyfin.apiclient.model.querying.ItemFields -import org.jellyfin.apiclient.model.querying.ItemQuery -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -class StdItemQuery @JvmOverloads constructor( - fields: Array = defaultFields -) : ItemQuery(), KoinComponent { - companion object { - val defaultFields = arrayOf( - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ItemCounts, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount - ) - } - - init { - userId = get().currentUser.value!!.id.toString() - setFields(fields) - } -} diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseGridFragment.java b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseGridFragment.java index 97bc7384c1..fc1e5b2ba0 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseGridFragment.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseGridFragment.java @@ -39,7 +39,6 @@ import org.jellyfin.androidtv.constant.QueryType; import org.jellyfin.androidtv.data.model.FilterOptions; import org.jellyfin.androidtv.data.querying.GetUserViewsRequest; -import org.jellyfin.androidtv.data.querying.StdItemQuery; import org.jellyfin.androidtv.data.repository.CustomMessageRepository; import org.jellyfin.androidtv.data.repository.UserViewsRepository; import org.jellyfin.androidtv.data.service.BackgroundService; @@ -62,7 +61,6 @@ import org.jellyfin.androidtv.util.KeyProcessor; import org.jellyfin.androidtv.util.Utils; import org.jellyfin.androidtv.util.apiclient.EmptyLifecycleAwareResponse; -import org.jellyfin.apiclient.model.querying.ItemFields; import org.jellyfin.sdk.api.client.ApiClient; import org.jellyfin.sdk.model.api.BaseItemDto; import org.jellyfin.sdk.model.api.BaseItemKind; @@ -548,47 +546,22 @@ private void setAutoCardGridValues() { } private void setupQueries() { - StdItemQuery query = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.ChildCount, - ItemFields.MediaSources, - ItemFields.MediaStreams, - ItemFields.DisplayPreferencesId - }); - query.setParentId(mParentId.toString()); if (mFolder.getType() == BaseItemKind.USER_VIEW || mFolder.getType() == BaseItemKind.COLLECTION_FOLDER) { CollectionType type = mFolder.getCollectionType() != null ? mFolder.getCollectionType() : CollectionType.UNKNOWN; - switch (type) { - case MOVIES: - query.setIncludeItemTypes(new String[]{"Movie"}); - query.setRecursive(true); - break; - case TVSHOWS: - query.setIncludeItemTypes(new String[]{"Series"}); - query.setRecursive(true); - break; - case BOXSETS: - query.setIncludeItemTypes(new String[]{"BoxSet"}); - query.setParentId(null); - query.setRecursive(true); - break; - case MUSIC: - //Special queries needed for album artists - String includeType = getArguments().getString(Extras.IncludeType); - if ("AlbumArtist".equals(includeType)) { - setRowDef(new BrowseRowDef("", BrowsingUtils.createAlbumArtistsRequest(mParentId), CHUNK_SIZE_MINIMUM, new ChangeTriggerType[]{})); - return; - } else if ("Artist".equals(includeType)) { - setRowDef(new BrowseRowDef("", BrowsingUtils.createArtistsRequest(mParentId), CHUNK_SIZE_MINIMUM, new ChangeTriggerType[]{})); - return; - } - query.setIncludeItemTypes(new String[]{includeType != null ? includeType : "MusicAlbum"}); - query.setRecursive(true); - break; + if (type == CollectionType.MUSIC) { + //Special queries needed for album artists + String includeType = getArguments().getString(Extras.IncludeType, null); + if ("AlbumArtist".equals(includeType)) { + setRowDef(new BrowseRowDef("", BrowsingUtils.createAlbumArtistsRequest(mParentId), CHUNK_SIZE_MINIMUM, new ChangeTriggerType[]{})); + return; + } else if ("Artist".equals(includeType)) { + setRowDef(new BrowseRowDef("", BrowsingUtils.createArtistsRequest(mParentId), CHUNK_SIZE_MINIMUM, new ChangeTriggerType[]{})); + return; + } } } - setRowDef(new BrowseRowDef("", query, CHUNK_SIZE_MINIMUM, false, true)); + setRowDef(new BrowseRowDef("", BrowsingUtils.createBrowseGridItemsRequest(mFolder), CHUNK_SIZE_MINIMUM, false, true)); } @Override diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRecordingsFragment.java b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRecordingsFragment.java index 72661c0227..294bb30f26 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRecordingsFragment.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRecordingsFragment.java @@ -20,6 +20,7 @@ import org.jellyfin.androidtv.util.TimeUtils; import org.jellyfin.androidtv.util.Utils; import org.jellyfin.androidtv.util.apiclient.LifecycleAwareResponse; +import org.jellyfin.androidtv.util.sdk.compat.JavaCompat; import org.jellyfin.apiclient.interaction.ApiClient; import org.jellyfin.apiclient.model.dto.BaseItemDto; import org.jellyfin.apiclient.model.dto.BaseItemType; @@ -104,7 +105,7 @@ public void onResponse(TimerInfoDtoResult response) { } } if (nearTimers.size() > 0) { - ItemRowAdapter scheduledAdapter = new ItemRowAdapter(requireContext(), nearTimers, mCardPresenter, mRowsAdapter, true); + ItemRowAdapter scheduledAdapter = new ItemRowAdapter(requireContext(), JavaCompat.mapBaseItemCollection(nearTimers), mCardPresenter, mRowsAdapter, true); scheduledAdapter.Retrieve(); ListRow scheduleRow = new ListRow(new HeaderItem("Scheduled in Next 24 Hours"), scheduledAdapter); mRowsAdapter.add(0, scheduleRow); diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRowDef.java b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRowDef.java index c9533c88e5..ab8beb4b32 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRowDef.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseRowDef.java @@ -5,9 +5,9 @@ import org.jellyfin.androidtv.data.querying.GetSeriesTimersRequest; import org.jellyfin.androidtv.data.querying.GetSpecialsRequest; import org.jellyfin.androidtv.data.querying.GetUserViewsRequest; -import org.jellyfin.apiclient.model.querying.ItemQuery; import org.jellyfin.sdk.model.api.request.GetAlbumArtistsRequest; import org.jellyfin.sdk.model.api.request.GetArtistsRequest; +import org.jellyfin.sdk.model.api.request.GetItemsRequest; import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest; import org.jellyfin.sdk.model.api.request.GetLiveTvChannelsRequest; import org.jellyfin.sdk.model.api.request.GetNextUpRequest; @@ -18,7 +18,7 @@ public class BrowseRowDef { private String headerText; - private ItemQuery query; + private GetItemsRequest query; private GetNextUpRequest nextUpQuery; private GetSimilarItemsRequest similarQuery; private GetLatestMediaRequest latestItemsQuery; @@ -39,11 +39,11 @@ public class BrowseRowDef { private ChangeTriggerType[] changeTriggers; - public BrowseRowDef(String header, ItemQuery query, int chunkSize) { + public BrowseRowDef(String header, GetItemsRequest query, int chunkSize) { this(header, query, chunkSize, false, false); } - public BrowseRowDef(String header, ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight) { + public BrowseRowDef(String header, GetItemsRequest query, int chunkSize, boolean preferParentThumb, boolean staticHeight) { headerText = header; this.query = query; this.chunkSize = chunkSize; @@ -52,15 +52,15 @@ public BrowseRowDef(String header, ItemQuery query, int chunkSize, boolean prefe this.queryType = QueryType.Items; } - public BrowseRowDef(String header, ItemQuery query, int chunkSize, ChangeTriggerType[] changeTriggers) { + public BrowseRowDef(String header, GetItemsRequest query, int chunkSize, ChangeTriggerType[] changeTriggers) { this(header, query, chunkSize, false, false, changeTriggers); } - public BrowseRowDef(String header, ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, ChangeTriggerType[] changeTriggers) { + public BrowseRowDef(String header, GetItemsRequest query, int chunkSize, boolean preferParentThumb, boolean staticHeight, ChangeTriggerType[] changeTriggers) { this(header,query,chunkSize,preferParentThumb,staticHeight,changeTriggers,QueryType.Items); } - public BrowseRowDef(String header, ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, ChangeTriggerType[] changeTriggers, QueryType queryType) { + public BrowseRowDef(String header, GetItemsRequest query, int chunkSize, boolean preferParentThumb, boolean staticHeight, ChangeTriggerType[] changeTriggers, QueryType queryType) { headerText = header; this.query = query; this.chunkSize = chunkSize; @@ -171,7 +171,7 @@ public String getHeaderText() { return headerText; } - public ItemQuery getQuery() { + public GetItemsRequest getQuery() { return query; } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseViewFragment.java b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseViewFragment.java index ca5ab1ea3c..18f72b41c7 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseViewFragment.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowseViewFragment.java @@ -14,7 +14,6 @@ import org.jellyfin.androidtv.constant.LiveTvOption; import org.jellyfin.androidtv.constant.QueryType; import org.jellyfin.androidtv.data.querying.GetSeriesTimersRequest; -import org.jellyfin.androidtv.data.querying.StdItemQuery; import org.jellyfin.androidtv.preference.UserPreferences; import org.jellyfin.androidtv.ui.GridButton; import org.jellyfin.androidtv.ui.itemhandling.ItemRowAdapter; @@ -23,21 +22,19 @@ import org.jellyfin.androidtv.util.TimeUtils; import org.jellyfin.androidtv.util.Utils; import org.jellyfin.androidtv.util.apiclient.LifecycleAwareResponse; +import org.jellyfin.androidtv.util.sdk.compat.JavaCompat; import org.jellyfin.apiclient.interaction.ApiClient; import org.jellyfin.apiclient.model.dto.BaseItemDto; import org.jellyfin.apiclient.model.dto.BaseItemType; import org.jellyfin.apiclient.model.entities.LocationType; -import org.jellyfin.apiclient.model.entities.SortOrder; import org.jellyfin.apiclient.model.livetv.RecordingQuery; import org.jellyfin.apiclient.model.livetv.TimerInfoDto; import org.jellyfin.apiclient.model.livetv.TimerQuery; import org.jellyfin.apiclient.model.querying.ItemFields; -import org.jellyfin.apiclient.model.querying.ItemFilter; import org.jellyfin.apiclient.model.querying.ItemsResult; import org.jellyfin.apiclient.model.results.TimerInfoDtoResult; import org.jellyfin.sdk.model.api.BaseItemKind; import org.jellyfin.sdk.model.api.CollectionType; -import org.jellyfin.sdk.model.api.ItemSortBy; import org.jellyfin.sdk.model.api.request.GetNextUpRequest; import org.koin.java.KoinJavaComponent; @@ -61,59 +58,16 @@ protected void setupQueries(final RowLoader rowLoader) { itemType = BaseItemKind.MOVIE; //Resume - StdItemQuery resumeMovies = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ItemCounts, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount, - ItemFields.MediaStreams, - ItemFields.MediaSources - }); - resumeMovies.setIncludeItemTypes(new String[]{"Movie"}); - resumeMovies.setRecursive(true); - resumeMovies.setParentId(mFolder.getId().toString()); - resumeMovies.setImageTypeLimit(1); - resumeMovies.setLimit(50); - resumeMovies.setCollapseBoxSetItems(false); - resumeMovies.setEnableTotalRecordCount(false); - resumeMovies.setFilters(new ItemFilter[]{ItemFilter.IsResumable}); - resumeMovies.setSortBy(new String[]{ItemSortBy.DATE_PLAYED.getSerialName()}); - resumeMovies.setSortOrder(SortOrder.Descending); - mRows.add(new BrowseRowDef(getString(R.string.lbl_continue_watching), resumeMovies, 0, new ChangeTriggerType[]{ChangeTriggerType.MoviePlayback})); + mRows.add(new BrowseRowDef(getString(R.string.lbl_continue_watching), BrowsingUtils.createResumeItemsRequest(mFolder.getId(), BaseItemKind.MOVIE), 0, new ChangeTriggerType[]{ChangeTriggerType.MoviePlayback})); //Latest mRows.add(new BrowseRowDef(getString(R.string.lbl_latest), BrowsingUtils.createLatestMediaRequest(mFolder.getId()), new ChangeTriggerType[]{ChangeTriggerType.MoviePlayback, ChangeTriggerType.LibraryUpdated})); //Favorites - StdItemQuery favorites = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ItemCounts, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount, - ItemFields.MediaStreams, - ItemFields.MediaSources - }); - favorites.setIncludeItemTypes(new String[]{"Movie"}); - favorites.setRecursive(true); - favorites.setParentId(mFolder.getId().toString()); - favorites.setImageTypeLimit(1); - favorites.setFilters(new ItemFilter[]{ItemFilter.IsFavorite}); - favorites.setSortBy(new String[]{ItemSortBy.SORT_NAME.getSerialName()}); - mRows.add(new BrowseRowDef(getString(R.string.lbl_favorites), favorites, 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate})); + mRows.add(new BrowseRowDef(getString(R.string.lbl_favorites), BrowsingUtils.createFavoriteItemsRequest(mFolder.getId(), BaseItemKind.MOVIE), 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate})); //Collections - StdItemQuery collections = new StdItemQuery(); - collections.setFields(new ItemFields[]{ - ItemFields.ChildCount - }); - collections.setIncludeItemTypes(new String[]{"BoxSet"}); - collections.setRecursive(true); - collections.setImageTypeLimit(1); - //collections.setParentId(mFolder.getId()); - collections.setSortBy(new String[]{ItemSortBy.SORT_NAME.getSerialName()}); - mRows.add(new BrowseRowDef(getString(R.string.lbl_collections), collections, 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated})); + mRows.add(new BrowseRowDef(getString(R.string.lbl_collections), BrowsingUtils.createCollectionsRequest(mFolder.getId()), 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated})); rowLoader.loadRows(mRows); break; @@ -121,22 +75,7 @@ protected void setupQueries(final RowLoader rowLoader) { itemType = BaseItemKind.SERIES; //Resume - StdItemQuery resumeEpisodes = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ChildCount, - ItemFields.MediaSources, - ItemFields.MediaStreams - }); - resumeEpisodes.setIncludeItemTypes(new String[]{"Episode"}); - resumeEpisodes.setLimit(50); - resumeEpisodes.setParentId(mFolder.getId().toString()); - resumeEpisodes.setRecursive(true); - resumeEpisodes.setImageTypeLimit(1); - resumeEpisodes.setFilters(new ItemFilter[]{ItemFilter.IsResumable}); - resumeEpisodes.setSortBy(new String[]{ItemSortBy.DATE_PLAYED.getSerialName()}); - resumeEpisodes.setSortOrder(SortOrder.Descending); - mRows.add(new BrowseRowDef(getString(R.string.lbl_continue_watching), resumeEpisodes, 0, new ChangeTriggerType[]{ChangeTriggerType.TvPlayback})); + mRows.add(new BrowseRowDef(getString(R.string.lbl_continue_watching), BrowsingUtils.createResumeItemsRequest(mFolder.getId(), BaseItemKind.EPISODE), 0, new ChangeTriggerType[]{ChangeTriggerType.TvPlayback})); //Next up GetNextUpRequest getNextUpRequest = BrowsingUtils.createGetNextUpRequest(mFolder.getId()); @@ -144,39 +83,14 @@ protected void setupQueries(final RowLoader rowLoader) { //Premieres if (KoinJavaComponent.get(UserPreferences.class).get(UserPreferences.Companion.getPremieresEnabled())) { - StdItemQuery newQuery = new StdItemQuery(new ItemFields[]{ - ItemFields.DateCreated, - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ChildCount - }); - newQuery.setUserId(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue().getId().toString()); - newQuery.setIncludeItemTypes(new String[]{"Episode"}); - newQuery.setParentId(mFolder.getId().toString()); - newQuery.setRecursive(true); - newQuery.setIsVirtualUnaired(false); - newQuery.setIsMissing(false); - newQuery.setImageTypeLimit(1); - newQuery.setFilters(new ItemFilter[]{ItemFilter.IsUnplayed}); - newQuery.setSortBy(new String[]{ItemSortBy.DATE_CREATED.getSerialName()}); - newQuery.setSortOrder(SortOrder.Descending); - newQuery.setEnableTotalRecordCount(false); - newQuery.setLimit(300); - mRows.add(new BrowseRowDef(getString(R.string.lbl_new_premieres), newQuery, 0, true, true, new ChangeTriggerType[]{ChangeTriggerType.TvPlayback}, QueryType.Premieres)); + mRows.add(new BrowseRowDef(getString(R.string.lbl_new_premieres), BrowsingUtils.createPremieresRequest(mFolder.getId()), 0, true, true, new ChangeTriggerType[]{ChangeTriggerType.TvPlayback}, QueryType.Premieres)); } //Latest content added mRows.add(new BrowseRowDef(getString(R.string.lbl_latest), BrowsingUtils.createLatestMediaRequest(mFolder.getId(), BaseItemKind.EPISODE, true), new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated})); //Favorites - StdItemQuery tvFavorites = new StdItemQuery(); - tvFavorites.setIncludeItemTypes(new String[]{"Series"}); - tvFavorites.setRecursive(true); - tvFavorites.setParentId(mFolder.getId().toString()); - tvFavorites.setImageTypeLimit(1); - tvFavorites.setFilters(new ItemFilter[]{ItemFilter.IsFavorite}); - tvFavorites.setSortBy(new String[]{ItemSortBy.SORT_NAME.getSerialName()}); - mRows.add(new BrowseRowDef(getString(R.string.lbl_favorites), tvFavorites, 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate})); + mRows.add(new BrowseRowDef(getString(R.string.lbl_favorites), BrowsingUtils.createFavoriteItemsRequest(mFolder.getId(), BaseItemKind.SERIES), 60, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate})); rowLoader.loadRows(mRows); break; @@ -185,40 +99,13 @@ protected void setupQueries(final RowLoader rowLoader) { mRows.add(new BrowseRowDef(getString(R.string.lbl_latest), BrowsingUtils.createLatestMediaRequest(mFolder.getId(), BaseItemKind.AUDIO, true), new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated})); //Last Played - StdItemQuery lastPlayed = new StdItemQuery(); - lastPlayed.setIncludeItemTypes(new String[]{"Audio"}); - lastPlayed.setRecursive(true); - lastPlayed.setParentId(mFolder.getId().toString()); - lastPlayed.setImageTypeLimit(1); - lastPlayed.setFilters(new ItemFilter[]{ItemFilter.IsPlayed}); - lastPlayed.setSortBy(new String[]{ItemSortBy.DATE_PLAYED.getSerialName()}); - lastPlayed.setSortOrder(SortOrder.Descending); - lastPlayed.setEnableTotalRecordCount(false); - lastPlayed.setLimit(50); - mRows.add(new BrowseRowDef(getString(R.string.lbl_last_played), lastPlayed, 0, false, true, new ChangeTriggerType[]{ChangeTriggerType.MusicPlayback, ChangeTriggerType.LibraryUpdated})); + mRows.add(new BrowseRowDef(getString(R.string.lbl_last_played), BrowsingUtils.createLastPlayedRequest(mFolder.getId()), 0, false, true, new ChangeTriggerType[]{ChangeTriggerType.MusicPlayback, ChangeTriggerType.LibraryUpdated})); //Favorites - StdItemQuery favAlbums = new StdItemQuery(); - favAlbums.setIncludeItemTypes(new String[]{"MusicAlbum", "MusicArtist"}); - favAlbums.setRecursive(true); - favAlbums.setParentId(mFolder.getId().toString()); - favAlbums.setImageTypeLimit(1); - favAlbums.setFilters(new ItemFilter[]{ItemFilter.IsFavorite}); - favAlbums.setSortBy(new String[]{ItemSortBy.SORT_NAME.getSerialName()}); - mRows.add(new BrowseRowDef(getString(R.string.lbl_favorites), favAlbums, 60, false, true, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate})); + mRows.add(new BrowseRowDef(getString(R.string.lbl_favorites), BrowsingUtils.createFavoriteItemsRequest(mFolder.getId(), BaseItemKind.MUSIC_ALBUM), 60, false, true, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated, ChangeTriggerType.FavoriteUpdate})); //AudioPlaylists - StdItemQuery playlists = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.CumulativeRunTimeTicks, - ItemFields.ChildCount - }); - playlists.setIncludeItemTypes(new String[]{"Playlist"}); - playlists.setImageTypeLimit(1); - playlists.setRecursive(true); - playlists.setSortBy(new String[]{ItemSortBy.DATE_CREATED.getSerialName()}); - playlists.setSortOrder(SortOrder.Descending); - mRows.add(new BrowseRowDef(getString(R.string.lbl_playlists), playlists, 60, false, true, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated}, QueryType.AudioPlaylists)); + mRows.add(new BrowseRowDef(getString(R.string.lbl_playlists), BrowsingUtils.createPlaylistsRequest(), 60, false, true, new ChangeTriggerType[]{ChangeTriggerType.LibraryUpdated}, QueryType.AudioPlaylists)); rowLoader.loadRows(mRows); break; @@ -311,19 +198,19 @@ public void onResponse(TimerInfoDtoResult response) { //Now insert our smart rows if (weekItems.size() > 0) { - ItemRowAdapter weekAdapter = new ItemRowAdapter(requireContext(), weekItems, mCardPresenter, mRowsAdapter, true); + ItemRowAdapter weekAdapter = new ItemRowAdapter(requireContext(), JavaCompat.mapBaseItemCollection(weekItems), mCardPresenter, mRowsAdapter, true); weekAdapter.Retrieve(); ListRow weekRow = new ListRow(new HeaderItem("Past Week"), weekAdapter); mRowsAdapter.add(0, weekRow); } if (nearTimers.size() > 0) { - ItemRowAdapter scheduledAdapter = new ItemRowAdapter(requireContext(), nearTimers, mCardPresenter, mRowsAdapter, true); + ItemRowAdapter scheduledAdapter = new ItemRowAdapter(requireContext(), JavaCompat.mapBaseItemCollection(nearTimers), mCardPresenter, mRowsAdapter, true); scheduledAdapter.Retrieve(); ListRow scheduleRow = new ListRow(new HeaderItem("Scheduled in Next 24 Hours"), scheduledAdapter); mRowsAdapter.add(0, scheduleRow); } if (dayItems.size() > 0) { - ItemRowAdapter dayAdapter = new ItemRowAdapter(requireContext(), dayItems, mCardPresenter, mRowsAdapter, true); + ItemRowAdapter dayAdapter = new ItemRowAdapter(requireContext(), JavaCompat.mapBaseItemCollection(dayItems), mCardPresenter, mRowsAdapter, true); dayAdapter.Retrieve(); ListRow dayRow = new ListRow(new HeaderItem("Past 24 Hours"), dayAdapter); mRowsAdapter.add(0, dayRow); @@ -333,7 +220,7 @@ public void onResponse(TimerInfoDtoResult response) { // no recordings rowLoader.loadRows(mRows); if (nearTimers.size() > 0) { - ItemRowAdapter scheduledAdapter = new ItemRowAdapter(requireContext(), nearTimers, mCardPresenter, mRowsAdapter, true); + ItemRowAdapter scheduledAdapter = new ItemRowAdapter(requireContext(), JavaCompat.mapBaseItemCollection(nearTimers), mCardPresenter, mRowsAdapter, true); scheduledAdapter.Retrieve(); ListRow scheduleRow = new ListRow(new HeaderItem("Scheduled in Next 24 Hours"), scheduledAdapter); mRowsAdapter.add(0, scheduleRow); diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowsingUtils.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowsingUtils.kt index 3329e16cdc..8a601b5296 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowsingUtils.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/BrowsingUtils.kt @@ -8,10 +8,14 @@ import org.jellyfin.sdk.api.client.exception.ApiClientException import org.jellyfin.sdk.api.client.extensions.itemsApi import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.ItemFields +import org.jellyfin.sdk.model.api.ItemFilter import org.jellyfin.sdk.model.api.ItemSortBy +import org.jellyfin.sdk.model.api.SortOrder import org.jellyfin.sdk.model.api.request.GetAlbumArtistsRequest import org.jellyfin.sdk.model.api.request.GetArtistsRequest +import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest import org.jellyfin.sdk.model.api.request.GetLiveTvChannelsRequest import org.jellyfin.sdk.model.api.request.GetNextUpRequest @@ -237,4 +241,194 @@ object BrowsingUtils { ), parentId = parentId, ) + + @JvmStatic + fun createPersonItemsRequest(personId: UUID, itemType: BaseItemKind) = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ), + personIds = setOf(personId), + recursive = true, + includeItemTypes = setOf(itemType), + sortBy = setOf(ItemSortBy.SORT_NAME), + ) + + @JvmStatic + fun createArtistItemsRequest(artistId: UUID, itemType: BaseItemKind) = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ), + artistIds = setOf(artistId), + recursive = true, + includeItemTypes = setOf(itemType), + sortBy = setOf(ItemSortBy.SORT_NAME), + ) + + @JvmStatic + fun createNextEpisodesRequest(seasonId: UUID, indexNumber: Int) = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ), + parentId = seasonId, + includeItemTypes = setOf(BaseItemKind.EPISODE), + startIndex = indexNumber, + limit = 20, + ) + + @JvmStatic + fun createResumeItemsRequest(parentId: UUID, itemType: BaseItemKind) = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ItemFields.MEDIA_STREAMS, + ItemFields.MEDIA_SOURCES, + ), + includeItemTypes = setOf(itemType), + recursive = true, + parentId = parentId, + imageTypeLimit = 1, + limit = 50, + collapseBoxSetItems = false, + enableTotalRecordCount = false, + filters = setOf(ItemFilter.IS_RESUMABLE), + sortBy = setOf(ItemSortBy.DATE_PLAYED), + sortOrder = setOf(SortOrder.DESCENDING), + ) + + @JvmStatic + fun createFavoriteItemsRequest(parentId: UUID, itemType: BaseItemKind) = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ItemFields.MEDIA_STREAMS, + ItemFields.MEDIA_SOURCES, + ), + includeItemTypes = setOf(itemType), + recursive = true, + parentId = parentId, + imageTypeLimit = 1, + filters = setOf(ItemFilter.IS_FAVORITE), + sortBy = setOf(ItemSortBy.SORT_NAME), + ) + + @JvmStatic + fun createCollectionsRequest(parentId: UUID) = GetItemsRequest( + fields = setOf(ItemFields.CHILD_COUNT), + includeItemTypes = setOf(BaseItemKind.BOX_SET), + recursive = true, + imageTypeLimit = 1, + parentId = parentId, + sortBy = setOf(ItemSortBy.SORT_NAME), + ) + + @JvmStatic + fun createPremieresRequest(parentId: UUID) = GetItemsRequest( + fields = setOf( + ItemFields.DATE_CREATED, + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.CHILD_COUNT, + ), + includeItemTypes = setOf(BaseItemKind.EPISODE), + parentId = parentId, + recursive = true, + isMissing = false, + imageTypeLimit = 1, + filters = setOf(ItemFilter.IS_UNPLAYED), + sortBy = setOf(ItemSortBy.DATE_CREATED), + sortOrder = setOf(SortOrder.DESCENDING), + enableTotalRecordCount = false, + limit = 300, + ) + + @JvmStatic + fun createLastPlayedRequest(parentId: UUID) = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ), + includeItemTypes = setOf(BaseItemKind.AUDIO), + recursive = true, + parentId = parentId, + imageTypeLimit = 1, + filters = setOf(ItemFilter.IS_PLAYED), + sortBy = setOf(ItemSortBy.DATE_PLAYED), + sortOrder = setOf(SortOrder.DESCENDING), + enableTotalRecordCount = false, + limit = 50, + ) + + @JvmStatic + fun createPlaylistsRequest() = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.CUMULATIVE_RUN_TIME_TICKS, + ItemFields.CHILD_COUNT, + ), + includeItemTypes = setOf(BaseItemKind.PLAYLIST), + imageTypeLimit = 1, + recursive = true, + sortBy = setOf(ItemSortBy.DATE_CREATED), + sortOrder = setOf(SortOrder.DESCENDING), + ) + + @JvmStatic + fun createBrowseGridItemsRequest(parent: BaseItemDto): GetItemsRequest { + val baseRequest = GetItemsRequest( + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.CHILD_COUNT, + ItemFields.MEDIA_SOURCES, + ItemFields.MEDIA_STREAMS, + ItemFields.DISPLAY_PREFERENCES_ID, + ), + parentId = parent.id, + ) + + if (parent.type == BaseItemKind.USER_VIEW || parent.type == BaseItemKind.COLLECTION_FOLDER) { + return when (parent.collectionType) { + CollectionType.MOVIES -> baseRequest.copy( + includeItemTypes = setOf(BaseItemKind.MOVIE), + recursive = true, + ) + + CollectionType.TVSHOWS -> baseRequest.copy( + includeItemTypes = setOf(BaseItemKind.SERIES), + recursive = true, + ) + + CollectionType.BOXSETS -> baseRequest.copy( + includeItemTypes = setOf(BaseItemKind.BOX_SET), + parentId = null, + recursive = true, + ) + + CollectionType.MUSIC -> baseRequest.copy( + includeItemTypes = setOf(BaseItemKind.MUSIC_ALBUM), + recursive = true, + ) + + else -> baseRequest + } + } + + return baseRequest + } } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByGenreFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByGenreFragment.kt index 78974c0e3a..b23ec7b141 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByGenreFragment.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByGenreFragment.kt @@ -1,9 +1,11 @@ package org.jellyfin.androidtv.ui.browsing -import org.jellyfin.androidtv.data.querying.StdItemQuery import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.genresApi +import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.ItemSortBy +import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.koin.android.ext.android.inject class ByGenreFragment : BrowseFolderFragment() { @@ -20,14 +22,21 @@ class ByGenreFragment : BrowseFolderFragment() { ) for (genre in genresResponse.items.orEmpty()) { - val itemsQuery = StdItemQuery().apply { - parentId = folder?.id.toString() - sortBy = arrayOf(ItemSortBy.SORT_NAME.serialName) - includeType?.let { includeItemTypes = arrayOf(it) } - genres = arrayOf(genre.name) - recursive = true - } - rows.add(BrowseRowDef(genre.name, itemsQuery, 40)) + val itemsRequest = GetItemsRequest( + parentId = folder?.id, + sortBy = setOf(ItemSortBy.SORT_NAME), + includeItemTypes = includeType?.let(BaseItemKind::fromNameOrNull)?.let(::setOf), + genres = setOf(genre.name.orEmpty()), + recursive = true, + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ), + ) + rows.add(BrowseRowDef(genre.name, itemsRequest, 40)) } rowLoader.loadRows(rows) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByLetterFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByLetterFragment.kt index d4a5d91ed9..e7681bf3a2 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByLetterFragment.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/ByLetterFragment.kt @@ -1,8 +1,10 @@ package org.jellyfin.androidtv.ui.browsing import org.jellyfin.androidtv.R -import org.jellyfin.androidtv.data.querying.StdItemQuery +import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.ItemSortBy +import org.jellyfin.sdk.model.api.request.GetItemsRequest class ByLetterFragment : BrowseFolderFragment() { override suspend fun setupQueries(rowLoader: RowLoader) { @@ -12,27 +14,41 @@ class ByLetterFragment : BrowseFolderFragment() { val letters = getString(R.string.byletter_letters) // Add a '#' item - val numbersQuery = StdItemQuery().apply { - parentId = folder?.id?.toString() - sortBy = arrayOf(ItemSortBy.SORT_NAME.serialName) - includeType?.let { includeItemTypes = arrayOf(it) } - nameLessThan = letters.substring(0, 1) - recursive = true - } - - rows.add(BrowseRowDef("#", numbersQuery, 40)) + val numbersItemsRequest = GetItemsRequest( + parentId = folder?.id, + sortBy = setOf(ItemSortBy.SORT_NAME), + includeItemTypes = includeType?.let(BaseItemKind::fromNameOrNull)?.let(::setOf), + nameStartsWith = letters.substring(0, 1), + recursive = true, + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ), + ) + + rows.add(BrowseRowDef("#", numbersItemsRequest, 40)) // Add all the defined letters for (letter in letters.toCharArray()) { - val letterQuery = StdItemQuery().apply { - parentId = folder?.id?.toString() - sortBy = arrayOf(ItemSortBy.SORT_NAME.serialName) - includeType?.let { includeItemTypes = arrayOf(it) } - nameStartsWith = letter.toString() - recursive = true - } - - rows.add(BrowseRowDef(letter.toString(), letterQuery, 40)) + val letterItemsRequest = GetItemsRequest( + parentId = folder?.id, + sortBy = setOf(ItemSortBy.SORT_NAME), + includeItemTypes = includeType?.let(BaseItemKind::fromNameOrNull)?.let(::setOf), + nameStartsWith = letter.toString(), + recursive = true, + fields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ), + ) + + rows.add(BrowseRowDef(letter.toString(), letterItemsRequest, 40)) } rowLoader.loadRows(rows) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/CollectionFragment.java b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/CollectionFragment.java deleted file mode 100644 index eaad3ded70..0000000000 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/CollectionFragment.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.jellyfin.androidtv.ui.browsing; - -import org.jellyfin.androidtv.R; -import org.jellyfin.androidtv.data.querying.StdItemQuery; -import org.jellyfin.apiclient.model.querying.ItemFields; -import org.jellyfin.sdk.model.api.BaseItemKind; - -public class CollectionFragment extends EnhancedBrowseFragment { - @Override - protected void setupQueries(RowLoader rowLoader) { - StdItemQuery movies = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ItemCounts, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount, - ItemFields.MediaStreams, - ItemFields.MediaSources - }); - movies.setParentId(mFolder.getId().toString()); - movies.setIncludeItemTypes(new String[]{BaseItemKind.MOVIE.getSerialName()}); - mRows.add(new BrowseRowDef(getString(R.string.lbl_movies), movies, 100)); - - StdItemQuery series = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ItemCounts, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount, - ItemFields.MediaStreams, - ItemFields.MediaSources - }); - series.setParentId(mFolder.getId().toString()); - series.setIncludeItemTypes(new String[]{BaseItemKind.SERIES.getSerialName()}); - mRows.add(new BrowseRowDef(getString(R.string.lbl_tv_series), series, 100)); - - StdItemQuery others = new StdItemQuery(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.Overview, - ItemFields.ItemCounts, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount, - ItemFields.MediaStreams, - ItemFields.MediaSources - }); - others.setParentId(mFolder.getId().toString()); - others.setExcludeItemTypes(new String[]{BaseItemKind.MOVIE.getSerialName(), BaseItemKind.SERIES.getSerialName()}); - mRows.add(new BrowseRowDef(getString(R.string.lbl_other), others, 100)); - - rowLoader.loadRows(mRows); - } -} diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/CollectionFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/CollectionFragment.kt new file mode 100644 index 0000000000..dfa4d63f8c --- /dev/null +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/CollectionFragment.kt @@ -0,0 +1,45 @@ +package org.jellyfin.androidtv.ui.browsing + +import org.jellyfin.androidtv.R +import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.ItemFields +import org.jellyfin.sdk.model.api.request.GetItemsRequest + +class CollectionFragment : EnhancedBrowseFragment() { + companion object { + private val itemFields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ItemFields.MEDIA_STREAMS, + ItemFields.MEDIA_SOURCES, + ) + } + + override fun setupQueries(rowLoader: RowLoader) { + val movies = GetItemsRequest( + fields = itemFields, + parentId = mFolder.id, + includeItemTypes = setOf(BaseItemKind.MOVIE), + ) + mRows.add(BrowseRowDef(getString(R.string.lbl_movies), movies, 100)) + + val series = GetItemsRequest( + fields = itemFields, + parentId = mFolder.id, + includeItemTypes = setOf(BaseItemKind.SERIES), + ) + mRows.add(BrowseRowDef(getString(R.string.lbl_tv_series), series, 100)) + + val others = GetItemsRequest( + fields = itemFields, + parentId = mFolder.id, + excludeItemTypes = setOf(BaseItemKind.MOVIE, BaseItemKind.SERIES), + ) + mRows.add(BrowseRowDef(getString(R.string.lbl_other), others, 100)) + + rowLoader.loadRows(mRows) + } +} diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/GenericFolderFragment.java b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/GenericFolderFragment.java deleted file mode 100644 index 91795aa241..0000000000 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/GenericFolderFragment.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.jellyfin.androidtv.ui.browsing; - -import android.os.Bundle; - -import org.jellyfin.androidtv.R; -import org.jellyfin.androidtv.data.querying.GetSpecialsRequest; -import org.jellyfin.androidtv.data.querying.StdItemQuery; -import org.jellyfin.androidtv.util.Utils; -import org.jellyfin.apiclient.model.entities.SortOrder; -import org.jellyfin.apiclient.model.querying.ItemFilter; -import org.jellyfin.sdk.model.api.BaseItemKind; -import org.jellyfin.sdk.model.api.ItemSortBy; - -import java.util.Arrays; - -public class GenericFolderFragment extends EnhancedBrowseFragment { - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - } - - private static BaseItemKind[] showSpecialViewTypes = new BaseItemKind[]{ - BaseItemKind.COLLECTION_FOLDER, - BaseItemKind.FOLDER, - BaseItemKind.USER_VIEW, - BaseItemKind.CHANNEL_FOLDER_ITEM - }; - - @Override - protected void setupQueries(RowLoader rowLoader) { - if (Utils.getSafeValue(mFolder.getChildCount(), 0) > 0 || - mFolder.getType() == BaseItemKind.CHANNEL || - mFolder.getType() == BaseItemKind.CHANNEL_FOLDER_ITEM || - mFolder.getType() == BaseItemKind.USER_VIEW || - mFolder.getType() == BaseItemKind.COLLECTION_FOLDER) { - boolean showSpecialViews = Arrays.asList(showSpecialViewTypes).contains(mFolder.getType()); - - if (showSpecialViews) { - if (mFolder.getType() != BaseItemKind.CHANNEL_FOLDER_ITEM) { - StdItemQuery resume = new StdItemQuery(); - resume.setParentId(mFolder.getId().toString()); - resume.setLimit(50); - resume.setFilters(new ItemFilter[]{ItemFilter.IsResumable}); - resume.setSortBy(new String[]{ItemSortBy.DATE_PLAYED.getSerialName()}); - resume.setSortOrder(SortOrder.Descending); - mRows.add(new BrowseRowDef(getString(R.string.lbl_continue_watching), resume, 0)); - } - - StdItemQuery latest = new StdItemQuery(); - latest.setParentId(mFolder.getId().toString()); - latest.setLimit(50); - latest.setFilters(new ItemFilter[]{ItemFilter.IsUnplayed}); - latest.setSortBy(new String[]{ItemSortBy.DATE_CREATED.getSerialName()}); - latest.setSortOrder(SortOrder.Descending); - mRows.add(new BrowseRowDef(getString(R.string.lbl_latest), latest, 0)); - - } - - StdItemQuery byName = new StdItemQuery(); - byName.setParentId(mFolder.getId().toString()); - String header = (mFolder.getType() == BaseItemKind.SEASON) ? mFolder.getName() : getString(R.string.lbl_by_name); - mRows.add(new BrowseRowDef(header, byName, 100)); - - if (mFolder.getType() == BaseItemKind.SEASON) { - GetSpecialsRequest specials = new GetSpecialsRequest(mFolder.getId()); - mRows.add(new BrowseRowDef(getString(R.string.lbl_specials), specials)); - } - - rowLoader.loadRows(mRows); - } - } -} diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/GenericFolderFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/GenericFolderFragment.kt new file mode 100644 index 0000000000..77cbe09d53 --- /dev/null +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/GenericFolderFragment.kt @@ -0,0 +1,81 @@ +package org.jellyfin.androidtv.ui.browsing + +import org.jellyfin.androidtv.R +import org.jellyfin.androidtv.data.querying.GetSpecialsRequest +import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.ItemFields +import org.jellyfin.sdk.model.api.ItemFilter +import org.jellyfin.sdk.model.api.ItemSortBy +import org.jellyfin.sdk.model.api.SortOrder +import org.jellyfin.sdk.model.api.request.GetItemsRequest + +class GenericFolderFragment : EnhancedBrowseFragment() { + companion object { + private val showSpecialViewTypes = setOf( + BaseItemKind.COLLECTION_FOLDER, + BaseItemKind.FOLDER, + BaseItemKind.USER_VIEW, + BaseItemKind.CHANNEL_FOLDER_ITEM, + ) + + private val itemFields = setOf( + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, + ItemFields.OVERVIEW, + ItemFields.ITEM_COUNTS, + ItemFields.DISPLAY_PREFERENCES_ID, + ItemFields.CHILD_COUNT, + ) + } + + override fun setupQueries(rowLoader: RowLoader) { + if ((mFolder.childCount == null || mFolder.childCount == 0) && !setOf( + BaseItemKind.CHANNEL, + BaseItemKind.CHANNEL_FOLDER_ITEM, + BaseItemKind.USER_VIEW, + BaseItemKind.COLLECTION_FOLDER, + ).contains(mFolder.type) + ) return + + if (showSpecialViewTypes.contains(mFolder.type)) { + if (mFolder.type != BaseItemKind.CHANNEL_FOLDER_ITEM) { + val resume = GetItemsRequest( + fields = itemFields, + parentId = mFolder.id, + limit = 50, + filters = setOf(ItemFilter.IS_RESUMABLE), + sortBy = setOf(ItemSortBy.DATE_PLAYED), + sortOrder = setOf(SortOrder.DESCENDING), + ) + mRows.add(BrowseRowDef(getString(R.string.lbl_continue_watching), resume, 0)) + } + + val latest = GetItemsRequest( + fields = itemFields, + parentId = mFolder.id, + limit = 50, + filters = setOf(ItemFilter.IS_UNPLAYED), + sortBy = setOf(ItemSortBy.DATE_CREATED), + sortOrder = setOf(SortOrder.DESCENDING), + ) + mRows.add(BrowseRowDef(getString(R.string.lbl_latest), latest, 0)) + } + + val byName = GetItemsRequest( + fields = itemFields, + parentId = mFolder.id, + ) + val header = when (mFolder.type) { + BaseItemKind.SEASON -> mFolder.name + else -> getString(R.string.lbl_by_name) + } + + mRows.add(BrowseRowDef(header, byName, 100)) + + if (mFolder.type == BaseItemKind.SEASON) { + val specials = GetSpecialsRequest(mFolder.id) + mRows.add(BrowseRowDef(getString(R.string.lbl_specials), specials)) + } + + rowLoader.loadRows(mRows) + } +} diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/itemdetail/FullDetailsFragment.java b/app/src/main/java/org/jellyfin/androidtv/ui/itemdetail/FullDetailsFragment.java index bae7a9c413..b74ea6e08a 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/itemdetail/FullDetailsFragment.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/itemdetail/FullDetailsFragment.java @@ -44,7 +44,6 @@ import org.jellyfin.androidtv.data.querying.GetAdditionalPartsRequest; import org.jellyfin.androidtv.data.querying.GetSpecialsRequest; import org.jellyfin.androidtv.data.querying.GetTrailersRequest; -import org.jellyfin.androidtv.data.querying.StdItemQuery; import org.jellyfin.androidtv.data.repository.CustomMessageRepository; import org.jellyfin.androidtv.data.service.BackgroundService; import org.jellyfin.androidtv.databinding.FragmentFullDetailsBinding; @@ -88,12 +87,9 @@ import org.jellyfin.apiclient.interaction.ApiClient; import org.jellyfin.apiclient.model.livetv.ChannelInfoDto; import org.jellyfin.apiclient.model.livetv.TimerQuery; -import org.jellyfin.apiclient.model.querying.ItemFields; -import org.jellyfin.apiclient.model.querying.ItemQuery; import org.jellyfin.sdk.model.api.BaseItemDto; import org.jellyfin.sdk.model.api.BaseItemKind; import org.jellyfin.sdk.model.api.BaseItemPerson; -import org.jellyfin.sdk.model.api.ItemSortBy; import org.jellyfin.sdk.model.api.MediaSourceInfo; import org.jellyfin.sdk.model.api.MediaStream; import org.jellyfin.sdk.model.api.MediaType; @@ -598,61 +594,18 @@ protected void addAdditionalRows(MutableObjectAdapter adapter) { addInfoRows(adapter); break; case PERSON: - - ItemQuery personMovies = new ItemQuery(); - personMovies.setFields(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.ChildCount - }); - personMovies.setUserId(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue().getId().toString()); - personMovies.setPersonIds(new String[]{mBaseItem.getId().toString()}); - personMovies.setRecursive(true); - personMovies.setIncludeItemTypes(new String[]{"Movie"}); - personMovies.setSortBy(new String[]{ItemSortBy.SORT_NAME.getSerialName()}); - ItemRowAdapter personMoviesAdapter = new ItemRowAdapter(requireContext(), personMovies, 100, false, new CardPresenter(), adapter); + ItemRowAdapter personMoviesAdapter = new ItemRowAdapter(requireContext(), BrowsingUtils.createPersonItemsRequest(mBaseItem.getId(), BaseItemKind.MOVIE), 100, false, new CardPresenter(), adapter); addItemRow(adapter, personMoviesAdapter, 0, getString(R.string.lbl_movies)); - ItemQuery personSeries = new ItemQuery(); - personSeries.setFields(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount - }); - personSeries.setUserId(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue().getId().toString()); - personSeries.setPersonIds(new String[]{mBaseItem.getId().toString()}); - personSeries.setRecursive(true); - personSeries.setIncludeItemTypes(new String[]{"Series"}); - personSeries.setSortBy(new String[]{ItemSortBy.SORT_NAME.getSerialName()}); - ItemRowAdapter personSeriesAdapter = new ItemRowAdapter(requireContext(), personSeries, 100, false, new CardPresenter(), adapter); + ItemRowAdapter personSeriesAdapter = new ItemRowAdapter(requireContext(), BrowsingUtils.createPersonItemsRequest(mBaseItem.getId(), BaseItemKind.SERIES), 100, false, new CardPresenter(), adapter); addItemRow(adapter, personSeriesAdapter, 1, getString(R.string.lbl_tv_series)); - ItemQuery personEpisodes = new ItemQuery(); - personEpisodes.setFields(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.DisplayPreferencesId, - ItemFields.ChildCount - }); - personEpisodes.setUserId(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue().getId().toString()); - personEpisodes.setPersonIds(new String[]{mBaseItem.getId().toString()}); - personEpisodes.setRecursive(true); - personEpisodes.setIncludeItemTypes(new String[]{"Episode"}); - personEpisodes.setSortBy(new String[]{ItemSortBy.SERIES_SORT_NAME.getSerialName(), ItemSortBy.SORT_NAME.getSerialName()}); - ItemRowAdapter personEpisodesAdapter = new ItemRowAdapter(requireContext(), personEpisodes, 100, false, new CardPresenter(), adapter); + ItemRowAdapter personEpisodesAdapter = new ItemRowAdapter(requireContext(), BrowsingUtils.createPersonItemsRequest(mBaseItem.getId(), BaseItemKind.EPISODE), 100, false, new CardPresenter(), adapter); addItemRow(adapter, personEpisodesAdapter, 2, getString(R.string.lbl_episodes)); break; case MUSIC_ARTIST: - - ItemQuery artistAlbums = new ItemQuery(); - artistAlbums.setFields(new ItemFields[]{ - ItemFields.PrimaryImageAspectRatio, - ItemFields.ChildCount - }); - artistAlbums.setUserId(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue().getId().toString()); - artistAlbums.setArtistIds(new String[]{mBaseItem.getId().toString()}); - artistAlbums.setRecursive(true); - artistAlbums.setIncludeItemTypes(new String[]{"MusicAlbum"}); - ItemRowAdapter artistAlbumsAdapter = new ItemRowAdapter(requireContext(), artistAlbums, 100, false, new CardPresenter(), adapter); + ItemRowAdapter artistAlbumsAdapter = new ItemRowAdapter(requireContext(), BrowsingUtils.createArtistItemsRequest(mBaseItem.getId(), BaseItemKind.MUSIC_ALBUM), 100, false, new CardPresenter(), adapter); addItemRow(adapter, artistAlbumsAdapter, 0, getString(R.string.lbl_albums)); break; @@ -682,12 +635,8 @@ protected void addAdditionalRows(MutableObjectAdapter adapter) { case EPISODE: if (mBaseItem.getSeasonId() != null && mBaseItem.getIndexNumber() != null) { - StdItemQuery nextEpisodes = new StdItemQuery(); - nextEpisodes.setParentId(mBaseItem.getSeasonId().toString()); - nextEpisodes.setIncludeItemTypes(new String[]{"Episode"}); - nextEpisodes.setStartIndex(mBaseItem.getIndexNumber()); // query index is zero-based but episode no is not - nextEpisodes.setLimit(20); - ItemRowAdapter nextAdapter = new ItemRowAdapter(requireContext(), nextEpisodes, 0, false, true, new CardPresenter(true, 120), adapter); + // query index is zero-based but episode no is not + ItemRowAdapter nextAdapter = new ItemRowAdapter(requireContext(), BrowsingUtils.createNextEpisodesRequest(mBaseItem.getSeasonId(), mBaseItem.getIndexNumber()), 0, false, true, new CardPresenter(true, 120), adapter); addItemRow(adapter, nextAdapter, 5, getString(R.string.lbl_next_episode)); } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemLauncher.java b/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemLauncher.java index 13f5a9c71f..29a50accca 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemLauncher.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemLauncher.java @@ -49,6 +49,7 @@ public void launchUserView(@Nullable final BaseItemDto baseItem) { public Destination.Fragment getUserViewDestination(@Nullable final BaseItemDto baseItem) { CollectionType collectionType = baseItem == null ? CollectionType.UNKNOWN : baseItem.getCollectionType(); + if (collectionType == null) collectionType = CollectionType.UNKNOWN; switch (collectionType) { case MOVIES: diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapter.java b/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapter.java index 7c7552165a..e4788ae4b5 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapter.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapter.java @@ -33,19 +33,14 @@ import org.jellyfin.androidtv.ui.presentation.TextItemPresenter; import org.jellyfin.androidtv.util.Utils; import org.jellyfin.androidtv.util.apiclient.EmptyLifecycleAwareResponse; -import org.jellyfin.androidtv.util.sdk.compat.JavaCompat; import org.jellyfin.androidtv.util.sdk.compat.ModelCompat; -import org.jellyfin.apiclient.interaction.ApiClient; -import org.jellyfin.apiclient.interaction.Response; -import org.jellyfin.apiclient.model.dto.BaseItemDto; -import org.jellyfin.apiclient.model.querying.ItemQuery; -import org.jellyfin.apiclient.model.querying.ItemsResult; -import org.jellyfin.apiclient.model.querying.NextUpQuery; +import org.jellyfin.sdk.model.api.BaseItemDto; import org.jellyfin.sdk.model.api.BaseItemPerson; import org.jellyfin.sdk.model.api.ItemSortBy; import org.jellyfin.sdk.model.api.SortOrder; import org.jellyfin.sdk.model.api.request.GetAlbumArtistsRequest; import org.jellyfin.sdk.model.api.request.GetArtistsRequest; +import org.jellyfin.sdk.model.api.request.GetItemsRequest; import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest; import org.jellyfin.sdk.model.api.request.GetLiveTvChannelsRequest; import org.jellyfin.sdk.model.api.request.GetNextUpRequest; @@ -67,7 +62,7 @@ import timber.log.Timber; public class ItemRowAdapter extends MutableObjectAdapter { - private ItemQuery mQuery; + private GetItemsRequest mQuery; private GetNextUpRequest mNextUpQuery; private GetSeasonsRequest mSeasonQuery; private GetUpcomingEpisodesRequest mUpcomingQuery; @@ -110,7 +105,6 @@ public class ItemRowAdapter extends MutableObjectAdapter { private boolean preferParentThumb = false; private boolean staticHeight = false; - private final Lazy apiClient = inject(ApiClient.class); private final Lazy api = inject(org.jellyfin.sdk.api.client.ApiClient.class); private final Lazy userViewsRepository = inject(UserViewsRepository.class); private Context context; @@ -147,41 +141,33 @@ public void setReRetrieveTriggers(ChangeTriggerType[] reRetrieveTriggers) { this.reRetrieveTriggers = reRetrieveTriggers; } - public ItemRowAdapter(Context context, ItemQuery query, int chunkSize, boolean preferParentThumb, Presenter presenter, MutableObjectAdapter parent) { + public ItemRowAdapter(Context context, GetItemsRequest query, int chunkSize, boolean preferParentThumb, Presenter presenter, MutableObjectAdapter parent) { this(context, query, chunkSize, preferParentThumb, false, presenter, parent); } - public ItemRowAdapter(Context context, ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, Presenter presenter, MutableObjectAdapter parent, QueryType queryType) { + public ItemRowAdapter(Context context, GetItemsRequest query, int chunkSize, boolean preferParentThumb, boolean staticHeight, Presenter presenter, MutableObjectAdapter parent, QueryType queryType) { super(presenter); this.context = context; mParent = parent; mQuery = query; - mQuery.setUserId(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue().getId().toString()); this.chunkSize = chunkSize; this.preferParentThumb = preferParentThumb; this.staticHeight = staticHeight; - if (chunkSize > 0) { - mQuery.setLimit(chunkSize); - } this.queryType = queryType; } - public ItemRowAdapter(Context context, ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, PresenterSelector presenter, MutableObjectAdapter parent, QueryType queryType) { + public ItemRowAdapter(Context context, GetItemsRequest query, int chunkSize, boolean preferParentThumb, boolean staticHeight, PresenterSelector presenter, MutableObjectAdapter parent, QueryType queryType) { super(presenter); this.context = context; mParent = parent; mQuery = query; - mQuery.setUserId(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue().getId().toString()); this.chunkSize = chunkSize; this.preferParentThumb = preferParentThumb; this.staticHeight = staticHeight; - if (chunkSize > 0) { - mQuery.setLimit(chunkSize); - } this.queryType = queryType; } - public ItemRowAdapter(Context context, ItemQuery query, int chunkSize, boolean preferParentThumb, boolean staticHeight, Presenter presenter, MutableObjectAdapter parent) { + public ItemRowAdapter(Context context, GetItemsRequest query, int chunkSize, boolean preferParentThumb, boolean staticHeight, Presenter presenter, MutableObjectAdapter parent) { this(context, query, chunkSize, preferParentThumb, staticHeight, presenter, parent, QueryType.Items); } @@ -262,7 +248,7 @@ public ItemRowAdapter(Context context, List items, Presenter presen super(presenter); this.context = context; mParent = parent; - mItems = JavaCompat.mapBaseItemCollection(items); + mItems = items; queryType = QueryType.StaticItems; } @@ -390,8 +376,7 @@ public void setSortBy(BrowseGridFragment.SortOption option) { mAlbumArtistsQuery = ItemRowAdapterHelperKt.setAlbumArtistsSorting(mAlbumArtistsQuery, option); break; default: - mQuery.setSortBy(new String[]{mSortBy.getSerialName(), ItemSortBy.SORT_NAME.getSerialName()}); - mQuery.setSortOrder(ModelCompat.asLegacy(option.order)); + mQuery = ItemRowAdapterHelperKt.setItemsSorting(mQuery, option); break; } if (!ItemSortBy.SORT_NAME.equals(option.value)) { @@ -422,7 +407,7 @@ public void setFilters(FilterOptions filters) { mAlbumArtistsQuery = ItemRowAdapterHelperKt.setAlbumArtistsFilter(mAlbumArtistsQuery, ModelCompat.asSdk(filters.getFilters())); break; default: - mQuery.setFilters(mFilters != null ? mFilters.getFilters() : null); + mQuery = ItemRowAdapterHelperKt.setItemsFilter(mQuery, ModelCompat.asSdk(filters.getFilters())); } removeRow(); } @@ -434,7 +419,7 @@ public void setFilters(FilterOptions filters) { case AlbumArtists: return mAlbumArtistsQuery != null ? mAlbumArtistsQuery.getNameStartsWith() : null; default: - return mQuery != null ? mQuery.getNameStartsWithOrGreater() : null; + return mQuery != null ? mQuery.getNameStartsWith() : null; } } @@ -456,9 +441,9 @@ public void setStartLetter(String value) { break; default: if (value != null && value.equals("#")) { - mQuery.setNameStartsWithOrGreater(null); + mQuery = ItemRowAdapterHelperKt.setItemsStartLetter(mQuery, null); } else { - mQuery.setNameStartsWithOrGreater(value); + mQuery = ItemRowAdapterHelperKt.setItemsStartLetter(mQuery, value); } break; } @@ -509,7 +494,6 @@ private void retrieveNext() { return; } - Integer savedIdx; switch (queryType) { case LiveTvChannel: if (mTvChannelQuery == null) { @@ -544,11 +528,7 @@ private void retrieveNext() { } notifyRetrieveStarted(); - savedIdx = mQuery.getStartIndex(); - //set the query to go get the next chunk - mQuery.setStartIndex(itemsLoaded); - retrieve(mQuery); - mQuery.setStartIndex(savedIdx); // is reused so reset + ItemRowAdapterHelperKt.retrieveItems(this, api.getValue(), mQuery, itemsLoaded, chunkSize); break; } } @@ -597,7 +577,7 @@ public void Retrieve() { itemsLoaded = 0; switch (queryType) { case Items: - retrieve(mQuery); + ItemRowAdapterHelperKt.retrieveItems(this, api.getValue(), mQuery, 0, chunkSize); break; case NextUp: ItemRowAdapterHelperKt.retrieveNextUpItems(this, api.getValue(), mNextUpQuery); @@ -662,7 +642,7 @@ public void Retrieve() { retrieveAudioPlaylists(mQuery); break; case Premieres: - retrievePremieres(mQuery); + ItemRowAdapterHelperKt.retrievePremieres(this, api.getValue(), mQuery); break; case SeriesTimer: boolean canManageRecordings = Utils.canManageRecordings(KoinJavaComponent.get(UserRepository.class).getCurrentUser().getValue()); @@ -733,112 +713,12 @@ private void addToParentIfResultsReceived() { } } - private void retrieve(final ItemQuery query) { - apiClient.getValue().GetItemsAsync(query, new Response() { - @Override - public void onResponse(ItemsResult response) { - if (response.getItems() != null && response.getItems().length > 0) { - setTotalItems(query.getEnableTotalRecordCount() ? response.getTotalRecordCount() : response.getItems().length); - - ItemRowAdapterHelperKt.setItems(ItemRowAdapter.this, response.getItems(), (item, i) -> new BaseItemDtoBaseRowItem(ModelCompat.asSdk(item), getPreferParentThumb(), isStaticHeight())); - } else if (getItemsLoaded() == 0) { - removeRow(); - } - - notifyRetrieveFinished(); - } - - @Override - public void onError(Exception exception) { - Timber.e(exception, "Error retrieving items"); - removeRow(); - notifyRetrieveFinished(exception); - } - }); - } - - private void retrieveAudioPlaylists(final ItemQuery query) { + private void retrieveAudioPlaylists(final GetItemsRequest query) { //Add specialized playlists first clear(); add(new GridButtonBaseRowItem(new GridButton(EnhancedBrowseFragment.FAVSONGS, context.getString(R.string.lbl_favorites), R.drawable.favorites))); itemsLoaded = 1; - retrieve(query); - } - - private void retrievePremieres(final ItemQuery query) { - final ItemRowAdapter adapter = this; - //First we need current Next Up to filter our list with - NextUpQuery nextUp = new NextUpQuery(); - nextUp.setUserId(query.getUserId()); - nextUp.setParentId(query.getParentId()); - nextUp.setLimit(50); - apiClient.getValue().GetNextUpEpisodesAsync(nextUp, new Response() { - @Override - public void onResponse(final ItemsResult nextUpResponse) { - apiClient.getValue().GetItemsAsync(query, new Response() { - @Override - public void onResponse(ItemsResult response) { - if (adapter.size() > 0) { - adapter.clear(); - } - if (response.getItems() != null && response.getItems().length > 0) { - Calendar compare = Calendar.getInstance(); - compare.add(Calendar.MONTH, -2); - BaseItemDto[] nextUpItems = nextUpResponse.getItems(); - for (BaseItemDto item : response.getItems()) { - if (item.getIndexNumber() != null && item.getIndexNumber() == 1 && (item.getDateCreated() == null || item.getDateCreated().after(compare.getTime())) - && (item.getUserData() == null || item.getUserData().getLikes() == null || item.getUserData().getLikes()) - ) { - // new unwatched episode 1 not disliked - check to be sure prev episode not already in next up - BaseItemDto nextUpItem = null; - for (BaseItemDto upItem : nextUpItems) { - if (upItem.getSeriesId().equals(item.getSeriesId())) { - nextUpItem = upItem; - break; - } - } - - if (nextUpItem == null || nextUpItem.getId().equals(item.getId())) { - //Now - let's be sure there isn't already a premiere for this series - BaseRowItem existing = null; - int existingPos = -1; - for (int n = 0; n < adapter.size(); n++) { - if (((BaseRowItem) adapter.get(n)).getBaseItem().getSeriesId().equals(item.getSeriesId())) { - existing = (BaseRowItem) adapter.get(n); - existingPos = n; - break; - } - } - if (existing == null) { - Timber.d("Adding new episode 1 to premieres %s", item.getSeriesName()); - adapter.add(new BaseItemDtoBaseRowItem(ModelCompat.asSdk(item), preferParentThumb, true)); - - } else if (existing.getBaseItem().getParentIndexNumber() > item.getParentIndexNumber()) { - //Replace the newer item with the earlier season - Timber.d("Replacing newer episode 1 with an older season for %s", item.getSeriesName()); - adapter.set(existingPos, new BaseItemDtoBaseRowItem(ModelCompat.asSdk(item), preferParentThumb, false)); - } // otherwise, just ignore this newer season premiere since we have the older one already - - } else { - Timber.i("Didn't add %s to premieres because different episode is in next up.", item.getSeriesName()); - } - } - } - setItemsLoaded(itemsLoaded + response.getItems().length); - } - - - if (adapter.size() == 0) { - removeRow(); - } - notifyRetrieveFinished(); - } - }); - - } - - }); - + ItemRowAdapterHelperKt.retrieveItems(this, api.getValue(), mQuery, 0, chunkSize); } protected void notifyRetrieveFinished() { diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapterHelper.kt b/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapterHelper.kt index f2476e67e0..501f800195 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapterHelper.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/itemhandling/ItemRowAdapterHelper.kt @@ -30,6 +30,7 @@ import org.jellyfin.sdk.model.api.ItemSortBy import org.jellyfin.sdk.model.api.SeriesTimerInfoDto import org.jellyfin.sdk.model.api.request.GetAlbumArtistsRequest import org.jellyfin.sdk.model.api.request.GetArtistsRequest +import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest import org.jellyfin.sdk.model.api.request.GetLiveTvChannelsRequest import org.jellyfin.sdk.model.api.request.GetNextUpRequest @@ -43,7 +44,7 @@ import timber.log.Timber import kotlin.math.min fun ItemRowAdapter.setItems( - items: Array, + items: Collection, transform: (T, Int) -> BaseRowItem?, ) { Timber.d("Creating items from $itemsLoaded existing and ${items.size} new, adapter size is ${size()}") @@ -76,7 +77,7 @@ fun ItemRowAdapter.retrieveResumeItems(api: ApiClient, query: GetResumeItemsRequ val response by api.itemsApi.getResumeItems(query) setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem( item, @@ -114,7 +115,7 @@ fun ItemRowAdapter.retrieveNextUpItems(api: ApiClient, query: GetNextUpRequest) val items = buildList { add(firstNextUp) episodesResponse.items?.let { addAll(it) } - }.toTypedArray() + } setItems( items = items, @@ -130,7 +131,7 @@ fun ItemRowAdapter.retrieveNextUpItems(api: ApiClient, query: GetNextUpRequest) if (items.isEmpty()) removeRow() } else { setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem( item, @@ -155,7 +156,7 @@ fun ItemRowAdapter.retrieveLatestMedia(api: ApiClient, query: GetLatestMediaRequ val response by api.userLibraryApi.getLatestMedia(query) setItems( - items = response.toTypedArray(), + items = response, transform = { item, _ -> BaseItemDtoBaseRowItem( item, @@ -181,7 +182,7 @@ fun ItemRowAdapter.retrieveSpecialFeatures(api: ApiClient, query: GetSpecialsReq val response by api.userLibraryApi.getSpecialFeatures(query.itemId) setItems( - items = response.toTypedArray(), + items = response, transform = { item, _ -> BaseItemDtoBaseRowItem(item, preferParentThumb, false) } @@ -201,7 +202,7 @@ fun ItemRowAdapter.retrieveAdditionalParts(api: ApiClient, query: GetAdditionalP val response by api.videosApi.getAdditionalPart(query.itemId) setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem(item) } ) @@ -223,7 +224,7 @@ fun ItemRowAdapter.retrieveUserViews(api: ApiClient, userViewsRepository: UserVi .map { it.copy(displayPreferencesId = it.id.toString()) } setItems( - items = filteredItems.toTypedArray(), + items = filteredItems, transform = { item, _ -> BaseItemDtoBaseRowItem(item) } ) @@ -241,7 +242,7 @@ fun ItemRowAdapter.retrieveSeasons(api: ApiClient, query: GetSeasonsRequest) { val response by api.tvShowsApi.getSeasons(query) setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem(item) } ) @@ -259,7 +260,7 @@ fun ItemRowAdapter.retrieveUpcomingEpisodes(api: ApiClient, query: GetUpcomingEp val response by api.tvShowsApi.getUpcomingEpisodes(query) setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem(item) } ) @@ -277,7 +278,7 @@ fun ItemRowAdapter.retrieveSimilarItems(api: ApiClient, query: GetSimilarItemsRe val response by api.libraryApi.getSimilarItems(query) setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem(item) } ) @@ -295,7 +296,7 @@ fun ItemRowAdapter.retrieveTrailers(api: ApiClient, query: GetTrailersRequest) { val response by api.userLibraryApi.getLocalTrailers(itemId = query.itemId) setItems( - items = response.toTypedArray(), + items = response, transform = { item, _ -> BaseItemDtoBaseRowItem( item, @@ -324,7 +325,7 @@ fun ItemRowAdapter.retrieveLiveTvRecommendedPrograms( val response by api.liveTvApi.getRecommendedPrograms(query) setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem( item, @@ -348,7 +349,7 @@ fun ItemRowAdapter.retrieveLiveTvRecordings(api: ApiClient, query: GetRecordings val response by api.liveTvApi.getRecordings(query) setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem( item, @@ -401,7 +402,7 @@ fun ItemRowAdapter.retrieveLiveTvSeriesTimers( } addAll(response.items.orEmpty()) - }.toTypedArray(), + }, transform = { item, _ -> when (item) { is GridButton -> GridButtonBaseRowItem(item) @@ -434,17 +435,16 @@ fun ItemRowAdapter.retrieveLiveTvChannels( ) ) - if (startIndex == 0) clear() totalItems = response.totalRecordCount setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem( item, false, isStaticHeight, ) - } + }, ) if (response.items.isNullOrEmpty()) removeRow() @@ -470,17 +470,16 @@ fun ItemRowAdapter.retrieveAlbumArtists( ) ) - if (startIndex == 0) clear() totalItems = response.totalRecordCount setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem( item, preferParentThumb, isStaticHeight, ) - } + }, ) if (response.items.isNullOrEmpty()) removeRow() @@ -506,17 +505,16 @@ fun ItemRowAdapter.retrieveArtists( ) ) - if (startIndex == 0) clear() totalItems = response.totalRecordCount setItems( - items = response.items.orEmpty().toTypedArray(), + items = response.items.orEmpty(), transform = { item, _ -> BaseItemDtoBaseRowItem( item, preferParentThumb, isStaticHeight, ) - } + }, ) if (response.items.isNullOrEmpty()) removeRow() @@ -527,6 +525,71 @@ fun ItemRowAdapter.retrieveArtists( } } +fun ItemRowAdapter.retrieveItems( + api: ApiClient, + query: GetItemsRequest, + startIndex: Int, + batchSize: Int +) { + ProcessLifecycleOwner.get().lifecycleScope.launch { + runCatching { + val response by api.itemsApi.getItems( + query.copy( + startIndex = startIndex, + limit = batchSize, + ) + ) + + totalItems = response.totalRecordCount + setItems( + items = response.items.orEmpty(), + transform = { item, _ -> + BaseItemDtoBaseRowItem( + item, + preferParentThumb, + isStaticHeight, + ) + }, + ) + + if (response.items.isNullOrEmpty()) removeRow() + }.fold( + onSuccess = { notifyRetrieveFinished() }, + onFailure = { error -> notifyRetrieveFinished(error as? Exception) } + ) + } +} + +fun ItemRowAdapter.retrievePremieres( + api: ApiClient, + query: GetItemsRequest, +) { + ProcessLifecycleOwner.get().lifecycleScope.launch { + runCatching { + val response by api.itemsApi.getItems(query) + val filteredItems = response.items + .orEmpty() + .filter { it.indexNumber == 1 } + + setItems( + items = filteredItems, + transform = { item, _ -> + BaseItemDtoBaseRowItem( + item, + preferParentThumb, + isStaticHeight, + ) + } + ) + + if (filteredItems.isEmpty()) removeRow() + }.fold( + onSuccess = { notifyRetrieveFinished() }, + onFailure = { error -> notifyRetrieveFinished(error as? Exception) } + ) + } +} + // Request modifiers fun setAlbumArtistsSorting( @@ -545,6 +608,14 @@ fun setArtistsSorting( sortOrder = setOf(sortOption.order) ) +fun setItemsSorting( + request: GetItemsRequest, + sortOption: SortOption, +) = request.copy( + sortBy = setOf(sortOption.value, ItemSortBy.SORT_NAME), + sortOrder = setOf(sortOption.order) +) + fun setAlbumArtistsFilter( request: GetAlbumArtistsRequest, filters: Collection?, @@ -559,6 +630,13 @@ fun setArtistsFilter( filters = filters, ) +fun setItemsFilter( + request: GetItemsRequest, + filters: Collection?, +) = request.copy( + filters = filters, +) + fun setAlbumArtistsStartLetter( request: GetAlbumArtistsRequest, startLetter: String?, @@ -573,6 +651,13 @@ fun setArtistsStartLetter( nameStartsWith = startLetter, ) +fun setItemsStartLetter( + request: GetItemsRequest, + startLetter: String?, +) = request.copy( + nameStartsWith = startLetter, +) + @JvmOverloads fun ItemRowAdapter.refreshItem( api: ApiClient, diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/livetv/TvManager.java b/app/src/main/java/org/jellyfin/androidtv/ui/livetv/TvManager.java index e71223e4b9..714172977b 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/livetv/TvManager.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/livetv/TvManager.java @@ -24,6 +24,7 @@ import org.jellyfin.androidtv.util.TimeUtils; import org.jellyfin.androidtv.util.Utils; import org.jellyfin.androidtv.util.apiclient.EmptyLifecycleAwareResponse; +import org.jellyfin.androidtv.util.sdk.compat.JavaCompat; import org.jellyfin.apiclient.interaction.ApiClient; import org.jellyfin.apiclient.interaction.Response; import org.jellyfin.apiclient.model.dto.BaseItemDto; @@ -335,7 +336,7 @@ public void onError(Exception exception) { } private static void addRow(Context context, List timers, Presenter presenter, MutableObjectAdapter rowAdapter) { - ItemRowAdapter scheduledAdapter = new ItemRowAdapter(context, timers, presenter, rowAdapter, true); + ItemRowAdapter scheduledAdapter = new ItemRowAdapter(context, JavaCompat.mapBaseItemCollection(timers), presenter, rowAdapter, true); scheduledAdapter.Retrieve(); ListRow scheduleRow = new ListRow(new HeaderItem(TimeUtils.getFriendlyDate(context, TimeUtils.convertToLocalDate(timers.get(0).getStartDate()), true)), scheduledAdapter); rowAdapter.add(scheduleRow); diff --git a/app/src/main/java/org/jellyfin/androidtv/util/KeyProcessor.java b/app/src/main/java/org/jellyfin/androidtv/util/KeyProcessor.java index a8fd07bb7d..dd16d29e56 100644 --- a/app/src/main/java/org/jellyfin/androidtv/util/KeyProcessor.java +++ b/app/src/main/java/org/jellyfin/androidtv/util/KeyProcessor.java @@ -10,7 +10,6 @@ import org.jellyfin.androidtv.R; import org.jellyfin.androidtv.constant.CustomMessage; -import org.jellyfin.androidtv.data.querying.StdItemQuery; import org.jellyfin.androidtv.data.repository.CustomMessageRepository; import org.jellyfin.androidtv.data.repository.ItemMutationRepository; import org.jellyfin.androidtv.ui.itemhandling.AudioQueueBaseRowItem; @@ -24,6 +23,7 @@ import org.jellyfin.apiclient.interaction.Response; import org.jellyfin.apiclient.model.entities.SortOrder; import org.jellyfin.apiclient.model.querying.ItemFilter; +import org.jellyfin.apiclient.model.querying.ItemQuery; import org.jellyfin.apiclient.model.querying.ItemsResult; import org.jellyfin.sdk.model.api.BaseItemDto; import org.jellyfin.sdk.model.api.BaseItemKind; @@ -313,7 +313,7 @@ public void onError(Exception exception) { }); return true; case MENU_PLAY_FIRST_UNWATCHED: - StdItemQuery query = new StdItemQuery(); + ItemQuery query = new ItemQuery(); query.setParentId(item.getId().toString()); query.setRecursive(true); query.setIsVirtualUnaired(false);