Merge pull request #218 from Bnyro/filter

Search Filters, Nextpage in Search, Search History Settings
This commit is contained in:
Bnyro 2022-05-16 18:28:14 +02:00 committed by GitHub
commit 8a6ec7b30a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 239 additions and 82 deletions

View File

@ -41,7 +41,7 @@ WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OP
| Search Suggestions | ✅ |
| Subtitles | ✅ |
| Comments | ✅ |
| Search Filters | 🔴 |
| Search Filters | |
## Contributing

View File

@ -25,6 +25,13 @@ interface PipedApi {
@Query("filter") filter: String
): SearchResult
@GET("nextpage/search")
suspend fun getSearchResultsNextPage(
@Query("q") searchQuery: String,
@Query("filter") filter: String,
@Query("nextpage") nextPage: String
): SearchResult
@GET("suggestions")
suspend fun getSuggestions(@Query("query") query: String): List<String>

View File

@ -1,6 +1,7 @@
package com.github.libretube
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@ -11,9 +12,9 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.*
import android.widget.TextView.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
@ -28,9 +29,15 @@ import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class SearchFragment : Fragment() {
private val TAG = "SearchFragment"
private var selectedFilter = 0
private var apiSearchFilter = "all"
private var nextPage : String? = null
private lateinit var searchRecView : RecyclerView
private var searchAdapter : SearchAdapter? = null
private var isLoading : Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
@ -49,31 +56,71 @@ class SearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.search_recycler)
searchRecView = view.findViewById<RecyclerView>(R.id.search_recycler)
val autoTextView = view.findViewById<AutoCompleteTextView>(R.id.autoCompleteTextView)
val historyRecycler = view.findViewById<RecyclerView>(R.id.history_recycler)
val filterImageView = view.findViewById<ImageView>(R.id.filterMenu_imageView)
var tempSelectedItem = 0
filterImageView.setOnClickListener {
val filterOptions = arrayOf(
getString(R.string.all),
getString(R.string.videos),
getString(R.string.channels),
getString(R.string.playlists),
getString(R.string.music_songs),
getString(R.string.music_videos),
getString(R.string.music_albums),
getString(R.string.music_playlists)
)
AlertDialog.Builder(view.context)
.setTitle(getString(R.string.choose_filter))
.setSingleChoiceItems(filterOptions, selectedFilter, DialogInterface.OnClickListener {
_, id -> tempSelectedItem = id
})
.setPositiveButton(getString(R.string.okay), DialogInterface.OnClickListener { _, _ ->
selectedFilter = tempSelectedItem
apiSearchFilter = when (selectedFilter) {
0 -> "all"
1 -> "videos"
2 -> "channels"
3 -> "playlists"
4 -> "music_songs"
5 -> "music_videos"
6 -> "music_albums"
7 -> "music_playlists"
else -> "all"
}
fetchSearch(autoTextView.text.toString())
})
.setNegativeButton(getString(R.string.cancel), null)
.create()
.show()
}
//show search history
recyclerView.visibility = GONE
searchRecView.visibility = GONE
historyRecycler.visibility = VISIBLE
historyRecycler.layoutManager = LinearLayoutManager(view.context)
var historylist = getHistory()
if (historylist.size != 0) {
if (historylist.isNotEmpty()) {
historyRecycler.adapter =
SearchHistoryAdapter(requireContext(), historylist, autoTextView)
}
recyclerView.layoutManager = GridLayoutManager(view.context, 1)
searchRecView.layoutManager = GridLayoutManager(view.context, 1)
autoTextView.requestFocus()
val imm =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm!!.showSoftInput(autoTextView, InputMethodManager.SHOW_IMPLICIT)
imm.showSoftInput(autoTextView, InputMethodManager.SHOW_IMPLICIT)
autoTextView.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
@ -86,27 +133,34 @@ class SearchFragment : Fragment() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (s!! != "") {
recyclerView.visibility = VISIBLE
searchRecView.visibility = VISIBLE
historyRecycler.visibility = GONE
recyclerView.adapter = null
searchRecView.adapter = null
searchRecView.viewTreeObserver
.addOnScrollChangedListener {
if (!searchRecView.canScrollVertically(1)) {
fetchNextSearchItems(autoTextView.text.toString())
}
}
GlobalScope.launch {
fetchSuggestions(s.toString(), autoTextView)
delay(3000)
addtohistory(s.toString())
fetchSearch(s.toString(), recyclerView)
delay(1000)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (sharedPreferences.getBoolean("search_history_toggle", true)) addtohistory(s.toString())
fetchSearch(s.toString())
}
}
}
override fun afterTextChanged(s: Editable?) {
if (s!!.isEmpty()) {
recyclerView.visibility = GONE
searchRecView.visibility = GONE
historyRecycler.visibility = VISIBLE
var historylist = getHistory()
if (historylist.size != 0) {
if (historylist.isNotEmpty()) {
historyRecycler.adapter =
SearchHistoryAdapter(requireContext(), historylist, autoTextView)
}
@ -116,8 +170,8 @@ class SearchFragment : Fragment() {
})
autoTextView.setOnEditorActionListener(OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
hideKeyboard();
autoTextView.dismissDropDown();
hideKeyboard()
autoTextView.dismissDropDown()
return@OnEditorActionListener true
}
false
@ -143,10 +197,10 @@ class SearchFragment : Fragment() {
autoTextView.setAdapter(adapter)
}
}
private fun fetchSearch(query: String, recyclerView: RecyclerView){
private fun fetchSearch(query: String){
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getSearchResults(query, "all")
RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection $e")
@ -155,12 +209,35 @@ class SearchFragment : Fragment() {
Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated
}
nextPage = response.nextpage
if(response.items!!.isNotEmpty()){
runOnUiThread {
recyclerView.adapter = SearchAdapter(response.items)
searchAdapter = SearchAdapter(response.items)
searchRecView.adapter = searchAdapter
}
}
isLoading = false
}
}
private fun fetchNextSearchItems(query: String){
lifecycleScope.launchWhenCreated {
if (!isLoading) {
isLoading = true
val response = try {
RetrofitInstance.api.getSearchResultsNextPage(query,apiSearchFilter,nextPage!!)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response," + e.response())
return@launchWhenCreated
}
nextPage = response.nextpage
searchAdapter?.updateItems(response.items!!)
isLoading = false
}
}
}

View File

@ -9,6 +9,7 @@ import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.system.Os.remove
import android.text.TextUtils
import android.util.Log
import android.view.View
@ -235,6 +236,13 @@ class SettingsActivity : AppCompatActivity(),
true
}
val clearHistory = findPreference<Preference>("clear_history")
clearHistory?.setOnPreferenceClickListener {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
sharedPreferences.edit().remove("search_history").commit()
true
}
val about = findPreference<Preference>("about")
about?.setOnPreferenceClickListener {
val uri = Uri.parse("https://libre-tube.github.io/")

View File

@ -18,7 +18,14 @@ import com.github.libretube.obj.SearchItem
import com.squareup.picasso.Picasso
class SearchAdapter(private val searchItems: List<SearchItem>): RecyclerView.Adapter<CustomViewHolder1>() {
class SearchAdapter(private val searchItems: MutableList<SearchItem>): RecyclerView.Adapter<CustomViewHolder1>() {
fun updateItems(newItems: List<SearchItem>){
var searchItemsSize = searchItems.size
searchItems.addAll(newItems)
notifyItemRangeInserted(searchItemsSize, newItems.size)
}
override fun getItemCount(): Int {
return searchItems.size
}
@ -52,19 +59,17 @@ class CustomViewHolder1(private val v: View): RecyclerView.ViewHolder(v){
private fun bindWatch(item: SearchItem) {
val thumbnailImage = v.findViewById<ImageView>(R.id.search_thumbnail)
Picasso.get().load(item.thumbnail).into(thumbnailImage)
Picasso.get().load(item.thumbnail).fit().centerCrop().into(thumbnailImage)
val thumbnailDuration = v.findViewById<TextView>(R.id.search_thumbnail_duration)
thumbnailDuration.text = DateUtils.formatElapsedTime(item.duration!!)
val channelImage = v.findViewById<ImageView>(R.id.search_channel_image)
Picasso.get().load(item.uploaderAvatar).into(channelImage)
Picasso.get().load(item.uploaderAvatar).fit().centerCrop().into(channelImage)
val title = v.findViewById<TextView>(R.id.search_description)
if (item.title!!.length > 60) {
title.text = item.title?.substring(0, 60) + "..."
} else {
title.text = item.title
}
title.text = if (item.title!!.length > 60) item.title?.substring(0, 60) + "..." else item.title
val views = v.findViewById<TextView>(R.id.search_views)
views.text = item.views.formatShort() +""+item.uploadedDate
val viewsString = if (item.views?.toInt() != -1) item.views.formatShort() else ""
val uploadDate = if (item.uploadedDate != null) item.uploadedDate else ""
views.text = if (viewsString != "" && uploadDate != "") viewsString + "" + uploadDate else viewsString + uploadDate
val channelName = v.findViewById<TextView>(R.id.search_channel_name)
channelName.text = item.uploaderName
v.setOnClickListener{
@ -88,7 +93,7 @@ class CustomViewHolder1(private val v: View): RecyclerView.ViewHolder(v){
}
private fun bindChannel(item: SearchItem) {
val channelImage = v.findViewById<ImageView>(R.id.search_channel_image)
Picasso.get().load(item.thumbnail).into(channelImage)
Picasso.get().load(item.thumbnail).fit().centerCrop().into(channelImage)
val channelName = v.findViewById<TextView>(R.id.search_channel_name)
channelName.text = item.name
val channelViews = v.findViewById<TextView>(R.id.search_views)
@ -102,15 +107,15 @@ class CustomViewHolder1(private val v: View): RecyclerView.ViewHolder(v){
}
private fun bindPlaylist(item: SearchItem) {
val playlistImage = v.findViewById<ImageView>(R.id.search_thumbnail)
Picasso.get().load(item.thumbnail).into(playlistImage)
Picasso.get().load(item.thumbnail).fit().centerCrop().into(playlistImage)
val playlistNumber = v.findViewById<TextView>(R.id.search_playlist_number)
playlistNumber.text = item.videos.toString()
if (item.videos?.toInt() != -1) playlistNumber.text = item.videos.toString()
val playlistName = v.findViewById<TextView>(R.id.search_description)
playlistName.text = item.name
val playlistChannelName = v.findViewById<TextView>(R.id.search_name)
playlistChannelName.text = item.uploaderName
val playlistVideosNumber = v.findViewById<TextView>(R.id.search_playlist_videos)
playlistVideosNumber.text = item.videos.toString()+" videos"
if (item.videos?.toInt() != -1) playlistVideosNumber.text = v.context.getString(R.string.videoCount, item.videos.toString())
v.setOnClickListener {
//playlist clicked
val activity = v.context as MainActivity

View File

@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class SearchResult(
val items: List<SearchItem>? = listOf(),
val items: MutableList<SearchItem>? = arrayListOf(),
val nextpage: String? ="",
val suggestion: String?="",
val corrected: Boolean? = null

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8.46,11.88l1.41,-1.41L12,12.59l2.12,-2.12 1.41,1.41L13.41,14l2.12,2.12 -1.41,1.41L12,15.41l-2.12,2.12 -1.41,-1.41L10.59,14l-2.13,-2.12zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/>
</vector>

View File

@ -6,12 +6,23 @@
android:layout_height="match_parent"
tools:context=".SearchFragment">
<LinearLayout
android:id="@+id/searchbar_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/outlinedTextField"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="27dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -35,12 +46,23 @@
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:padding="12dp" />
android:padding="12dp"
android:dropDownWidth="match_parent" />
</com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.card.MaterialCardView>
<ImageView
android:id="@+id/filterMenu_imageView"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_weight="0"
android:layout_alignParentRight="true"
android:layout_marginTop="25dp"
android:layout_marginRight="20dp"
android:src="@drawable/ic_filter" />
</LinearLayout>
<!-- <TextView-->
<!-- android:id="@+id/tv_genres"-->
@ -297,21 +319,18 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/outlinedTextField"
app:layout_constraintTop_toBottomOf="@+id/searchbar_holder"
android:visibility="gone"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="20dp"
android:layout_margin="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/outlinedTextField"
app:layout_constraintTop_toBottomOf="@+id/searchbar_holder"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -43,21 +43,17 @@
android:id="@+id/search_playlist_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10"
android:textColor="#ECE4E4"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
/>
android:textColor="#ECE4E4" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_playlist"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_below="@+id/search_playlist_number"
/>
android:layout_centerInParent="true"
app:srcCompat="@drawable/ic_playlist" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
@ -67,7 +63,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_playlist"
app:layout_constraintTop_toTopOf="parent" />
@ -77,7 +72,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_playlist"
app:layout_constraintTop_toBottomOf="@+id/search_description" />
@ -86,7 +80,6 @@
android:id="@+id/search_playlist_videos"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="TextView"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"

View File

@ -50,7 +50,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_search_thumbnail"
app:layout_constraintTop_toTopOf="parent" />
@ -60,7 +59,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_search_thumbnail"
app:layout_constraintTop_toBottomOf="@+id/channel_description" />

View File

@ -53,7 +53,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_search_thumbnail"
app:layout_constraintTop_toTopOf="parent" />
@ -63,7 +62,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_search_thumbnail"
app:layout_constraintTop_toBottomOf="@+id/search_description" />
@ -83,7 +81,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/search_channel_image"
app:layout_constraintTop_toBottomOf="@+id/search_views" />

View File

@ -72,6 +72,18 @@
<string name="noInternet">No Internet Connection</string>
<string name="retry">Retry</string>
<string name="comments">Comments</string>
<string name="choose_filter">Choose search filter</string>
<string name="channels">Channels</string>
<string name="all">All</string>
<string name="playlists">Playlists</string>
<string name="okay">Ok</string>
<string name="history">History</string>
<string name="search_history">Search History</string>
<string name="clear_history">Clear History</string>
<string name="music_songs">Music Songs</string>
<string name="music_videos">Music Videos</string>
<string name="music_albums">Music Albums</string>
<string name="music_playlists">Music Playlists</string>
<string name="defaultTab">Default Tab</string>
<string name="sponsorblock">SponsorBlock</string>
<string name="sponsorblock_summary">Uses API from https://sponsor.ajay.app/</string>

View File

@ -109,6 +109,21 @@
</PreferenceCategory>
<PreferenceCategory app:title="@string/history">
<SwitchPreference
app:title="@string/search_history"
app:key="search_history_toggle"
android:defaultValue="true"
android:icon="@drawable/ic_history" />
<Preference
app:title="@string/clear_history"
app:key="clear_history"
android:icon="@drawable/ic_trash" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/about">
<Preference