Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ data class FeedGroupEntity(
const val SORT_ORDER = "sort_order"

const val GROUP_ALL_ID = -1L
const val GROUP_UNGROUPED_ID = -2L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,30 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
filter: String
): Flowable<List<SubscriptionEntity>>

@RewriteQueriesToDropUnusedColumns
@Query(
"""
SELECT * FROM subscriptions s
INNER JOIN feed_group_subscription_join fgs
ON s.uid = fgs.subscription_id
WHERE fgs.group_id = :groupId
ORDER BY name COLLATE NOCASE ASC
"""
)
abstract fun getSubscriptionsForGroup(groupId: Long): Flowable<List<SubscriptionEntity>>

@Query(
"""
SELECT * FROM subscriptions s
WHERE NOT EXISTS (
SELECT 1 FROM feed_group_subscription_join fgs
WHERE fgs.subscription_id = s.uid
)
ORDER BY name COLLATE NOCASE ASC
"""
)
abstract fun getSubscriptionsNotInAnyGroup(): Flowable<List<SubscriptionEntity>>

@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.xwray.groupie.viewbinding.GroupieViewHolder
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_UNGROUPED_ID
import org.schabi.newpipe.databinding.DialogTitleBinding
import org.schabi.newpipe.databinding.FeedItemCarouselBinding
import org.schabi.newpipe.databinding.FragmentSubscriptionBinding
Expand Down Expand Up @@ -68,6 +69,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
private lateinit var feedGroupsSortMenuItem: GroupsHeader
private val subscriptionsSection = Section()

private var currentGroups: List<Group> = emptyList()
private var currentListViewMode: Boolean = true // updated from ViewModel after init

@State
@JvmField
var itemsListState: Parcelable? = null
Expand Down Expand Up @@ -204,12 +208,16 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
binding.emptyStateView.setEmptyStateComposable()

viewModel = ViewModelProvider(this)[SubscriptionViewModel::class.java]
currentListViewMode = viewModel.getListViewMode()
viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) }
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) {
it?.let { (groups, listViewMode) ->
handleFeedGroups(groups, listViewMode)
}
}
viewModel.selectedGroupLiveData.observe(viewLifecycleOwner) {
rebuildCarousel()
}

setupInitialLayout()
}
Expand All @@ -219,34 +227,45 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
carouselAdapter = GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>()

carouselAdapter.setOnItemClickListener { item, _ ->
when (item) {
is FeedGroupCardItem ->
NavigationHelper.openFeedFragment(fm, item.groupId, item.name)
val groupId = when (item) {
is FeedGroupCardItem -> item.groupId

is FeedGroupCardGridItem ->
NavigationHelper.openFeedFragment(fm, item.groupId, item.name)
is FeedGroupCardGridItem -> item.groupId

is FeedGroupAddNewItem ->
is FeedGroupAddNewItem -> {
FeedGroupDialog.newInstance().show(fm, null)
return@setOnItemClickListener
}

is FeedGroupAddNewGridItem ->
is FeedGroupAddNewGridItem -> {
FeedGroupDialog.newInstance().show(fm, null)
return@setOnItemClickListener
}

else -> return@setOnItemClickListener
}
val name = when (item) {
is FeedGroupCardItem -> item.name
is FeedGroupCardGridItem -> item.name
else -> ""
}
if (groupId == viewModel.getSelectedGroupId() && groupId != GROUP_UNGROUPED_ID) {
NavigationHelper.openFeedFragment(fm, groupId, name)
} else {
viewModel.selectGroup(groupId)
}
}
carouselAdapter.setOnItemLongClickListener { item, _ ->
if ((item is FeedGroupCardItem && item.groupId == GROUP_ALL_ID) ||
(item is FeedGroupCardGridItem && item.groupId == GROUP_ALL_ID)
) {
val groupId = when (item) {
is FeedGroupCardItem -> item.groupId
is FeedGroupCardGridItem -> item.groupId
else -> return@setOnItemLongClickListener false
}
if (groupId == GROUP_ALL_ID || groupId == GROUP_UNGROUPED_ID) {
return@setOnItemLongClickListener false
}

when (item) {
is FeedGroupCardItem ->
FeedGroupDialog.newInstance(item.groupId).show(fm, null)

is FeedGroupCardGridItem ->
FeedGroupDialog.newInstance(item.groupId).show(fm, null)
}
FeedGroupDialog.newInstance(groupId).show(fm, null)
return@setOnItemLongClickListener true
}

Expand Down Expand Up @@ -375,31 +394,45 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
feedGroupsCarousel.onRestoreInstanceState(feedGroupsCarouselState)
feedGroupsCarouselState = null
}
currentGroups = groups
currentListViewMode = listViewMode
rebuildCarousel()
}

