Merge pull request #4818 from Bnyro/master

feat: parse YouTube links in the search bar
This commit is contained in:
Bnyro 2023-09-19 11:26:43 +02:00 committed by GitHub
commit 797a7ceec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 26 additions and 10 deletions

View File

@ -30,7 +30,8 @@ import com.github.libretube.util.TextUtils
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
class SearchAdapter( class SearchAdapter(
private val isChannelAdapter: Boolean = false private val isChannelAdapter: Boolean = false,
private val timeStamp: Long = 0,
) : ListAdapter<ContentItem, SearchViewHolder>(SearchCallback) { ) : ListAdapter<ContentItem, SearchViewHolder>(SearchCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
@ -97,7 +98,7 @@ class SearchAdapter(
ImageHelper.loadImage(item.uploaderAvatar, channelImage) ImageHelper.loadImage(item.uploaderAvatar, channelImage)
} }
root.setOnClickListener { root.setOnClickListener {
NavigationHelper.navigateVideo(root.context, item.url) NavigationHelper.navigateVideo(root.context, item.url, timestamp = timeStamp)
} }
val videoId = item.url.toID() val videoId = item.url.toID()

View File

@ -1129,7 +1129,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
*/ */
private fun handleLink(link: String) { private fun handleLink(link: String) {
// get video id if the link is a valid youtube video link // get video id if the link is a valid youtube video link
val videoId = TextUtils.getVideoIdFromUri(link) val videoId = TextUtils.getVideoIdFromUrl(link)
if (videoId.isNullOrEmpty()) { if (videoId.isNullOrEmpty()) {
// not a YouTube video link, thus handle normally // not a YouTube video link, thus handle normally
val intent = Intent(Intent.ACTION_VIEW, link.toUri()) val intent = Intent(Intent.ACTION_VIEW, link.toUri())

View File

@ -21,13 +21,18 @@ import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.obj.SearchHistoryItem import com.github.libretube.db.obj.SearchHistoryItem
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.hideKeyboard import com.github.libretube.extensions.hideKeyboard
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toastFromMainDispatcher import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.ui.adapters.SearchAdapter import com.github.libretube.ui.adapters.SearchAdapter
import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.util.TextUtils
import com.github.libretube.util.TextUtils.toTimeInSeconds
import com.github.libretube.util.deArrow import com.github.libretube.util.deArrow
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
class SearchResultFragment : Fragment() { class SearchResultFragment : Fragment() {
private var _binding: FragmentSearchResultBinding? = null private var _binding: FragmentSearchResultBinding? = null
@ -37,7 +42,7 @@ class SearchResultFragment : Fragment() {
private var query = "" private var query = ""
private lateinit var searchAdapter: SearchAdapter private lateinit var searchAdapter: SearchAdapter
private var apiSearchFilter = "all" private var searchFilter = "all"
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -63,7 +68,7 @@ class SearchResultFragment : Fragment() {
// filter options // filter options
binding.filterChipGroup.setOnCheckedStateChangeListener { _, _ -> binding.filterChipGroup.setOnCheckedStateChangeListener { _, _ ->
apiSearchFilter = when ( searchFilter = when (
binding.filterChipGroup.checkedChipId binding.filterChipGroup.checkedChipId
) { ) {
R.id.chip_all -> "all" R.id.chip_all -> "all"
@ -95,11 +100,20 @@ class SearchResultFragment : Fragment() {
_binding?.searchResultsLayout?.isGone = true _binding?.searchResultsLayout?.isGone = true
lifecycleScope.launch { lifecycleScope.launch {
var timeStamp: Long? = null
// parse search URLs from YouTube entered in the search bar
val searchQuery = query.toHttpUrlOrNull()?.let {
val videoId = TextUtils.getVideoIdFromUrl(it.toString()) ?: query
timeStamp = it.queryParameter("t")?.toTimeInSeconds()
"${ShareDialog.YOUTUBE_FRONTEND_URL}/watch?v=${videoId}"
} ?: query
repeatOnLifecycle(Lifecycle.State.CREATED) { repeatOnLifecycle(Lifecycle.State.CREATED) {
view?.let { context?.hideKeyboard(it) } view?.let { context?.hideKeyboard(it) }
val response = try { val response = try {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
RetrofitInstance.api.getSearchResults(query, apiSearchFilter).apply { RetrofitInstance.api.getSearchResults(searchQuery, searchFilter).apply {
items = items.deArrow() items = items.deArrow()
} }
} }
@ -110,9 +124,10 @@ class SearchResultFragment : Fragment() {
} }
val binding = _binding ?: return@repeatOnLifecycle val binding = _binding ?: return@repeatOnLifecycle
searchAdapter = SearchAdapter() searchAdapter = SearchAdapter(timeStamp = timeStamp ?: 0)
binding.searchRecycler.adapter = searchAdapter binding.searchRecycler.adapter = searchAdapter
searchAdapter.submitList(response.items) searchAdapter.submitList(response.items)
binding.searchResultsLayout.isVisible = true binding.searchResultsLayout.isVisible = true
binding.progress.isGone = true binding.progress.isGone = true
binding.noSearchResult.isVisible = response.items.isEmpty() binding.noSearchResult.isVisible = response.items.isEmpty()
@ -129,7 +144,7 @@ class SearchResultFragment : Fragment() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
RetrofitInstance.api.getSearchResultsNextPage( RetrofitInstance.api.getSearchResultsNextPage(
query, query,
apiSearchFilter, searchFilter,
nextPage!! nextPage!!
).apply { ).apply {
items = items.deArrow() items = items.deArrow()

View File

@ -52,10 +52,10 @@ object TextUtils {
/** /**
* Get video id if the link is a valid youtube video link * Get video id if the link is a valid youtube video link
*/ */
fun getVideoIdFromUri(link: String): String? { fun getVideoIdFromUrl(link: String): String? {
return link.toHttpUrlOrNull()?.let { return link.toHttpUrlOrNull()?.let {
when (it.host) { when (it.host) {
"www.youtube.com" -> it.queryParameter("v") "www.youtube.com", "m.youtube.com" -> it.queryParameter("v")
"youtu.be" -> it.pathSegments.lastOrNull() "youtu.be" -> it.pathSegments.lastOrNull()
else -> null else -> null
} }