diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt index 968d0c88f..6947f66af 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt @@ -32,6 +32,7 @@ abstract class FeedDAO { * @return the feed streams filtered according to the conditions provided in the parameters * @see StreamStateEntity.isFinished() * @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS + * @see StreamStateEntity.PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS */ @Query( """ @@ -66,6 +67,15 @@ abstract class FeedDAO { OR s.stream_type = 'LIVE_STREAM' OR s.stream_type = 'AUDIO_LIVE_STREAM' ) + AND ( + :includePartiallyPlayed + OR sh.stream_id IS NULL + OR sst.stream_id IS NULL + OR (sst.progress_time <= ${StreamStateEntity.PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS} + AND sst.progress_time <= s.duration * 1000 / 4) + OR (sst.progress_time >= s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} + AND sst.progress_time >= s.duration * 1000 * 3 / 4) + ) AND ( :uploadDateBefore IS NULL OR s.upload_date IS NULL @@ -79,6 +89,7 @@ abstract class FeedDAO { abstract fun getStreams( groupId: Long, includePlayed: Boolean, + includePartiallyPlayed: Boolean, uploadDateBefore: OffsetDateTime? ): Maybe> diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java index 75766850f..627acea45 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java @@ -30,7 +30,7 @@ public class StreamStateEntity { /** * Playback state will not be saved, if playback time is less than this threshold (5000ms = 5s). */ - private static final long PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS = 5000; + public static final long PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS = 5000; /** * Stream will be considered finished if the playback time left exceeds this threshold diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index 07edb0499..ed65d4048 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -43,11 +43,13 @@ class FeedDatabaseManager(context: Context) { fun getStreams( groupId: Long, includePlayedStreams: Boolean, + includePartiallyPlayedStreams: Boolean, includeFutureStreams: Boolean ): Maybe> { return feedTable.getStreams( groupId, includePlayedStreams, + includePartiallyPlayedStreams, if (includeFutureStreams) null else OffsetDateTime.now() ) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 2bb2f9986..9fe90969f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -37,11 +37,9 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import androidx.appcompat.app.AlertDialog -import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.edit import androidx.core.math.MathUtils import androidx.core.os.bundleOf -import androidx.core.view.MenuItemCompat import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager @@ -100,8 +98,6 @@ class FeedFragment : BaseStateFragment() { private var oldestSubscriptionUpdate: OffsetDateTime? = null private lateinit var groupAdapter: GroupieAdapter - @State @JvmField var showPlayedItems: Boolean = true - @State @JvmField var showFutureItems: Boolean = true private var onSettingsChangeListener: SharedPreferences.OnSharedPreferenceChangeListener? = null private var updateListViewModeOnResume = false @@ -140,8 +136,6 @@ class FeedFragment : BaseStateFragment() { val factory = FeedViewModel.getFactory(requireContext(), groupId) viewModel = ViewModelProvider(this, factory)[FeedViewModel::class.java] - showPlayedItems = viewModel.getShowPlayedItemsFromPreferences() - showFutureItems = viewModel.getShowFutureItemsFromPreferences() viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(::handleResult) } groupAdapter = GroupieAdapter().apply { @@ -216,8 +210,6 @@ class FeedFragment : BaseStateFragment() { activity.supportActionBar?.subtitle = groupName inflater.inflate(R.menu.menu_feed_fragment, menu) - updateTogglePlayedItemsButton(menu.findItem(R.id.menu_item_feed_toggle_played_items)) - updateToggleFutureItemsButton(menu.findItem(R.id.menu_item_feed_toggle_future_items)) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -243,20 +235,43 @@ class FeedFragment : BaseStateFragment() { .show() return true } else if (item.itemId == R.id.menu_item_feed_toggle_played_items) { - showPlayedItems = !item.isChecked - updateTogglePlayedItemsButton(item) - viewModel.togglePlayedItems(showPlayedItems) - viewModel.saveShowPlayedItemsToPreferences(showPlayedItems) - } else if (item.itemId == R.id.menu_item_feed_toggle_future_items) { - showFutureItems = !item.isChecked - updateToggleFutureItemsButton(item) - viewModel.toggleFutureItems(showFutureItems) - viewModel.saveShowFutureItemsToPreferences(showFutureItems) + showStreamVisibilityDialog() } return super.onOptionsItemSelected(item) } + private fun showStreamVisibilityDialog() { + val dialogItems = arrayOf( + getString(R.string.feed_show_watched), + getString(R.string.feed_show_partially_watched), + getString(R.string.feed_show_upcoming) + ) + + val checkedDialogItems = booleanArrayOf( + viewModel.getShowPlayedItemsFromPreferences(), + viewModel.getShowPartiallyPlayedItemsFromPreferences(), + viewModel.getShowFutureItemsFromPreferences() + ) + + val builder = AlertDialog.Builder(context!!) + builder.setTitle(R.string.feed_hide_streams_title) + builder.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked -> + checkedDialogItems[which] = isChecked + } + + builder.setPositiveButton(R.string.ok) { _, _ -> + viewModel.setSaveShowPlayedItems(checkedDialogItems[0]) + + viewModel.setSaveShowPartiallyPlayedItems(checkedDialogItems[1]) + + viewModel.setSaveShowFutureItems(checkedDialogItems[2]) + } + builder.setNegativeButton(R.string.cancel, null) + + builder.create().show() + } + override fun onDestroyOptionsMenu() { super.onDestroyOptionsMenu() activity?.supportActionBar?.subtitle = null @@ -283,40 +298,6 @@ class FeedFragment : BaseStateFragment() { super.onDestroyView() } - private fun updateTogglePlayedItemsButton(menuItem: MenuItem) { - menuItem.isChecked = showPlayedItems - menuItem.icon = AppCompatResources.getDrawable( - requireContext(), - if (showPlayedItems) R.drawable.ic_visibility_on else R.drawable.ic_visibility_off - ) - MenuItemCompat.setTooltipText( - menuItem, - getString( - if (showPlayedItems) - R.string.feed_toggle_hide_played_items - else - R.string.feed_toggle_show_played_items - ) - ) - } - - private fun updateToggleFutureItemsButton(menuItem: MenuItem) { - menuItem.isChecked = showFutureItems - menuItem.icon = AppCompatResources.getDrawable( - requireContext(), - if (showFutureItems) R.drawable.ic_history_future else R.drawable.ic_history - ) - MenuItemCompat.setTooltipText( - menuItem, - getString( - if (showFutureItems) - R.string.feed_toggle_hide_future_items - else - R.string.feed_toggle_show_future_items - ) - ) - } - // ////////////////////////////////////////////////////////////////////////// // Handling // ////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 76d5e9d63..58f9e9edc 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -11,7 +11,7 @@ import androidx.lifecycle.viewmodel.viewModelFactory import androidx.preference.PreferenceManager import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.functions.Function5 +import io.reactivex.rxjava3.functions.Function6 import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.App @@ -31,18 +31,24 @@ import java.util.concurrent.TimeUnit class FeedViewModel( private val application: Application, groupId: Long = FeedGroupEntity.GROUP_ALL_ID, - initialShowPlayedItems: Boolean = true, - initialShowFutureItems: Boolean = true + initialShowPlayedItems: Boolean, + initialShowPartiallyPlayedItems: Boolean, + initialShowFutureItems: Boolean ) : ViewModel() { private val feedDatabaseManager = FeedDatabaseManager(application) - private val toggleShowPlayedItems = BehaviorProcessor.create() - private val toggleShowPlayedItemsFlowable = toggleShowPlayedItems + private val showPlayedItems = BehaviorProcessor.create() + private val showPlayedItemsFlowable = showPlayedItems .startWithItem(initialShowPlayedItems) .distinctUntilChanged() - private val toggleShowFutureItems = BehaviorProcessor.create() - private val toggleShowFutureItemsFlowable = toggleShowFutureItems + private val showPartiallyPlayedItems = BehaviorProcessor.create() + private val showPartiallyPlayedItemsFlowable = showPartiallyPlayedItems + .startWithItem(initialShowPartiallyPlayedItems) + .distinctUntilChanged() + + private val showFutureItems = BehaviorProcessor.create() + private val showFutureItemsFlowable = showFutureItems .startWithItem(initialShowFutureItems) .distinctUntilChanged() @@ -52,23 +58,24 @@ class FeedViewModel( private var combineDisposable = Flowable .combineLatest( FeedEventManager.events(), - toggleShowPlayedItemsFlowable, - toggleShowFutureItemsFlowable, + showPlayedItemsFlowable, + showPartiallyPlayedItemsFlowable, + showFutureItemsFlowable, feedDatabaseManager.notLoadedCount(groupId), feedDatabaseManager.oldestSubscriptionUpdate(groupId), - Function5 { t1: FeedEventManager.Event, t2: Boolean, t3: Boolean, - t4: Long, t5: List -> - return@Function5 CombineResultEventHolder(t1, t2, t3, t4, t5.firstOrNull()) + Function6 { t1: FeedEventManager.Event, t2: Boolean, t3: Boolean, t4: Boolean, + t5: Long, t6: List -> + return@Function6 CombineResultEventHolder(t1, t2, t3, t4, t5, t6.firstOrNull()) } ) .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) - .map { (event, showPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) -> + .map { (event, showPlayedItems, showPartiallyPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) -> val streamItems = if (event is SuccessResultEvent || event is IdleEvent) feedDatabaseManager - .getStreams(groupId, showPlayedItems, showFutureItems) + .getStreams(groupId, showPlayedItems, showPartiallyPlayedItems, showFutureItems) .blockingGet(arrayListOf()) else arrayListOf() @@ -100,8 +107,9 @@ class FeedViewModel( val t1: FeedEventManager.Event, val t2: Boolean, val t3: Boolean, - val t4: Long, - val t5: OffsetDateTime? + val t4: Boolean, + val t5: Long, + val t6: OffsetDateTime? ) private data class CombineResultDataHolder( @@ -111,37 +119,49 @@ class FeedViewModel( val t4: OffsetDateTime? ) - fun togglePlayedItems(showPlayedItems: Boolean) { - toggleShowPlayedItems.onNext(showPlayedItems) - } - - fun saveShowPlayedItemsToPreferences(showPlayedItems: Boolean) = + fun setSaveShowPlayedItems(showPlayedItems: Boolean) { + this.showPlayedItems.onNext(showPlayedItems) PreferenceManager.getDefaultSharedPreferences(application).edit { - this.putBoolean(application.getString(R.string.feed_show_played_items_key), showPlayedItems) + this.putBoolean(application.getString(R.string.feed_show_watched_items_key), showPlayedItems) this.apply() } + } fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(application) - fun toggleFutureItems(showFutureItems: Boolean) { - toggleShowFutureItems.onNext(showFutureItems) + fun setSaveShowPartiallyPlayedItems(showPartiallyPlayedItems: Boolean) { + this.showPartiallyPlayedItems.onNext(showPartiallyPlayedItems) + PreferenceManager.getDefaultSharedPreferences(application).edit { + this.putBoolean(application.getString(R.string.feed_show_partially_watched_items_key), showPartiallyPlayedItems) + this.apply() + } } - fun saveShowFutureItemsToPreferences(showFutureItems: Boolean) = + fun getShowPartiallyPlayedItemsFromPreferences() = getShowPartiallyPlayedItemsFromPreferences(application) + + fun setSaveShowFutureItems(showFutureItems: Boolean) { + this.showFutureItems.onNext(showFutureItems) PreferenceManager.getDefaultSharedPreferences(application).edit { this.putBoolean(application.getString(R.string.feed_show_future_items_key), showFutureItems) this.apply() } + } fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(application) companion object { private fun getShowPlayedItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.feed_show_played_items_key), true) + .getBoolean(context.getString(R.string.feed_show_watched_items_key), true) + + private fun getShowPartiallyPlayedItemsFromPreferences(context: Context) = + PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.feed_show_partially_watched_items_key), true) + private fun getShowFutureItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.feed_show_future_items_key), true) + fun getFactory(context: Context, groupId: Long) = viewModelFactory { initializer { FeedViewModel( @@ -149,6 +169,7 @@ class FeedViewModel( groupId, // Read initial value from preferences getShowPlayedItemsFromPreferences(context.applicationContext), + getShowPartiallyPlayedItemsFromPreferences(context.applicationContext), getShowFutureItemsFromPreferences(context.applicationContext) ) } 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 b8d2eae2d..ed3cf548f 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 @@ -87,7 +87,7 @@ public class HistoryRecordManager { * 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#togglePlayedItems + * @see FeedViewModel#setSaveShowPlayedItems * @param info the item to mark as watched * @return a Maybe containing the ID of the item if successful */ diff --git a/app/src/main/res/menu/menu_feed_fragment.xml b/app/src/main/res/menu/menu_feed_fragment.xml index 9e5cc862e..2365c30e4 100644 --- a/app/src/main/res/menu/menu_feed_fragment.xml +++ b/app/src/main/res/menu/menu_feed_fragment.xml @@ -5,19 +5,8 @@ - - feed_update_threshold_key 300 - feed_show_played_items + feed_show_played_items + feed_show_partially_watched_items feed_show_future_items show_thumbnail_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06f8cf4f1..c842dc51b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -694,8 +694,8 @@ \nYouTube is an example of a service that offers this fast method with its RSS feed. \n \nSo the choice boils down to what you prefer: speed or precise information. - Show watched items - Hide watched items + Show the following streams + Show/Hide streams This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. Channel\'s avatar thumbnail Created by %s @@ -763,5 +763,8 @@ Unknown quality Show future items Hide future items + Fully watched + Partially watched + Upcoming Sort \ No newline at end of file