private fun rebuildCarousel() {
binding.itemsList.post {
if (context == null) {
// since this part was posted to the next UI cycle, the fragment might have been
// removed in the meantime
return@post
}
if (context == null) return@post

feedGroupsCarousel.listViewMode = listViewMode
feedGroupsSortMenuItem.showSortButton = groups.size > 1
feedGroupsSortMenuItem.listViewMode = listViewMode
val selectedId = viewModel.getSelectedGroupId()
feedGroupsCarousel.listViewMode = currentListViewMode
feedGroupsSortMenuItem.showSortButton = currentGroups.size > 1
feedGroupsSortMenuItem.listViewMode = currentListViewMode
feedGroupsCarousel.notifyChanged(FeedGroupCarouselItem.PAYLOAD_UPDATE_LIST_VIEW_MODE)
feedGroupsSortMenuItem.notifyChanged(GroupsHeader.PAYLOAD_UPDATE_ICONS)

// update items here to prevent flickering
carouselAdapter.apply {
clear()
if (listViewMode) {
if (currentListViewMode) {
add(FeedGroupAddNewItem())
add(FeedGroupCardItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.WHATS_NEW))
add(FeedGroupCardItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.WHATS_NEW, selectedId == GROUP_ALL_ID))
} else {
add(FeedGroupAddNewGridItem())
add(FeedGroupCardGridItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.WHATS_NEW))
add(FeedGroupCardGridItem(GROUP_ALL_ID, getString(R.string.all), FeedGroupIcon.WHATS_NEW, selectedId == GROUP_ALL_ID))
}
addAll(
currentGroups.map { group ->
when (group) {
is FeedGroupCardItem -> group.copy(isSelected = group.groupId == selectedId)
is FeedGroupCardGridItem -> group.copy(isSelected = group.groupId == selectedId)
else -> group
}
}
)
if (currentListViewMode) {
add(FeedGroupCardItem(GROUP_UNGROUPED_ID, getString(R.string.feed_group_ungrouped), FeedGroupIcon.PERSON, selectedId == GROUP_UNGROUPED_ID))
} else {
add(FeedGroupCardGridItem(GROUP_UNGROUPED_ID, getString(R.string.feed_group_ungrouped), FeedGroupIcon.PERSON, selectedId == GROUP_UNGROUPED_ID))
}
addAll(groups)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class SubscriptionManager(context: Context) {
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
fun subscriptions() = subscriptionTable.getAll()

fun subscriptionsFilteredByGroup(groupId: Long): Flowable<List<SubscriptionEntity>> = when (groupId) {
FeedGroupEntity.GROUP_ALL_ID -> subscriptionTable.getAll()
FeedGroupEntity.GROUP_UNGROUPED_ID -> subscriptionTable.getSubscriptionsNotInAnyGroup()
else -> subscriptionTable.getSubscriptionsForGroup(groupId)
}

fun getSubscriptions(
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
filterQuery: String = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.processors.BehaviorProcessor
import io.reactivex.rxjava3.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.database.feed.model.FeedGroupEntity.Companion.GROUP_ALL_ID
import org.schabi.newpipe.info_list.ItemViewMode
import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.item.ChannelItem
Expand All @@ -28,10 +30,14 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
)
private val listViewModeFlowable = listViewMode.distinctUntilChanged()

private val selectedGroupId = BehaviorProcessor.createDefault(GROUP_ALL_ID)

private val mutableStateLiveData = MutableLiveData<SubscriptionState>()
private val mutableFeedGroupsLiveData = MutableLiveData<Pair<List<Group>, Boolean>>()
private val mutableSelectedGroupLiveData = MutableLiveData<Long>(GROUP_ALL_ID)
val stateLiveData: LiveData<SubscriptionState> = mutableStateLiveData
val feedGroupsLiveData: LiveData<Pair<List<Group>, Boolean>> = mutableFeedGroupsLiveData
val selectedGroupLiveData: LiveData<Long> = mutableSelectedGroupLiveData

private var feedGroupItemsDisposable = Flowable
.combineLatest(
Expand All @@ -52,10 +58,12 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
)

private var stateItemsDisposable = subscriptionManager.subscriptions()
private var stateItemsDisposable = selectedGroupId
.switchMap { groupId ->
subscriptionManager.subscriptionsFilteredByGroup(groupId).subscribeOn(Schedulers.io())
}
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
.map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } }
.subscribeOn(Schedulers.io())
.subscribe(
{ mutableStateLiveData.postValue(SubscriptionState.LoadedState(it)) },
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
Expand All @@ -75,6 +83,13 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
return listViewMode.value ?: true
}

