Merge pull request #938 from Bnyro/master

search refactor
This commit is contained in:
Bnyro 2022-07-31 23:18:23 +02:00 committed by GitHub
commit e5a748f3f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 308 additions and 314 deletions

View File

@ -17,6 +17,7 @@ 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
@ -46,6 +47,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
lateinit var searchView: SearchView
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)
@ -133,6 +135,7 @@ class MainActivity : AppCompatActivity() {
// clear backstack if it's the start fragment // clear backstack if it's the start fragment
if (startFragmentId == it.itemId) navController.backQueue.clear() if (startFragmentId == it.itemId) navController.backQueue.clear()
// set menu item on click listeners // set menu item on click listeners
removeSearchFocus()
when (it.itemId) { when (it.itemId) {
R.id.homeFragment -> { R.id.homeFragment -> {
navController.navigate(R.id.homeFragment) navController.navigate(R.id.homeFragment)
@ -151,10 +154,37 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun removeSearchFocus() {
searchView.setQuery("", false)
searchView.clearFocus()
searchView.onActionViewCollapsed()
}
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
// stuff for the search in the topBar
val searchItem = menu.findItem(R.id.action_search)
searchView = searchItem.actionView as SearchView
searchView.setMaxWidth(Integer.MAX_VALUE)
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 {
@ -311,6 +341,9 @@ class MainActivity : AppCompatActivity() {
} }
override fun onBackPressed() { override fun onBackPressed() {
// remove focus from search
removeSearchFocus()
if (binding.mainMotionLayout.progress == 0F) { if (binding.mainMotionLayout.progress == 0F) {
try { try {
minimizePlayer() minimizePlayer()

View File

@ -2,16 +2,14 @@ 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.appcompat.widget.SearchView
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 searchView: SearchView
private val searchFragment: SearchFragment
) : ) :
RecyclerView.Adapter<SearchHistoryViewHolder>() { RecyclerView.Adapter<SearchHistoryViewHolder>() {
@ -37,8 +35,7 @@ class SearchHistoryAdapter(
} }
root.setOnClickListener { root.setOnClickListener {
editText.setText(historyQuery) searchView.setQuery(historyQuery, true)
searchFragment.fetchSearch(historyQuery)
} }
} }
} }

View File

@ -2,15 +2,13 @@ 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.appcompat.widget.SearchView
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 searchView: SearchView
private val searchFragment: SearchFragment
) : ) :
RecyclerView.Adapter<SearchSuggestionsViewHolder>() { RecyclerView.Adapter<SearchSuggestionsViewHolder>() {
@ -31,8 +29,7 @@ class SearchSuggestionsAdapter(
holder.binding.apply { holder.binding.apply {
suggestionText.text = suggestion suggestionText.text = suggestion
root.setOnClickListener { root.setOnClickListener {
editText.setText(suggestion) searchView.setQuery(suggestion, true)
searchFragment.fetchSearch(editText.text.toString())
} }
} }
} }

View File

@ -1,51 +1,34 @@
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.activities.MainActivity
import com.github.libretube.adapters.SearchHistoryAdapter 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.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys 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 +43,26 @@ 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 { // add the query to the history
binding.autoCompleteTextView.text.clear() if (query != null) addToHistory(query!!)
binding.historyRecycler.adapter = null
showHistory() binding.suggestionsRecycler.layoutManager = LinearLayoutManager(requireContext())
// fetch the search or history
if (query == null || query == "") showHistory()
else fetchSuggestions(query!!)
}
private fun addToHistory(query: String) {
val searchHistoryEnabled =
PreferenceHelper.getBoolean(PreferenceKeys.SEARCH_HISTORY_TOGGLE, true)
if (searchHistoryEnabled && query != "") {
PreferenceHelper.saveToSearchHistory(query)
} }
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 +74,28 @@ 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, (activity as MainActivity).searchView
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.adapter = suggestionsAdapter
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
} }
} }
run()
}
private fun showHistory() {
val historyList = PreferenceHelper.getSearchHistory()
if (historyList.isNotEmpty()) {
binding.suggestionsRecycler.adapter =
SearchHistoryAdapter(
historyList,
(activity as MainActivity).searchView
)
}
} }
private fun Fragment?.runOnUiThread(action: () -> Unit) { private fun Fragment?.runOnUiThread(action: () -> Unit) {
@ -244,35 +104,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)
}
} }
} }

View File

@ -0,0 +1,128 @@
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 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 lateinit var searchAdapter: SearchAdapter
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)
// filter options
binding.filterChipGroup.setOnCheckedStateChangeListener { _, _ ->
apiSearchFilter = when (
binding.filterChipGroup.checkedChipId
) {
R.id.chip_all -> "all"
R.id.chip_videos -> "videos"
R.id.chip_channels -> "channels"
R.id.chip_playlists -> "playlists"
R.id.chip_music_songs -> "music_songs"
R.id.chip_music_videos -> "music_videos"
R.id.chip_music_albums -> "music_albums"
R.id.chip_music_playlists -> "music_playlists"
else -> throw IllegalArgumentException("Filter out of range")
}
fetchSearch()
}
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())
searchAdapter = SearchAdapter(response.items, childFragmentManager)
binding.searchRecycler.adapter = searchAdapter
}
}
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!!
kotlin.runCatching {
if (response.items?.isNotEmpty() == true) {
searchAdapter.updateItems(response.items.toMutableList())
}
}
}
}
private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
}

View File

@ -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>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".fragments.SearchFragment">
<HorizontalScrollView
android:id="@+id/filter_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none"
app:layout_constraintBottom_toTopOf="@id/recycler_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.chip.ChipGroup
android:id="@+id/filter_chip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/chip_all"
style="@style/Chip"
android:text="@string/all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_videos"
style="@style/Chip"
android:text="@string/videos" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_channels"
style="@style/Chip"
android:text="@string/channels" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_playlists"
style="@style/Chip"
android:text="@string/playlists" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_music_songs"
style="@style/Chip"
android:text="@string/music_songs" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_music_videos"
style="@style/Chip"
android:text="@string/music_videos" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_music_albums"
style="@style/Chip"
android:text="@string/music_albums" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_music_playlists"
style="@style/Chip"
android:text="@string/music_playlists" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
</LinearLayout>

View File

@ -6,7 +6,9 @@
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"
android:focusableInTouchMode="true" />
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"

View File

@ -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"

View File

@ -147,4 +147,11 @@
<item name="android:textColor">@android:color/white</item> <item name="android:textColor">@android:color/white</item>
</style> </style>
<style name="Chip" parent="Widget.Material3.Chip.Suggestion">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
</resources> </resources>