Skip to content

Commit

Permalink
feat: Support multiple filter selection (#5478)
Browse files Browse the repository at this point in the history
Co-authored-by: Bnyro <[email protected]>
  • Loading branch information
RafaelsRamos and Bnyro authored Jan 12, 2024
1 parent 0e960e1 commit 444eb69
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ object IntentData {
const val bitmapUrl = "bitmapUrl"
const val isCurrentlyPlaying = "isCurrentlyPlaying"
const val isSubscribed = "isSubscribed"
const val sortOptions = "sortOptions"
}
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
33 changes: 33 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,33 @@
package com.github.libretube.enums

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

enum class ContentFilter {
VIDEOS,
SHORTS,
LIVESTREAMS;

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

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

PreferenceHelper.putString(SELECTED_FEED_FILTERS, newFilters)
}

companion object {

private val enabledFiltersSet get() = PreferenceHelper
.getString(
key = SELECTED_FEED_FILTERS,
defValue = entries.joinToString(",") { it.ordinal.toString() }
)
.split(',')
.toMutableSet()

}

}
10 changes: 10 additions & 0 deletions app/src/main/java/com/github/libretube/obj/SelectableOption.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.github.libretube.obj

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class SelectableOption(
val isSelected: Boolean,
val name: String
): Parcelable
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 @@ -6,6 +6,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.core.view.isGone
import androidx.core.view.isVisible
Expand All @@ -16,24 +18,29 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.IntentData
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.ui.sheets.FilterSortBottomSheet.Companion.FILTER_SORT_REQUEST_KEY
import com.github.libretube.ui.sheets.FilterSortBottomSheet.Companion.SELECTED_SORT_OPTION_KEY
import com.github.libretube.util.PlayingQueue
import com.google.android.material.chip.Chip
import kotlinx.coroutines.Dispatchers
Expand All @@ -57,11 +64,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 +86,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 +109,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 +172,33 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment() {
}
}

private fun setupSortAndFilter() {
binding.filterSort.setOnClickListener {
val activityCompat = context as AppCompatActivity
val fragManager = activityCompat
.supportFragmentManager
.apply {
setFragmentResultListener(FILTER_SORT_REQUEST_KEY, activityCompat) { _, resultBundle ->
selectedSortOrder = resultBundle.getInt(SELECTED_SORT_OPTION_KEY)
showFeed()
}
}

FilterSortBottomSheet()
.apply { arguments = bundleOf(IntentData.sortOptions to fetchSortOptions()) }
.show(fragManager)
}
}

private fun fetchSortOptions(): Array<SelectableOption> {
return resources
.getStringArray(R.array.sortOptions)
.mapIndexed { index, option ->
SelectableOption(isSelected = index == selectedSortOrder, name = option)
}
.toTypedArray()
}

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

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

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

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

}

if (!PreferenceHelper.getBoolean(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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 androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import com.github.libretube.constants.IntentData
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: Array<SelectableOption>

private var selectedIndex: Int = 0

override fun onCreate(savedInstanceState: Bundle?) {
sortOptions = requireArguments().getParcelableArray(IntentData.sortOptions) as Array<SelectableOption>
super.onCreate(savedInstanceState)
}

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) {
selectedIndex = i
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
selectedIndex = index
notifyChange()
}
}

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)
notifyChange()
}
}

private fun notifyChange() {
setFragmentResult(
requestKey = FILTER_SORT_REQUEST_KEY,
result = bundleOf(SELECTED_SORT_OPTION_KEY to selectedIndex)
)
}

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

companion object {
const val FILTER_SORT_REQUEST_KEY = "filter_sort_request_key"
const val SELECTED_SORT_OPTION_KEY = "selected_sort_option_key"
}

}
12 changes: 12 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,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">

<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

0 comments on commit 444eb69

Please sign in to comment.