fun selectGroup(groupId: Long) {
selectedGroupId.onNext(groupId)
mutableSelectedGroupLiveData.postValue(groupId)
}

fun getSelectedGroupId(): Long = selectedGroupId.value ?: GROUP_ALL_ID

sealed class SubscriptionState {
data class LoadedState(val subscriptions: List<Group>) : SubscriptionState()
data class ErrorState(val error: Throwable? = null) : SubscriptionState()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package org.schabi.newpipe.local.subscription.item

import android.view.View
import androidx.core.graphics.ColorUtils
import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.FeedGroupCardGridItemBinding
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import org.schabi.newpipe.util.ThemeHelper

data class FeedGroupCardGridItem(
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
val name: String,
val icon: FeedGroupIcon
val icon: FeedGroupIcon,
val isSelected: Boolean = false
) : BindableItem<FeedGroupCardGridItemBinding>() {
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)

override fun getId(): Long {
return when (groupId) {
FeedGroupEntity.GROUP_ALL_ID -> super.getId()
FeedGroupEntity.GROUP_ALL_ID, FeedGroupEntity.GROUP_UNGROUPED_ID -> super.getId()
else -> groupId
}
}
Expand All @@ -26,6 +29,18 @@ data class FeedGroupCardGridItem(
override fun bind(viewBinding: FeedGroupCardGridItemBinding, position: Int) {
viewBinding.title.text = name
viewBinding.icon.setImageResource(icon.getDrawableRes())
val context = viewBinding.root.context
viewBinding.root.setCardBackgroundColor(
if (isSelected) {
ColorUtils.blendARGB(
ThemeHelper.resolveColorFromAttr(context, R.attr.card_item_background_color),
ThemeHelper.resolveColorFromAttr(context, android.R.attr.colorAccent),
0.15f
)
} else {
ThemeHelper.resolveColorFromAttr(context, R.attr.card_item_background_color)
}
)
}

override fun initializeViewBinding(view: View) = FeedGroupCardGridItemBinding.bind(view)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package org.schabi.newpipe.local.subscription.item

import android.view.View
import androidx.core.graphics.ColorUtils
import com.xwray.groupie.viewbinding.BindableItem
import org.schabi.newpipe.R
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.databinding.FeedGroupCardItemBinding
import org.schabi.newpipe.local.subscription.FeedGroupIcon
import org.schabi.newpipe.util.ThemeHelper

data class FeedGroupCardItem(
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
val name: String,
val icon: FeedGroupIcon
val icon: FeedGroupIcon,
val isSelected: Boolean = false
) : BindableItem<FeedGroupCardItemBinding>() {
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)

override fun getId(): Long {
return when (groupId) {
FeedGroupEntity.GROUP_ALL_ID -> super.getId()
FeedGroupEntity.GROUP_ALL_ID, FeedGroupEntity.GROUP_UNGROUPED_ID -> super.getId()
else -> groupId
}
}
Expand All @@ -26,6 +29,18 @@ data class FeedGroupCardItem(
override fun bind(viewBinding: FeedGroupCardItemBinding, position: Int) {
viewBinding.title.text = name
viewBinding.icon.setImageResource(icon.getDrawableRes())
val context = viewBinding.root.context
viewBinding.root.setCardBackgroundColor(
if (isSelected) {
ColorUtils.blendARGB(
ThemeHelper.resolveColorFromAttr(context, R.attr.card_item_background_color),
ThemeHelper.resolveColorFromAttr(context, android.R.attr.colorAccent),
0.15f
)
} else {
ThemeHelper.resolveColorFromAttr(context, R.attr.card_item_background_color)
}
)
}

override fun initializeViewBinding(view: View) = FeedGroupCardItemBinding.bind(view)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,7 @@
<string name="feed_group_dialog_delete_message">Do you want to delete this group?</string>
<string name="feed_create_new_group_button_title">New</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Show only ungrouped subscriptions</string>
<string name="feed_group_ungrouped">Ungrouped</string>
<string name="settings_category_feed_title">Feed</string>
<string name="feed_update_threshold_title">Feed update threshold</string>
<string name="feed_update_threshold_summary">Time after last update before a subscription is considered outdated — %s</string>
Expand Down
Loading