mirror of
https://github.com/jellyfin/jellyfin-androidtv.git
synced 2025-08-06 15:20:34 +08:00
Drop leanback implementation of search
This commit is contained in:

committed by
Niels van Velzen

parent
2a32babc8e
commit
fa83fc1b30
@ -1,43 +0,0 @@
|
||||
package org.jellyfin.androidtv.ui.search
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.leanback.app.SearchSupportFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class LeanbackSearchFragment : SearchSupportFragment(), SearchSupportFragment.SearchResultProvider {
|
||||
private val viewModel: SearchViewModel by viewModel()
|
||||
|
||||
private val searchFragmentDelegate: SearchFragmentDelegate by inject {
|
||||
parametersOf(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setSearchResultProvider(this)
|
||||
setOnItemViewClickedListener(searchFragmentDelegate.onItemViewClickedListener)
|
||||
setOnItemViewSelectedListener(searchFragmentDelegate.onItemViewSelectedListener)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.searchResultsFlow
|
||||
.onEach { searchFragmentDelegate.showResults(it) }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
val query = arguments?.getString(SearchFragment.EXTRA_QUERY)
|
||||
if (!query.isNullOrBlank()) setSearchQuery(query, true)
|
||||
}
|
||||
|
||||
override fun getResultsAdapter() = searchFragmentDelegate.rowsAdapter
|
||||
override fun onQueryTextChange(query: String): Boolean = viewModel.searchDebounced(query)
|
||||
override fun onQueryTextSubmit(query: String): Boolean = viewModel.searchImmediately(query)
|
||||
|
||||
}
|
@ -1,39 +1,129 @@
|
||||
package org.jellyfin.androidtv.ui.search
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.speech.SpeechRecognizer
|
||||
import androidx.core.content.ContextCompat
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import org.jellyfin.androidtv.R
|
||||
import androidx.leanback.app.RowsSupportFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.jellyfin.androidtv.databinding.FragmentSearchBinding
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class SearchFragment : Fragment(R.layout.fragment_content_view) {
|
||||
class SearchFragment : Fragment() {
|
||||
companion object {
|
||||
const val EXTRA_QUERY = "query"
|
||||
}
|
||||
|
||||
private val isSpeechEnabled by lazy {
|
||||
SpeechRecognizer.isRecognitionAvailable(requireContext())
|
||||
&& ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.RECORD_AUDIO
|
||||
) != PackageManager.PERMISSION_DENIED
|
||||
private val viewModel: SearchViewModel by viewModel()
|
||||
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val searchFragmentDelegate: SearchFragmentDelegate by inject {
|
||||
parametersOf(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentSearchBinding.inflate(inflater, container, false)
|
||||
|
||||
// Determine fragment to use
|
||||
val searchFragment = when {
|
||||
isSpeechEnabled -> LeanbackSearchFragment::class.java
|
||||
else -> TextSearchFragment::class.java
|
||||
binding.searchBar.apply {
|
||||
onTextChanged { viewModel.searchDebounced(it) }
|
||||
onSubmit { viewModel.searchImmediately(it) }
|
||||
}
|
||||
|
||||
val rowsSupportFragment = RowsSupportFragment().apply {
|
||||
adapter = searchFragmentDelegate.rowsAdapter
|
||||
onItemViewClickedListener = searchFragmentDelegate.onItemViewClickedListener
|
||||
onItemViewSelectedListener = searchFragmentDelegate.onItemViewSelectedListener
|
||||
}
|
||||
|
||||
// Add fragment
|
||||
childFragmentManager.commit {
|
||||
replace(R.id.content_view, searchFragment, arguments)
|
||||
replace(binding.resultsFrame.id, rowsSupportFragment)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.searchResultsFlow
|
||||
.onEach { searchFragmentDelegate.showResults(it) }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
val query = arguments?.getString(SearchFragment.EXTRA_QUERY)
|
||||
if (!query.isNullOrBlank()) {
|
||||
binding.searchBar.setText(query)
|
||||
viewModel.searchImmediately(query)
|
||||
binding.resultsFrame.requestFocus()
|
||||
} else {
|
||||
binding.searchBar.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun EditText.onSubmit(onSubmit: (String) -> Unit) {
|
||||
setOnEditorActionListener { view, actionId, _ ->
|
||||
when (actionId) {
|
||||
EditorInfo.IME_ACTION_DONE,
|
||||
EditorInfo.IME_ACTION_SEARCH,
|
||||
EditorInfo.IME_ACTION_PREVIOUS -> {
|
||||
onSubmit(text.toString())
|
||||
|
||||
// Manually close IME to workaround focus issue with Fire TV
|
||||
context.getSystemService<InputMethodManager>()
|
||||
?.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
|
||||
// Focus on search results
|
||||
binding.resultsFrame.requestFocus()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun EditText.onTextChanged(onTextChanged: (String) -> Unit) {
|
||||
addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(
|
||||
s: Editable,
|
||||
) = onTextChanged(s.toString())
|
||||
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int,
|
||||
) = Unit
|
||||
|
||||
override fun onTextChanged(
|
||||
s: CharSequence,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int,
|
||||
) = Unit
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,125 +0,0 @@
|
||||
package org.jellyfin.androidtv.ui.search
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.leanback.app.RowsSupportFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.jellyfin.androidtv.databinding.FragmentSearchTextBinding
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class TextSearchFragment : Fragment() {
|
||||
private val viewModel: SearchViewModel by viewModel()
|
||||
|
||||
private var _binding: FragmentSearchTextBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val searchFragmentDelegate: SearchFragmentDelegate by inject {
|
||||
parametersOf(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentSearchTextBinding.inflate(inflater, container, false)
|
||||
|
||||
binding.searchBar.apply {
|
||||
onTextChanged { viewModel.searchDebounced(it) }
|
||||
onSubmit { viewModel.searchImmediately(it) }
|
||||
}
|
||||
|
||||
val rowsSupportFragment = RowsSupportFragment().apply {
|
||||
adapter = searchFragmentDelegate.rowsAdapter
|
||||
onItemViewClickedListener = searchFragmentDelegate.onItemViewClickedListener
|
||||
onItemViewSelectedListener = searchFragmentDelegate.onItemViewSelectedListener
|
||||
}
|
||||
|
||||
childFragmentManager.commit {
|
||||
replace(binding.resultsFrame.id, rowsSupportFragment)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.searchResultsFlow
|
||||
.onEach { searchFragmentDelegate.showResults(it) }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
val query = arguments?.getString(SearchFragment.EXTRA_QUERY)
|
||||
if (!query.isNullOrBlank()) {
|
||||
binding.searchBar.setText(query)
|
||||
viewModel.searchImmediately(query)
|
||||
binding.resultsFrame.requestFocus()
|
||||
} else {
|
||||
binding.searchBar.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun EditText.onSubmit(onSubmit: (String) -> Unit) {
|
||||
setOnEditorActionListener { view, actionId, _ ->
|
||||
when (actionId) {
|
||||
EditorInfo.IME_ACTION_DONE,
|
||||
EditorInfo.IME_ACTION_SEARCH,
|
||||
EditorInfo.IME_ACTION_PREVIOUS -> {
|
||||
onSubmit(text.toString())
|
||||
|
||||
// Manually close IME to workaround focus issue with Fire TV
|
||||
context.getSystemService<InputMethodManager>()
|
||||
?.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
|
||||
// Focus on search results
|
||||
binding.resultsFrame.requestFocus()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun EditText.onTextChanged(onTextChanged: (String) -> Unit) {
|
||||
addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(
|
||||
s: Editable,
|
||||
) = onTextChanged(s.toString())
|
||||
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int,
|
||||
) = Unit
|
||||
|
||||
override fun onTextChanged(
|
||||
s: CharSequence,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int,
|
||||
) = Unit
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user