LibreTube/app/src/main/java/com/github/libretube/fragments/SearchFragment.kt
2022-07-24 11:56:53 +02:00

281 lines
10 KiB
Kotlin

package com.github.libretube.fragments
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
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.EditText
import android.widget.TextView.GONE
import android.widget.TextView.OnEditorActionListener
import android.widget.TextView.VISIBLE
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
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.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.hideKeyboard
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
import java.io.IOException
class SearchFragment : Fragment() {
private val TAG = "SearchFragment"
private lateinit var binding: FragmentSearchBinding
private var selectedFilter = 0
private var apiSearchFilter = "all"
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?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSearchBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var tempSelectedItem = 0
binding.clearSearchImageView.setOnClickListener {
binding.autoCompleteTextView.text.clear()
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))
.setSingleChoiceItems(filterOptions, selectedFilter) { _, id ->
tempSelectedItem = id
}
.setPositiveButton(
getString(R.string.okay)
) { _, _ ->
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(binding.autoCompleteTextView.text.toString())
}
.setNegativeButton(getString(R.string.cancel), null)
.create()
.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!! != "") {
binding.searchRecycler.adapter = null
binding.searchRecycler.viewTreeObserver
.addOnScrollChangedListener {
if (!binding.searchRecycler.canScrollVertically(1)) {
fetchNextSearchItems(binding.autoCompleteTextView.text.toString())
}
}
fetchSuggestions(s.toString(), binding.autoCompleteTextView)
}
}
override fun afterTextChanged(s: Editable?) {
if (s!!.isEmpty()) {
showHistory()
}
}
})
binding.autoCompleteTextView.setOnEditorActionListener(
OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
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, autoTextView: EditText) {
fun run() {
lifecycleScope.launchWhenCreated {
binding.searchRecycler.visibility = GONE
binding.historyRecycler.visibility = VISIBLE
val response = try {
RetrofitInstance.api.getSuggestions(query)
} 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")
return@launchWhenCreated
}
val suggestionsAdapter =
SearchSuggestionsAdapter(response, autoTextView, 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) {
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
}
}
}
private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
override fun onResume() {
super.onResume()
requireActivity().window.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN)
}
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)
}
}
}