diff --git a/NewPipeExtractor b/NewPipeExtractor new file mode 160000 index 00000000000..3af73262cc6 --- /dev/null +++ b/NewPipeExtractor @@ -0,0 +1 @@ +Subproject commit 3af73262cc60cf555fd5f1d691f6c58e2db38ef5 diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java index 8a281bdb48c..18b42c25997 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java @@ -49,4 +49,11 @@ public interface SearchHistoryDAO extends HistoryDAO { @Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'" + " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit") Flowable> getSimilarEntries(String query, int limit); + + + @Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE) + List getAllEntries(); + + @androidx.room.Delete + void delete(SearchHistoryEntry entry); } diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index f2fdf9eba63..d62fd045a1d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -1,9 +1,7 @@ package org.schabi.newpipe.local.history; /* - * Copyright (C) Mauricio Colli 2018 - * HistoryRecordManager.java is part of NewPipe. - * + * Copyright 2018 Christian Schabesberger * NewPipe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -11,8 +9,8 @@ * * NewPipe is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . @@ -24,7 +22,6 @@ import android.content.SharedPreferences; import androidx.annotation.NonNull; -import androidx.collection.LongLongPair; import androidx.preference.PreferenceManager; import org.schabi.newpipe.NewPipeDatabase; @@ -85,46 +82,27 @@ public HistoryRecordManager(final Context context) { // Watch History /////////////////////////////////////////////////////// - /** - * Marks a stream item as watched such that it is hidden from the feed if watched videos are - * hidden. Adds a history entry and updates the stream progress to 100%. - * - * @see FeedViewModel#setSaveShowPlayedItems - * @param info the item to mark as watched - * @return a Maybe containing the ID of the item if successful - */ public Completable markAsWatched(final StreamInfoItem info) { if (!isStreamHistoryEnabled()) { return Completable.complete(); } - final var remoteInfo = getStreamInfo(info.getServiceId(), info.getUrl(), false) - .map(item -> - new LongLongPair(item.getDuration(), streamTable.upsert(new StreamEntity(item)))); - - return Single.just(info) - .filter(item -> item.getDuration() >= 0) - .map(item -> - new LongLongPair(item.getDuration(), streamTable.upsert(new StreamEntity(item))) - ) - .switchIfEmpty(remoteInfo) - .flatMapCompletable(pair -> Completable.fromRunnable(() -> { - final long duration = pair.getFirst(); - final long streamId = pair.getSecond(); + return getStreamInfo(info.getServiceId(), info.getUrl(), false) + .map(streamInfo -> { + long duration = streamInfo.getDuration(); + long streamId = streamTable.upsert(new StreamEntity(streamInfo)); - // Update the stream progress to the full duration of the video - final var entity = new StreamStateEntity(streamId, duration * 1000); + final StreamStateEntity entity = new StreamStateEntity(streamId, duration * 1000); streamStateTable.upsert(entity); - // Add a history entry - final var latestEntry = streamHistoryTable.getLatestEntry(streamId); + final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId); if (latestEntry == null) { - final var currentTime = OffsetDateTime.now(ZoneOffset.UTC); - // never actually viewed: add history entry but with 0 views - final var entry = new StreamHistoryEntity(streamId, currentTime, 0); - streamHistoryTable.insert(entry); + final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC); + streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime, 0)); } - })) + return true; + }) + .ignoreElement() .subscribeOn(Schedulers.io()); } @@ -144,41 +122,11 @@ public Maybe onViewed(final StreamInfo info) { latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1); return streamHistoryTable.insert(latestEntry); } else { - // just viewed for the first time: set 1 view return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime, 1)); } })).subscribeOn(Schedulers.io()); } - public Completable deleteStreamHistoryAndState(final long streamId) { - return Completable.fromAction(() -> { - streamStateTable.deleteState(streamId); - streamHistoryTable.deleteStreamHistory(streamId); - }).subscribeOn(Schedulers.io()); - } - - public Single deleteWholeStreamHistory() { - return Single.fromCallable(streamHistoryTable::deleteAll) - .subscribeOn(Schedulers.io()); - } - - public Single deleteCompleteStreamStateHistory() { - return Single.fromCallable(streamStateTable::deleteAll) - .subscribeOn(Schedulers.io()); - } - - public Flowable> getStreamHistorySortedById() { - return streamHistoryTable.getHistorySortedById().subscribeOn(Schedulers.io()); - } - - public Flowable> getStreamStatistics() { - return streamHistoryTable.getStatistics().subscribeOn(Schedulers.io()); - } - - private boolean isStreamHistoryEnabled() { - return sharedPreferences.getBoolean(streamHistoryKey, false); - } - /////////////////////////////////////////////////////// // Search History /////////////////////////////////////////////////////// @@ -197,94 +145,52 @@ public Maybe onSearched(final int serviceId, final String search) { latestEntry.setCreationDate(currentTime); return (long) searchHistoryTable.update(latestEntry); } else { - return searchHistoryTable.insert(newEntry); + final long insertedId = searchHistoryTable.insert(newEntry); + + boolean infiniteEnabled = sharedPreferences.getBoolean("infinite_search_history", false); + if (!infiniteEnabled) { + List allEntries = searchHistoryTable.getAllEntries(); + int maxSize = 25; + if (allEntries.size() > maxSize) { + for (int i = maxSize; i < allEntries.size(); i++) { + searchHistoryTable.delete(allEntries.get(i)); + } + } + } + return insertedId; } })).subscribeOn(Schedulers.io()); } - public Single deleteSearchHistory(final String search) { - return Single.fromCallable(() -> searchHistoryTable.deleteAllWhereQuery(search)) - .subscribeOn(Schedulers.io()); - } + public Flowable> getRelatedSearches( + final String query, + final int similarQueryLimit, + final int uniqueQueryLimit) { - public Single deleteCompleteSearchHistory() { - return Single.fromCallable(searchHistoryTable::deleteAll) - .subscribeOn(Schedulers.io()); - } + boolean infiniteEnabled = sharedPreferences.getBoolean("infinite_search_history", false); + int largeLimit = 100000; - public Flowable> getRelatedSearches(final String query, - final int similarQueryLimit, - final int uniqueQueryLimit) { - return !query.isEmpty() - ? searchHistoryTable.getSimilarEntries(query, similarQueryLimit) - : searchHistoryTable.getUniqueEntries(uniqueQueryLimit); - } + int sLimit = infiniteEnabled ? largeLimit : similarQueryLimit; + int uLimit = infiniteEnabled ? largeLimit : uniqueQueryLimit; - private boolean isSearchHistoryEnabled() { - return sharedPreferences.getBoolean(searchHistoryKey, false); + return query.isEmpty() + ? searchHistoryTable.getUniqueEntries(uLimit) + : searchHistoryTable.getSimilarEntries(query, sLimit); } /////////////////////////////////////////////////////// - // Stream State History + // Utility /////////////////////////////////////////////////////// - public Maybe loadStreamState(final PlayQueueItem queueItem) { - return queueItem.getStream() - .flatMapMaybe(this::loadStreamState) - .filter(state -> state.isValid(queueItem.getDuration())) - .subscribeOn(Schedulers.io()); - } - - public Maybe loadStreamState(final StreamInfo info) { - return Single.fromCallable(() -> streamTable.upsert(new StreamEntity(info))) - .flatMapMaybe(streamStateTable::getState) - .subscribeOn(Schedulers.io()); - } - - public Completable saveStreamState(@NonNull final StreamInfo info, final long progressMillis) { - return Completable.fromAction(() -> database.runInTransaction(() -> { - final long streamId = streamTable.upsert(new StreamEntity(info)); - final var state = new StreamStateEntity(streamId, progressMillis); - if (state.isValid(info.getDuration())) { - streamStateTable.upsert(state); - } - })).subscribeOn(Schedulers.io()); - } - - public Maybe loadStreamState(final InfoItem info) { - return streamTable.getStream(info.getServiceId(), info.getUrl()) - .flatMap(entity -> streamStateTable.getState(entity.getUid())) - .subscribeOn(Schedulers.io()); + private boolean isSearchHistoryEnabled() { + return sharedPreferences.getBoolean(searchHistoryKey, false); } - public Single> loadLocalStreamStateBatch( - final List items) { - return Single.fromCallable(() -> { - final List result = new ArrayList<>(items.size()); - for (final LocalItem item : items) { - final long streamId; - if (item instanceof StreamStatisticsEntry) { - streamId = ((StreamStatisticsEntry) item).getStreamId(); - } else if (item instanceof PlaylistStreamEntity) { - streamId = ((PlaylistStreamEntity) item).getStreamUid(); - } else if (item instanceof PlaylistStreamEntry) { - streamId = ((PlaylistStreamEntry) item).getStreamId(); - } else { - result.add(null); - continue; - } - result.add(streamStateTable.getState(streamId).blockingGet()); - } - return result; - }).subscribeOn(Schedulers.io()); + private boolean isStreamHistoryEnabled() { + return sharedPreferences.getBoolean(streamHistoryKey, false); } - /////////////////////////////////////////////////////// - // Utility - /////////////////////////////////////////////////////// - public Single removeOrphanedRecords() { return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io()); } - } diff --git a/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java b/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java index 9727c808300..655ea7092f2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java @@ -50,6 +50,10 @@ public static void initPlaylistControlClickListener( }); // long click listener + playlistControlBinding.playlistCtrlPlayAllButton.setOnLongClickListener(view -> { + NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.MAIN); + return true; + }); playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> { NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.POPUP); return true; diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index 82e9a2d5323..8136639f535 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -123,7 +123,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="6dp" - android:layout_marginRight="8dp" + android:layout_marginEnd="8dp" android:layout_weight="1" android:gravity="top" android:orientation="vertical" @@ -160,12 +160,15 @@ HTTP error 403 received from server while playing, likely caused by an IP ban or streaming URL deobfuscation issues %1$s refused to provide data, asking for a login to confirm the requester is not a bot.\n\nYour IP might have been temporarily banned by %1$s, you can wait some time or switch to a different IP (for example by turning on/off a VPN, or by switching from WiFi to mobile data). This content is not available for the currently selected content country.\n\nChange your selection from \"Settings > Content > Default content country\". + Enable Infinite Search History + Allow search history to load infinitely instead of being limited to 25 entries. + infinite_search_history diff --git a/app/src/main/res/xml/history_settings.xml b/app/src/main/res/xml/history_settings.xml index 46111635c20..f0e87cb5b9b 100644 --- a/app/src/main/res/xml/history_settings.xml +++ b/app/src/main/res/xml/history_settings.xml @@ -38,6 +38,16 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + + +