mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 16:00:31 +05:30
new search behavior
This commit is contained in:
parent
f33b9b7afb
commit
022281e107
@ -17,9 +17,11 @@ import android.view.WindowInsetsController
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.fragment.app.replace
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
@ -28,6 +30,7 @@ import com.github.libretube.Globals
|
|||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.databinding.ActivityMainBinding
|
import com.github.libretube.databinding.ActivityMainBinding
|
||||||
import com.github.libretube.fragments.PlayerFragment
|
import com.github.libretube.fragments.PlayerFragment
|
||||||
|
import com.github.libretube.fragments.SearchFragment
|
||||||
import com.github.libretube.preferences.PreferenceHelper
|
import com.github.libretube.preferences.PreferenceHelper
|
||||||
import com.github.libretube.preferences.PreferenceKeys
|
import com.github.libretube.preferences.PreferenceKeys
|
||||||
import com.github.libretube.services.ClosingService
|
import com.github.libretube.services.ClosingService
|
||||||
@ -46,6 +49,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
lateinit var navController: NavController
|
lateinit var navController: NavController
|
||||||
private var startFragmentId = R.id.homeFragment
|
private var startFragmentId = R.id.homeFragment
|
||||||
var autoRotationEnabled = false
|
var autoRotationEnabled = false
|
||||||
|
private var searchFragment: SearchFragment? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// set the app theme (e.g. Material You)
|
// set the app theme (e.g. Material You)
|
||||||
@ -154,7 +158,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
menuInflater.inflate(R.menu.action_bar, menu)
|
menuInflater.inflate(R.menu.action_bar, menu)
|
||||||
return true
|
val searchItem = menu.findItem(R.id.action_search)
|
||||||
|
|
||||||
|
// stuff for the search in the topBar
|
||||||
|
val searchView = searchItem.actionView as SearchView
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("query", query)
|
||||||
|
navController.navigate(R.id.searchResultFragment, bundle)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("query", newText)
|
||||||
|
navController.navigate(R.id.searchFragment, bundle)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
@ -162,10 +185,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// automatically handle clicks on the Home/Up button, so long
|
// automatically handle clicks on the Home/Up button, so long
|
||||||
// as you specify a parent activity in AndroidManifest.xml.
|
// as you specify a parent activity in AndroidManifest.xml.
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.action_search -> {
|
|
||||||
navController.navigate(R.id.searchFragment)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.action_settings -> {
|
R.id.action_settings -> {
|
||||||
val settingsIntent = Intent(this, SettingsActivity::class.java)
|
val settingsIntent = Intent(this, SettingsActivity::class.java)
|
||||||
startActivity(settingsIntent)
|
startActivity(settingsIntent)
|
||||||
@ -185,6 +204,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addToHistory(query: String) {
|
||||||
|
val searchHistoryEnabled =
|
||||||
|
PreferenceHelper.getBoolean(PreferenceKeys.SEARCH_HISTORY_TOGGLE, true)
|
||||||
|
if (searchHistoryEnabled && query != "") {
|
||||||
|
PreferenceHelper.saveToSearchHistory(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
val intentData: Uri? = intent?.data
|
val intentData: Uri? = intent?.data
|
||||||
|
@ -2,16 +2,12 @@ package com.github.libretube.adapters
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.github.libretube.databinding.SearchhistoryRowBinding
|
import com.github.libretube.databinding.SearchhistoryRowBinding
|
||||||
import com.github.libretube.fragments.SearchFragment
|
|
||||||
import com.github.libretube.preferences.PreferenceHelper
|
import com.github.libretube.preferences.PreferenceHelper
|
||||||
|
|
||||||
class SearchHistoryAdapter(
|
class SearchHistoryAdapter(
|
||||||
private var historyList: List<String>,
|
private var historyList: List<String>
|
||||||
private val editText: EditText,
|
|
||||||
private val searchFragment: SearchFragment
|
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<SearchHistoryViewHolder>() {
|
RecyclerView.Adapter<SearchHistoryViewHolder>() {
|
||||||
|
|
||||||
@ -37,8 +33,6 @@ class SearchHistoryAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
editText.setText(historyQuery)
|
|
||||||
searchFragment.fetchSearch(historyQuery)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,11 @@ package com.github.libretube.adapters
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.github.libretube.databinding.SearchsuggestionRowBinding
|
import com.github.libretube.databinding.SearchsuggestionRowBinding
|
||||||
import com.github.libretube.fragments.SearchFragment
|
|
||||||
|
|
||||||
class SearchSuggestionsAdapter(
|
class SearchSuggestionsAdapter(
|
||||||
private var suggestionsList: List<String>,
|
private var suggestionsList: List<String>
|
||||||
private var editText: EditText,
|
|
||||||
private val searchFragment: SearchFragment
|
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<SearchSuggestionsViewHolder>() {
|
RecyclerView.Adapter<SearchSuggestionsViewHolder>() {
|
||||||
|
|
||||||
@ -31,8 +27,6 @@ class SearchSuggestionsAdapter(
|
|||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
suggestionText.text = suggestion
|
suggestionText.text = suggestion
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
editText.setText(suggestion)
|
|
||||||
searchFragment.fetchSearch(editText.text.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,51 +1,30 @@
|
|||||||
package com.github.libretube.fragments
|
package com.github.libretube.fragments
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import android.widget.TextView.GONE
|
|
||||||
import android.widget.TextView.OnEditorActionListener
|
|
||||||
import android.widget.TextView.VISIBLE
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.adapters.SearchAdapter
|
|
||||||
import com.github.libretube.adapters.SearchHistoryAdapter
|
|
||||||
import com.github.libretube.adapters.SearchSuggestionsAdapter
|
import com.github.libretube.adapters.SearchSuggestionsAdapter
|
||||||
import com.github.libretube.databinding.FragmentSearchBinding
|
import com.github.libretube.databinding.FragmentSearchBinding
|
||||||
import com.github.libretube.preferences.PreferenceHelper
|
|
||||||
import com.github.libretube.preferences.PreferenceKeys
|
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.RetrofitInstance
|
||||||
import com.github.libretube.util.hideKeyboard
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class SearchFragment : Fragment() {
|
class SearchFragment() : Fragment() {
|
||||||
private val TAG = "SearchFragment"
|
private val TAG = "SearchFragment"
|
||||||
private lateinit var binding: FragmentSearchBinding
|
private lateinit var binding: FragmentSearchBinding
|
||||||
|
|
||||||
private var apiSearchFilter = "all"
|
private var query: String? = null
|
||||||
private var nextPage: String? = null
|
|
||||||
|
|
||||||
private var searchAdapter: SearchAdapter? = null
|
|
||||||
private var isLoading: Boolean = true
|
|
||||||
private var isFetchingSearch: Boolean = false
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
arguments?.let {
|
query = arguments?.getString("query")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -60,103 +39,13 @@ class SearchFragment : Fragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
binding.clearSearchImageView.setOnClickListener {
|
// fetch the search
|
||||||
binding.autoCompleteTextView.text.clear()
|
fetchSuggestions(query!!)
|
||||||
binding.historyRecycler.adapter = null
|
|
||||||
showHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.filterMenuImageView.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)
|
|
||||||
)
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(view.context)
|
|
||||||
.setTitle(getString(R.string.choose_filter))
|
|
||||||
.setItems(filterOptions) { _, id ->
|
|
||||||
apiSearchFilter = when (id) {
|
|
||||||
0 -> "all"
|
|
||||||
1 -> "videos"
|
|
||||||
2 -> "channels"
|
|
||||||
3 -> "playlists"
|
|
||||||
4 -> "music_songs"
|
|
||||||
5 -> "music_videos"
|
|
||||||
6 -> "music_albums"
|
|
||||||
7 -> "music_playlists"
|
|
||||||
else -> "all"
|
|
||||||
}
|
|
||||||
fetchSearch(binding.autoCompleteTextView.text.toString())
|
|
||||||
}
|
|
||||||
.setNegativeButton(getString(R.string.cancel), null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
// show search history
|
|
||||||
binding.historyRecycler.layoutManager = LinearLayoutManager(view.context)
|
|
||||||
showHistory()
|
|
||||||
|
|
||||||
binding.searchRecycler.layoutManager = GridLayoutManager(view.context, 1)
|
|
||||||
binding.autoCompleteTextView.requestFocus()
|
|
||||||
val imm =
|
|
||||||
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.showSoftInput(binding.autoCompleteTextView, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
|
|
||||||
binding.autoCompleteTextView.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(
|
|
||||||
s: CharSequence?,
|
|
||||||
start: Int,
|
|
||||||
count: Int,
|
|
||||||
after: Int
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
||||||
if (s.toString() != "") {
|
|
||||||
binding.searchRecycler.adapter = null
|
|
||||||
|
|
||||||
binding.searchRecycler.viewTreeObserver
|
|
||||||
.addOnScrollChangedListener {
|
|
||||||
if (!binding.searchRecycler.canScrollVertically(1)) {
|
|
||||||
fetchNextSearchItems(binding.autoCompleteTextView.text.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchSuggestions(s.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
|
||||||
if (s!!.isEmpty()) {
|
|
||||||
binding.historyRecycler.adapter = null
|
|
||||||
showHistory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
binding.autoCompleteTextView.setOnEditorActionListener(
|
|
||||||
OnEditorActionListener { textView, actionId, _ ->
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_SEARCH && textView.text.toString() != "") {
|
|
||||||
view.let { context?.hideKeyboard(it) }
|
|
||||||
binding.searchRecycler.visibility = VISIBLE
|
|
||||||
binding.historyRecycler.visibility = GONE
|
|
||||||
fetchSearch(binding.autoCompleteTextView.text.toString())
|
|
||||||
return@OnEditorActionListener true
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchSuggestions(query: String) {
|
private fun fetchSuggestions(query: String) {
|
||||||
fun run() {
|
fun run() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
binding.searchRecycler.visibility = GONE
|
|
||||||
binding.historyRecycler.visibility = VISIBLE
|
|
||||||
val response = try {
|
val response = try {
|
||||||
RetrofitInstance.api.getSuggestions(query)
|
RetrofitInstance.api.getSuggestions(query)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
@ -168,74 +57,17 @@ class SearchFragment : Fragment() {
|
|||||||
return@launchWhenCreated
|
return@launchWhenCreated
|
||||||
}
|
}
|
||||||
// only load the suggestions if the input field didn't get cleared yet
|
// only load the suggestions if the input field didn't get cleared yet
|
||||||
if (binding.autoCompleteTextView.text.toString() != "") {
|
val suggestionsAdapter =
|
||||||
val suggestionsAdapter =
|
SearchSuggestionsAdapter(
|
||||||
SearchSuggestionsAdapter(
|
response
|
||||||
response,
|
|
||||||
binding.autoCompleteTextView,
|
|
||||||
this@SearchFragment
|
|
||||||
)
|
|
||||||
binding.historyRecycler.adapter = suggestionsAdapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isFetchingSearch) run()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fetchSearch(query: String) {
|
|
||||||
runOnUiThread {
|
|
||||||
binding.historyRecycler.visibility = GONE
|
|
||||||
}
|
|
||||||
lifecycleScope.launchWhenCreated {
|
|
||||||
isFetchingSearch = true
|
|
||||||
view?.let { context?.hideKeyboard(it) }
|
|
||||||
val response = try {
|
|
||||||
RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
println(e)
|
|
||||||
Log.e(TAG, "IOException, you might not have internet connection $e")
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG, "HttpException, unexpected response")
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
nextPage = response.nextpage
|
|
||||||
if (response.items!!.isNotEmpty()) {
|
|
||||||
runOnUiThread {
|
|
||||||
binding.searchRecycler.visibility = VISIBLE
|
|
||||||
searchAdapter = SearchAdapter(response.items, childFragmentManager)
|
|
||||||
binding.searchRecycler.adapter = searchAdapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addToHistory(query)
|
|
||||||
isLoading = false
|
|
||||||
isFetchingSearch = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchNextSearchItems(query: String) {
|
|
||||||
lifecycleScope.launchWhenCreated {
|
|
||||||
if (!isLoading) {
|
|
||||||
isLoading = true
|
|
||||||
val response = try {
|
|
||||||
RetrofitInstance.api.getSearchResultsNextPage(
|
|
||||||
query,
|
|
||||||
apiSearchFilter,
|
|
||||||
nextPage!!
|
|
||||||
)
|
)
|
||||||
} catch (e: IOException) {
|
runOnUiThread {
|
||||||
println(e)
|
binding.suggestionsRecycler.layoutManager = LinearLayoutManager(requireContext())
|
||||||
Log.e(TAG, "IOException, you might not have internet connection")
|
binding.suggestionsRecycler.adapter = suggestionsAdapter
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG, "HttpException, unexpected response," + e.response())
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
}
|
||||||
nextPage = response.nextpage
|
|
||||||
searchAdapter?.updateItems(response.items!!)
|
|
||||||
isLoading = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Fragment?.runOnUiThread(action: () -> Unit) {
|
private fun Fragment?.runOnUiThread(action: () -> Unit) {
|
||||||
@ -244,35 +76,9 @@ class SearchFragment : Fragment() {
|
|||||||
activity?.runOnUiThread(action)
|
activity?.runOnUiThread(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onDestroy() {
|
||||||
super.onResume()
|
// remove the backstack entries
|
||||||
requireActivity().window.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
findNavController().popBackStack(R.id.searchFragment, true)
|
||||||
}
|
super.onDestroy()
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
view?.let { context?.hideKeyboard(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showHistory() {
|
|
||||||
binding.searchRecycler.visibility = GONE
|
|
||||||
val historyList = PreferenceHelper.getSearchHistory()
|
|
||||||
if (historyList.isNotEmpty()) {
|
|
||||||
binding.historyRecycler.adapter =
|
|
||||||
SearchHistoryAdapter(
|
|
||||||
historyList,
|
|
||||||
binding.autoCompleteTextView,
|
|
||||||
this
|
|
||||||
)
|
|
||||||
binding.historyRecycler.visibility = VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addToHistory(query: String) {
|
|
||||||
val searchHistoryEnabled =
|
|
||||||
PreferenceHelper.getBoolean(PreferenceKeys.SEARCH_HISTORY_TOGGLE, true)
|
|
||||||
if (searchHistoryEnabled && query != "") {
|
|
||||||
PreferenceHelper.saveToSearchHistory(query)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.github.libretube.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.adapters.SearchAdapter
|
||||||
|
import com.github.libretube.databinding.FragmentSearchResultBinding
|
||||||
|
import com.github.libretube.util.RetrofitInstance
|
||||||
|
import com.github.libretube.util.hideKeyboard
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class SearchResultFragment : Fragment() {
|
||||||
|
private val TAG = "SearchResultFragment"
|
||||||
|
private lateinit var binding: FragmentSearchResultBinding
|
||||||
|
|
||||||
|
private lateinit var nextPage: String
|
||||||
|
private var query: String = ""
|
||||||
|
|
||||||
|
private var apiSearchFilter: String = "all"
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
query = arguments?.getString("query").toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentSearchResultBinding.inflate(layoutInflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.filterMenuImageView.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)
|
||||||
|
)
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(getString(R.string.choose_filter))
|
||||||
|
.setItems(filterOptions) { _, id ->
|
||||||
|
apiSearchFilter = when (id) {
|
||||||
|
0 -> "all"
|
||||||
|
1 -> "videos"
|
||||||
|
2 -> "channels"
|
||||||
|
3 -> "playlists"
|
||||||
|
4 -> "music_songs"
|
||||||
|
5 -> "music_videos"
|
||||||
|
6 -> "music_albums"
|
||||||
|
7 -> "music_playlists"
|
||||||
|
else -> "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(getString(R.string.cancel), null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
fetchSearch()
|
||||||
|
|
||||||
|
binding.searchRecycler.viewTreeObserver
|
||||||
|
.addOnScrollChangedListener {
|
||||||
|
if (!binding.searchRecycler.canScrollVertically(1)) {
|
||||||
|
fetchNextSearchItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchSearch() {
|
||||||
|
lifecycleScope.launchWhenCreated {
|
||||||
|
view?.let { context?.hideKeyboard(it) }
|
||||||
|
val response = try {
|
||||||
|
RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
println(e)
|
||||||
|
Log.e(TAG, "IOException, you might not have internet connection $e")
|
||||||
|
return@launchWhenCreated
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
Log.e(TAG, "HttpException, unexpected response")
|
||||||
|
return@launchWhenCreated
|
||||||
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
if (response.items?.isNotEmpty() == true) {
|
||||||
|
binding.searchRecycler.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
binding.searchRecycler.adapter = SearchAdapter(response.items, childFragmentManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextPage = response.nextpage!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchNextSearchItems() {
|
||||||
|
lifecycleScope.launchWhenCreated {
|
||||||
|
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!!
|
||||||
|
with(binding.searchRecycler.adapter as SearchAdapter) {
|
||||||
|
if (response.items?.isNotEmpty() == true) this.updateItems(response.items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Fragment?.runOnUiThread(action: () -> Unit) {
|
||||||
|
this ?: return
|
||||||
|
if (!isAdded) return // Fragment not attached to an Activity
|
||||||
|
activity?.runOnUiThread(action)
|
||||||
|
}
|
||||||
|
}
|
@ -1,101 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".fragments.SearchFragment">
|
tools:context=".fragments.SearchFragment">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/searchbar_holder"
|
android:id="@+id/suggestions_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:layout_marginVertical="10dp" />
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
</FrameLayout>
|
||||||
android:id="@+id/outlinedTextField"
|
|
||||||
style="@style/Widget.Material3.CardView.Filled"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
app:cardCornerRadius="24dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginEnd="30dp"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
app:hintEnabled="false">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/autoCompleteTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:hint="@string/search_hint"
|
|
||||||
android:imeOptions="actionSearch"
|
|
||||||
android:inputType="textFilter|textNoSuggestions"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:padding="12dp" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/clearSearch_imageView"
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:src="@drawable/ic_close" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/filterMenu_imageView"
|
|
||||||
android:layout_width="30dp"
|
|
||||||
android:layout_height="30dp"
|
|
||||||
android:layout_marginTop="25dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:src="@drawable/ic_filter" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/history_recycler"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginVertical="10dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/searchbar_holder" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/search_recycler"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_margin="10dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/searchbar_holder" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
23
app/src/main/res/layout/fragment_search_result.xml
Normal file
23
app/src/main/res/layout/fragment_search_result.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".fragments.SearchFragment">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/filterMenu_imageView"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:src="@drawable/ic_filter" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/search_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -6,7 +6,8 @@
|
|||||||
android:id="@+id/action_search"
|
android:id="@+id/action_search"
|
||||||
android:icon="@drawable/ic_search"
|
android:icon="@drawable/ic_search"
|
||||||
android:title="@string/search_hint"
|
android:title="@string/search_hint"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom"
|
||||||
|
app:actionViewClass="androidx.appcompat.widget.SearchView" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_settings"
|
||||||
|
@ -19,12 +19,17 @@
|
|||||||
android:id="@+id/libraryFragment"
|
android:id="@+id/libraryFragment"
|
||||||
android:name="com.github.libretube.fragments.LibraryFragment"
|
android:name="com.github.libretube.fragments.LibraryFragment"
|
||||||
android:label="fragment_library"
|
android:label="fragment_library"
|
||||||
tools:layout="@layout/fragment_library"></fragment>
|
tools:layout="@layout/fragment_library" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/searchFragment"
|
android:id="@+id/searchFragment"
|
||||||
android:name="com.github.libretube.fragments.SearchFragment"
|
android:name="com.github.libretube.fragments.SearchFragment"
|
||||||
android:label="fragment_search"
|
android:label="fragment_search"
|
||||||
tools:layout="@layout/fragment_search" />
|
tools:layout="@layout/fragment_search" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/searchResultFragment"
|
||||||
|
android:name="com.github.libretube.fragments.SearchResultFragment"
|
||||||
|
android:label="fragment_search"
|
||||||
|
tools:layout="@layout/fragment_search_result" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/channelFragment"
|
android:id="@+id/channelFragment"
|
||||||
android:name="com.github.libretube.fragments.ChannelFragment"
|
android:name="com.github.libretube.fragments.ChannelFragment"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user