Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support multiple filter selection #5478

Merged
merged 10 commits into from
Jan 12, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ object PreferenceKeys {
const val LAST_STREAM_VIDEO_ID = "last_stream_video_id"
const val LAST_WATCHED_FEED_TIME = "last_watched_feed_time"
const val HIDE_WATCHED_FROM_FEED = "hide_watched_from_feed"
const val SELECTED_FEED_FILTER = "filer_feed"
const val SELECTED_FEED_FILTERS = "filter_feed"
const val FEED_SORT_ORDER = "sort_oder_feed"

/**
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/java/com/github/libretube/enums/ContentFilter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.libretube.enums

import com.github.libretube.constants.PreferenceKeys.SELECTED_FEED_FILTERS
import com.github.libretube.helpers.PreferenceHelper

private val enabledFiltersSet get() = PreferenceHelper
.getString(SELECTED_FEED_FILTERS, "1,2,3")
.split(',')
.toMutableSet()
Bnyro marked this conversation as resolved.
Show resolved Hide resolved

enum class ContentFilter(private val id: Int) {
Bnyro marked this conversation as resolved.
Show resolved Hide resolved
VIDEOS(1),
SHORTS(2),
LIVESTREAMS(3);

fun isEnabled() = enabledFiltersSet.contains(id.toString())

fun setState(enabled: Boolean) {
val newFilters = enabledFiltersSet
.apply { if (enabled) add(id.toString()) else remove(id.toString()) }
.joinToString(",")

PreferenceHelper.putString(SELECTED_FEED_FILTERS, newFilters)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.libretube.obj

data class SelectableOption(
val isSelected: Boolean,
val name: String
)
Bnyro marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.FragmentHomeBinding
import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.enums.ContentFilter
import com.github.libretube.helpers.LocaleHelper
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PreferenceHelper
Expand Down Expand Up @@ -145,12 +146,13 @@ class HomeFragment : Fragment() {
}
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
}

val allowShorts = ContentFilter.SHORTS.isEnabled()
val allowVideos = ContentFilter.VIDEOS.isEnabled()
val allowAll = (!allowShorts && !allowVideos)

var filteredFeed = feed.filter {
when (PreferenceHelper.getInt(PreferenceKeys.SELECTED_FEED_FILTER, 0)) {
1 -> !it.isShort
2 -> it.isShort
else -> true
}
(allowShorts && it.isShort) || (allowVideos && !it.isShort) || allowAll
}
if (PreferenceHelper.getBoolean(PreferenceKeys.HIDE_WATCHED_FROM_FEED, false)) {
filteredFeed = runBlocking { DatabaseHelper.filterUnwatched(filteredFeed) }
Expand Down Expand Up @@ -256,4 +258,4 @@ class HomeFragment : Fragment() {
private const val BOOKMARKS = "bookmarks"
private const val PLAYLISTS = "playlists"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@ import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.FragmentSubscriptionsBinding
import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.enums.ContentFilter
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.formatShort
import com.github.libretube.extensions.toID
import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.obj.SelectableOption
import com.github.libretube.ui.adapters.LegacySubscriptionAdapter
import com.github.libretube.ui.adapters.SubscriptionChannelAdapter
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.base.DynamicLayoutManagerFragment
import com.github.libretube.ui.models.EditChannelGroupsModel
import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.ui.models.SubscriptionsViewModel
import com.github.libretube.ui.sheets.BaseBottomSheet
import com.github.libretube.ui.sheets.ChannelGroupsSheet
import com.github.libretube.ui.sheets.FilterSortBottomSheet
import com.github.libretube.util.PlayingQueue
import com.google.android.material.chip.Chip
import kotlinx.coroutines.Dispatchers
Expand All @@ -57,11 +59,6 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment() {
PreferenceHelper.putInt(PreferenceKeys.FEED_SORT_ORDER, value)
field = value
}
private var selectedFilter = PreferenceHelper.getInt(PreferenceKeys.SELECTED_FEED_FILTER, 0)
set(value) {
PreferenceHelper.putInt(PreferenceKeys.SELECTED_FEED_FILTER, value)
field = value
}

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -84,9 +81,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment() {
false
)

// update the text according to the current order and filter
binding.sortTV.text = resources.getStringArray(R.array.sortOptions)[selectedSortOrder]
binding.filterTV.text = resources.getStringArray(R.array.filterOptions)[selectedFilter]
setupSortAndFilter()

binding.subRefresh.isEnabled = true
binding.subProgress.isVisible = true
Expand All @@ -109,30 +104,6 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment() {
viewModel.fetchFeed(requireContext())
}

binding.sortTV.setOnClickListener {
val sortOptions = resources.getStringArray(R.array.sortOptions)

BaseBottomSheet().apply {
setSimpleItems(sortOptions.toList()) { index ->
binding.sortTV.text = sortOptions[index]
selectedSortOrder = index
showFeed()
}
}.show(childFragmentManager)
}

binding.filterTV.setOnClickListener {
val filterOptions = resources.getStringArray(R.array.filterOptions)

BaseBottomSheet().apply {
setSimpleItems(filterOptions.toList()) { index ->
binding.filterTV.text = filterOptions[index]
selectedFilter = index
showFeed()
}
}.show(childFragmentManager)
}

binding.toggleSubs.isVisible = true

binding.toggleSubs.setOnClickListener {
Expand Down Expand Up @@ -196,6 +167,23 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment() {
}
}

private fun setupSortAndFilter() {
binding.filterSort.setOnClickListener {
val sortOptions = resources.getStringArray(R.array.sortOptions)

FilterSortBottomSheet.createWith(
sortOptions = sortOptions.mapIndexed { index, option ->
SelectableOption(isSelected = index == selectedSortOrder, name = option)
},
sortListener = { index, _ ->
selectedSortOrder = index
showFeed()
},
filtersListener = ::showFeed
).show(childFragmentManager)
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
Expand Down Expand Up @@ -258,15 +246,18 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment() {
}

private fun List<StreamItem>.filterByStatusAndWatchPosition(): List<StreamItem> {

val streamItems = this.filter {
val isLive = (it.duration ?: -1L) < 0L
Bnyro marked this conversation as resolved.
Show resolved Hide resolved
when (selectedFilter) {
0 -> true
1 -> !it.isShort && !isLive
2 -> it.isShort
3 -> isLive
else -> throw IllegalArgumentException()
val isVideo = !it.isShort && !isLive

return@filter when {
!ContentFilter.SHORTS.isEnabled() && it.isShort -> false
!ContentFilter.VIDEOS.isEnabled() && isVideo -> false
!ContentFilter.LIVESTREAMS.isEnabled() && isLive -> false
else -> true
}

}

if (!PreferenceHelper.getBoolean(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.github.libretube.ui.sheets

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import com.github.libretube.databinding.FilterSortSheetBinding
import com.github.libretube.enums.ContentFilter
import com.github.libretube.obj.SelectableOption

class FilterSortBottomSheet: ExpandedBottomSheet() {

private var _binding: FilterSortSheetBinding? = null
private val binding get() = _binding!!

private lateinit var sortOptions: Collection<SelectableOption>
private lateinit var sortFilter: (index: Int, isSelected: Boolean) -> Unit
private lateinit var filterListener: () -> Unit

Bnyro marked this conversation as resolved.
Show resolved Hide resolved
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FilterSortSheetBinding.inflate(layoutInflater)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
addSortOptions()
observeSortChanges()
setInitialFiltersState()
observeFiltersChanges()
}

private fun addSortOptions() {
for (i in sortOptions.indices) {
val option = sortOptions.elementAt(i)
val rb = createRadioButton(i, option.name)

binding.sortRadioGroup.addView(rb)

if (option.isSelected) {
binding.sortRadioGroup.check(rb.id)
}
}
}

private fun createRadioButton(index: Int, name: String): RadioButton {
return RadioButton(context).apply {
tag = index
text = name
}
}

private fun observeSortChanges() {
binding.sortRadioGroup.setOnCheckedChangeListener { group, checkedId ->
val index = group.findViewById<RadioButton>(checkedId).tag as Int
sortFilter(index, true)
}
}

private fun setInitialFiltersState() {
binding.filterVideos.isChecked = ContentFilter.VIDEOS.isEnabled()
binding.filterShorts.isChecked = ContentFilter.SHORTS.isEnabled()
binding.filterLivestreams.isChecked = ContentFilter.LIVESTREAMS.isEnabled()
}

private fun observeFiltersChanges() {
binding.filters.setOnCheckedStateChangeListener { _, _ ->
ContentFilter.VIDEOS.setState(binding.filterVideos.isChecked)
ContentFilter.SHORTS.setState(binding.filterShorts.isChecked)
ContentFilter.LIVESTREAMS.setState(binding.filterLivestreams.isChecked)

filterListener.invoke()
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

companion object {

fun createWith(
sortOptions: Collection<SelectableOption>,
sortListener: (index: Int, isSelected: Boolean) -> Unit,
filtersListener: () -> Unit
) = FilterSortBottomSheet().apply {
this.sortOptions = sortOptions
this.sortFilter = sortListener
this.filterListener = filtersListener
}
Bnyro marked this conversation as resolved.
Show resolved Hide resolved

}

}
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ic_filter_sort.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
RafaelsRamos marked this conversation as resolved.
Show resolved Hide resolved

<path android:fillColor="@android:color/white" android:pathData="M4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>

</vector>
Loading
Loading