mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-05-17 21:55:55 +08:00
fix: make feed notifications robust against subscription changes
This commit is contained in:
@ -140,11 +140,13 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
// Internally saved data / not a preference
|
// Internally saved data / not a preference
|
||||||
const val ERROR_LOG = "error_log"
|
const val ERROR_LOG = "error_log"
|
||||||
const val LAST_STREAM_VIDEO_ID = "last_stream_video_id"
|
|
||||||
const val LAST_WATCHED_FEED_TIME = "last_watched_feed_time"
|
|
||||||
const val AUTH_PREF_FILE = "auth"
|
const val AUTH_PREF_FILE = "auth"
|
||||||
const val IMAGE_PROXY_URL = "image_proxy_url"
|
const val IMAGE_PROXY_URL = "image_proxy_url"
|
||||||
const val SELECTED_CHANNEL_GROUP = "selected_channel_group"
|
const val SELECTED_CHANNEL_GROUP = "selected_channel_group"
|
||||||
const val SELECTED_DOWNLOAD_SORT_TYPE = "selected_download_sort_type"
|
const val SELECTED_DOWNLOAD_SORT_TYPE = "selected_download_sort_type"
|
||||||
const val LAST_SHOWN_INFO_MESSAGE_VERSION_CODE = "last_shown_info_message_version"
|
const val LAST_SHOWN_INFO_MESSAGE_VERSION_CODE = "last_shown_info_message_version"
|
||||||
|
|
||||||
|
// use the helper methods at PreferenceHelper to access these
|
||||||
|
const val LAST_USER_SEEN_FEED_TIME = "last_watched_feed_time"
|
||||||
|
const val LAST_REFRESHED_FEED_TIME = "last_refreshed_feed_time"
|
||||||
}
|
}
|
||||||
|
@ -96,24 +96,24 @@ object PreferenceHelper {
|
|||||||
authSettings.edit { putString(PreferenceKeys.USERNAME, newValue) }
|
authSettings.edit { putString(PreferenceKeys.USERNAME, newValue) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setLastSeenVideoId(videoId: String) {
|
fun updateLastFeedWatchedTime(time: Long, seenByUser: Boolean) {
|
||||||
putString(PreferenceKeys.LAST_STREAM_VIDEO_ID, videoId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLastSeenVideoId(): String {
|
|
||||||
return getString(PreferenceKeys.LAST_STREAM_VIDEO_ID, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateLastFeedWatchedTime(time: Long) {
|
|
||||||
// only update the time if the time is newer
|
// only update the time if the time is newer
|
||||||
// this avoids cases, where the user last saw an older video, which had already been seen,
|
// this avoids cases, where the user last saw an older video, which had already been seen,
|
||||||
// causing all following video to be incorrectly marked as unseen again
|
// causing all following video to be incorrectly marked as unseen again
|
||||||
if (getLastCheckedFeedTime() < time)
|
if (getLastCheckedFeedTime(false) < time)
|
||||||
putLong(PreferenceKeys.LAST_WATCHED_FEED_TIME, time)
|
putLong(PreferenceKeys.LAST_REFRESHED_FEED_TIME, time)
|
||||||
|
|
||||||
|
// this value holds the last time the user opened the subscriptions feed
|
||||||
|
// whereas [LAST_REFRESHED_FEED_TIME] considers the last time the feed was loaded,
|
||||||
|
// which could also be possible in the background (e.g. via notifications)
|
||||||
|
if (seenByUser && getLastCheckedFeedTime(true) < time)
|
||||||
|
putLong(PreferenceKeys.LAST_USER_SEEN_FEED_TIME, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLastCheckedFeedTime(): Long {
|
fun getLastCheckedFeedTime(seenByUser: Boolean): Long {
|
||||||
return getLong(PreferenceKeys.LAST_WATCHED_FEED_TIME, 0)
|
val key =
|
||||||
|
if (seenByUser) PreferenceKeys.LAST_USER_SEEN_FEED_TIME else PreferenceKeys.LAST_REFRESHED_FEED_TIME
|
||||||
|
return getLong(key, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveErrorLog(log: String) {
|
fun saveErrorLog(log: String) {
|
||||||
|
@ -37,7 +37,6 @@ import com.github.libretube.constants.IntentData
|
|||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.databinding.ActivityMainBinding
|
import com.github.libretube.databinding.ActivityMainBinding
|
||||||
import com.github.libretube.enums.ImportFormat
|
import com.github.libretube.enums.ImportFormat
|
||||||
import com.github.libretube.extensions.toID
|
|
||||||
import com.github.libretube.helpers.ImportHelper
|
import com.github.libretube.helpers.ImportHelper
|
||||||
import com.github.libretube.helpers.IntentHelper
|
import com.github.libretube.helpers.IntentHelper
|
||||||
import com.github.libretube.helpers.NavBarHelper
|
import com.github.libretube.helpers.NavBarHelper
|
||||||
@ -240,9 +239,12 @@ class MainActivity : BaseActivity() {
|
|||||||
subscriptionsViewModel.fetchSubscriptions(this)
|
subscriptionsViewModel.fetchSubscriptions(this)
|
||||||
|
|
||||||
subscriptionsViewModel.videoFeed.observe(this) { feed ->
|
subscriptionsViewModel.videoFeed.observe(this) { feed ->
|
||||||
|
val lastCheckedFeedTime = PreferenceHelper.getLastCheckedFeedTime(seenByUser = true)
|
||||||
val lastSeenVideoIndex = feed.orEmpty()
|
val lastSeenVideoIndex = feed.orEmpty()
|
||||||
.indexOfFirst { PreferenceHelper.getLastSeenVideoId() == it.url?.toID() }
|
.filter { !it.isUpcoming }
|
||||||
|
.indexOfFirst { it.uploaded <= lastCheckedFeedTime }
|
||||||
if (lastSeenVideoIndex < 1) return@observe
|
if (lastSeenVideoIndex < 1) return@observe
|
||||||
|
|
||||||
binding.bottomNav.getOrCreateBadge(R.id.subscriptionsFragment).apply {
|
binding.bottomNav.getOrCreateBadge(R.id.subscriptionsFragment).apply {
|
||||||
number = lastSeenVideoIndex
|
number = lastSeenVideoIndex
|
||||||
backgroundColor = ThemeHelper.getThemeColor(
|
backgroundColor = ThemeHelper.getThemeColor(
|
||||||
|
@ -140,13 +140,17 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
// any other feed updates are caused by manual refreshing and thus should reset the scroll
|
// any other feed updates are caused by manual refreshing and thus should reset the scroll
|
||||||
// position to zero
|
// position to zero
|
||||||
var alreadyShowedFeedOnce = false
|
var alreadyShowedFeedOnce = false
|
||||||
viewModel.videoFeed.observe(viewLifecycleOwner) {
|
viewModel.videoFeed.observe(viewLifecycleOwner) { feed ->
|
||||||
if (!viewModel.isCurrentTabSubChannels && it != null) {
|
if (!viewModel.isCurrentTabSubChannels && feed != null) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
showFeed(!alreadyShowedFeedOnce)
|
showFeed(!alreadyShowedFeedOnce)
|
||||||
}
|
}
|
||||||
alreadyShowedFeedOnce = true
|
alreadyShowedFeedOnce = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feed?.firstOrNull { !it.isUpcoming }?.uploaded?.let {
|
||||||
|
PreferenceHelper.updateLastFeedWatchedTime(it, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore the scroll position, same conditions as above
|
// restore the scroll position, same conditions as above
|
||||||
@ -384,7 +388,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
|
|
||||||
// add an "all caught up item"
|
// add an "all caught up item"
|
||||||
if (selectedSortOrder == 0) {
|
if (selectedSortOrder == 0) {
|
||||||
val lastCheckedFeedTime = PreferenceHelper.getLastCheckedFeedTime()
|
val lastCheckedFeedTime = PreferenceHelper.getLastCheckedFeedTime(seenByUser = true)
|
||||||
val caughtUpIndex =
|
val caughtUpIndex =
|
||||||
feed.indexOfFirst { it.uploaded <= lastCheckedFeedTime && !it.isUpcoming }
|
feed.indexOfFirst { it.uploaded <= lastCheckedFeedTime && !it.isUpcoming }
|
||||||
if (caughtUpIndex > 0 && !feed[caughtUpIndex - 1].isUpcoming) {
|
if (caughtUpIndex > 0 && !feed[caughtUpIndex - 1].isUpcoming) {
|
||||||
@ -404,10 +408,6 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
|
|
||||||
binding.toggleSubs.text = getString(R.string.subscriptions)
|
binding.toggleSubs.text = getString(R.string.subscriptions)
|
||||||
|
|
||||||
feed.firstOrNull { !it.isUpcoming }?.uploaded?.let {
|
|
||||||
PreferenceHelper.updateLastFeedWatchedTime(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.subRefresh.isRefreshing = false
|
binding.subRefresh.isRefreshing = false
|
||||||
|
|
||||||
feedAdapter.submitList(sortedFeed) {
|
feedAdapter.submitList(sortedFeed) {
|
||||||
|
@ -11,7 +11,6 @@ import com.github.libretube.api.SubscriptionHelper
|
|||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.api.obj.Subscription
|
import com.github.libretube.api.obj.Subscription
|
||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
import com.github.libretube.extensions.toID
|
|
||||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||||
import com.github.libretube.helpers.PreferenceHelper
|
import com.github.libretube.helpers.PreferenceHelper
|
||||||
import com.github.libretube.repo.FeedProgress
|
import com.github.libretube.repo.FeedProgress
|
||||||
@ -41,9 +40,8 @@ class SubscriptionsViewModel : ViewModel() {
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
this@SubscriptionsViewModel.videoFeed.postValue(videoFeed)
|
this@SubscriptionsViewModel.videoFeed.postValue(videoFeed)
|
||||||
if (videoFeed.isNotEmpty()) {
|
videoFeed.firstOrNull { !it.isUpcoming }?.uploaded?.let {
|
||||||
// save the last recent video to the prefs for the notification worker
|
PreferenceHelper.updateLastFeedWatchedTime(it, false)
|
||||||
PreferenceHelper.setLastSeenVideoId(videoFeed[0].url!!.toID())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,18 +83,15 @@ class NotificationWorker(appContext: Context, parameters: WorkerParameters) :
|
|||||||
val videoFeed = try {
|
val videoFeed = try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
SubscriptionHelper.getFeed(forceRefresh = true)
|
SubscriptionHelper.getFeed(forceRefresh = true)
|
||||||
}
|
}.filter { !it.isUpcoming }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastUserSeenVideoId = PreferenceHelper.getLastSeenVideoId()
|
val lastFeedCheckMillis = PreferenceHelper.getLastCheckedFeedTime(seenByUser = false)
|
||||||
val mostRecentStreamId = videoFeed.firstOrNull()?.url?.toID() ?: return true
|
|
||||||
// save the latest streams that got notified about
|
|
||||||
PreferenceHelper.setLastSeenVideoId(mostRecentStreamId)
|
|
||||||
|
|
||||||
// first time notifications are enabled or no new video available
|
// first time notifications are enabled or no new video available
|
||||||
if (lastUserSeenVideoId.isEmpty() || lastUserSeenVideoId == mostRecentStreamId) return true
|
if (lastFeedCheckMillis == 0L || videoFeed.none { it.uploaded > lastFeedCheckMillis }) return true
|
||||||
|
|
||||||
val channelsToIgnore = PreferenceHelper.getIgnorableNotificationChannels()
|
val channelsToIgnore = PreferenceHelper.getIgnorableNotificationChannels()
|
||||||
val enableShortsNotification =
|
val enableShortsNotification =
|
||||||
@ -102,7 +99,7 @@ class NotificationWorker(appContext: Context, parameters: WorkerParameters) :
|
|||||||
|
|
||||||
val channelGroups = videoFeed.asSequence()
|
val channelGroups = videoFeed.asSequence()
|
||||||
// filter the new videos until the last seen video in the feed
|
// filter the new videos until the last seen video in the feed
|
||||||
.takeWhile { it.url!!.toID() != lastUserSeenVideoId }
|
.filter { it.uploaded > lastFeedCheckMillis }
|
||||||
// don't show notifications for shorts videos if not enabled
|
// don't show notifications for shorts videos if not enabled
|
||||||
.filter { enableShortsNotification || !it.isShort }
|
.filter { enableShortsNotification || !it.isShort }
|
||||||
// hide for notifications unsubscribed channels
|
// hide for notifications unsubscribed channels
|
||||||
@ -110,6 +107,9 @@ class NotificationWorker(appContext: Context, parameters: WorkerParameters) :
|
|||||||
// group the new streams by the uploader
|
// group the new streams by the uploader
|
||||||
.groupBy { it.uploaderUrl!!.toID() }
|
.groupBy { it.uploaderUrl!!.toID() }
|
||||||
|
|
||||||
|
// update the last feed check time in order to not show the same notification again
|
||||||
|
PreferenceHelper.updateLastFeedWatchedTime(videoFeed.first().uploaded, seenByUser = false)
|
||||||
|
|
||||||
// return if the previous video didn't get found or all the channels have notifications disabled
|
// return if the previous video didn't get found or all the channels have notifications disabled
|
||||||
if (channelGroups.isEmpty()) return true
|
if (channelGroups.isEmpty()) return true
|
||||||
|
|
||||||
@ -226,6 +226,6 @@ class NotificationWorker(appContext: Context, parameters: WorkerParameters) :
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val INTENT_FLAGS = Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
private const val INTENT_FLAGS = Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
||||||
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user