ytdlnis/app/src/main/java/com/deniscerri/ytdl/ui/HomeFragment.kt
2024-08-25 19:51:39 +02:00

1013 lines
No EOL
44 KiB
Kotlin

package com.deniscerri.ytdl.ui
import android.annotation.SuppressLint
import android.app.Activity
import android.content.*
import android.content.Context.CLIPBOARD_SERVICE
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.Patterns
import android.view.*
import android.view.View.*
import android.widget.*
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.ResultItem
import com.deniscerri.ytdl.database.models.SearchSuggestionItem
import com.deniscerri.ytdl.database.models.SearchSuggestionType
import com.deniscerri.ytdl.database.repository.ResultRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.ui.adapter.HomeAdapter
import com.deniscerri.ytdl.ui.adapter.SearchSuggestionsAdapter
import com.deniscerri.ytdl.ui.downloads.HistoryFragment
import com.deniscerri.ytdl.ui.downloads.HistoryFragment.Companion
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.UiUtil
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
import kotlin.collections.ArrayList
class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggestionsAdapter.OnItemClickListener, OnClickListener {
private var inputQueries: MutableList<String>? = null
private var homeAdapter: HomeAdapter? = null
private var searchSuggestionsAdapter: SearchSuggestionsAdapter? = null
private var queriesConstraint: ConstraintLayout? = null
private var downloadSelectedFab: ExtendedFloatingActionButton? = null
private var downloadAllFab: ExtendedFloatingActionButton? = null
private var clipboardFab: ExtendedFloatingActionButton? = null
private var homeFabs: LinearLayout? = null
private var notificationUtil: NotificationUtil? = null
private var downloadQueue: ArrayList<ResultItem>? = null
private lateinit var playlistNameFilterScrollView: HorizontalScrollView
private lateinit var playlistNameFilterChipGroup: ChipGroup
private lateinit var resultViewModel : ResultViewModel
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var historyViewModel : HistoryViewModel
private var fragmentView: View? = null
private var activity: Activity? = null
private var mainActivity: MainActivity? = null
private var fragmentContext: Context? = null
private var layoutinflater: LayoutInflater? = null
private var shimmerCards: ShimmerFrameLayout? = null
private var searchBar: SearchBar? = null
private var searchView: SearchView? = null
private var providersChipGroup: ChipGroup? = null
private var chipGroupDivider: View? = null
private var queriesChipGroup: ChipGroup? = null
private var recyclerView: RecyclerView? = null
private var searchSuggestionsRecyclerView: RecyclerView? = null
private var uiHandler: Handler? = null
private var resultsList: List<ResultItem?>? = null
private lateinit var selectedObjects: ArrayList<ResultItem>
private var quickLaunchSheet = false
private var sharedPreferences: SharedPreferences? = null
private var actionMode: ActionMode? = null
private var appBarLayout: AppBarLayout? = null
private var materialToolbar: MaterialToolbar? = null
private var loadingItems: Boolean = false
private var queryList = mutableListOf<String>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentView = inflater.inflate(R.layout.fragment_home, container, false)
activity = getActivity()
mainActivity = activity as MainActivity?
quickLaunchSheet = false
notificationUtil = NotificationUtil(requireContext())
selectedObjects = arrayListOf()
return fragmentView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fragmentContext = context
layoutinflater = LayoutInflater.from(context)
uiHandler = Handler(Looper.getMainLooper())
selectedObjects = ArrayList()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
downloadQueue = ArrayList()
resultsList = mutableListOf()
selectedObjects = ArrayList()
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
//initViews
searchBar = view.findViewById(R.id.search_bar)
searchView = view.findViewById(R.id.search_view)
appBarLayout = view.findViewById(R.id.home_appbarlayout)
materialToolbar = view.findViewById(R.id.home_toolbar)
queriesChipGroup = view.findViewById(R.id.queries)
queriesConstraint = view.findViewById(R.id.queries_constraint)
homeFabs = view.findViewById(R.id.home_fabs)
downloadSelectedFab = homeFabs!!.findViewById(R.id.download_selected_fab)
downloadAllFab = homeFabs!!.findViewById(R.id.download_all_fab)
clipboardFab = homeFabs!!.findViewById(R.id.copied_url_fab)
playlistNameFilterScrollView = view.findViewById(R.id.playlist_selection_chips_scrollview)
playlistNameFilterChipGroup = view.findViewById(R.id.playlist_selection_chips)
runCatching { materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) }
homeAdapter =
HomeAdapter(
this,
requireActivity()
)
recyclerView = view.findViewById(R.id.recyclerViewHome)
recyclerView?.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
recyclerView?.adapter = homeAdapter
recyclerView?.enableFastScroll()
shimmerCards = view.findViewById(R.id.shimmer_results_framelayout)
searchSuggestionsAdapter = SearchSuggestionsAdapter(
this,
requireActivity()
)
searchSuggestionsRecyclerView = view.findViewById(R.id.search_suggestions_recycler)
searchSuggestionsRecyclerView?.layoutManager = LinearLayoutManager(context)
searchSuggestionsRecyclerView?.adapter = searchSuggestionsAdapter
searchSuggestionsRecyclerView?.itemAnimator = null
val progressBar = view.findViewById<View>(R.id.progress)
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
resultViewModel.getFilteredList().observe(requireActivity()) {
kotlin.runCatching {
homeAdapter!!.submitList(it)
resultsList = it
progressBar.isVisible = loadingItems && resultsList!!.isNotEmpty()
if(resultViewModel.repository.itemCount.value > 1 || resultViewModel.repository.itemCount.value == -1){
if (it.size > 1 && it[0].playlistTitle.isNotEmpty() && !loadingItems){
downloadAllFab!!.visibility = VISIBLE
}else{
downloadAllFab!!.visibility = GONE
}
}else if (resultViewModel.repository.itemCount.value == 1){
if (sharedPreferences!!.getBoolean("download_card", true)){
if(it.size == 1 && quickLaunchSheet && parentFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
showSingleDownloadSheet(
it[0],
DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
)
}
}
}else{
downloadAllFab!!.visibility = GONE
}
quickLaunchSheet = true
}
}
resultViewModel.items.observe(requireActivity()) {
updateMultiplePlaylistResults(it
.filter { it2 -> it2.playlistTitle != "" && it2.playlistTitle != "YTDLNIS_SEARCH" }
.map { it.playlistTitle }
.distinct()
)
}
initMenu()
downloadSelectedFab?.tag = "downloadSelected"
downloadSelectedFab?.setOnClickListener(this)
downloadAllFab?.tag = "downloadAll"
downloadAllFab?.setOnClickListener(this)
if (arguments?.getString("url") != null){
val url = requireArguments().getString("url")
if (inputQueries == null) inputQueries = mutableListOf()
searchBar?.setText(url)
val argList = url!!.split("\n").toMutableList()
argList.removeAll(listOf("", null))
inputQueries!!.addAll(argList)
}
if (inputQueries != null) {
lifecycleScope.launch(Dispatchers.IO){
resultViewModel.deleteAll()
resultViewModel.parseQueries(inputQueries!!){}
inputQueries = null
}
}
searchView?.addTransitionListener { _, _, newState ->
if (newState == SearchView.TransitionState.SHOWING){
mainActivity?.hideBottomNavigation()
}else if (newState == SearchView.TransitionState.HIDING){
mainActivity?.showBottomNavigation()
}
}
mainActivity?.onBackPressedDispatcher?.addCallback(this) {
if (searchView?.isShowing == true) {
searchView?.hide()
}else{
mainActivity?.finishAffinity()
}
}
lifecycleScope.launch {
launch{
resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){
val isSingleQueryAndURL = queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()
kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, continueAnyway = isSingleQueryAndURL, continued = {
lifecycleScope.launch {
if (sharedPreferences!!.getBoolean("download_card", true)) {
withContext(Dispatchers.Main){
showSingleDownloadSheet(
resultItem = downloadViewModel.createEmptyResultItem(queryList.first()),
type = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!),
disableUpdateData = true
)
}
} else {
val downloadItem = downloadViewModel.createDownloadItemFromResult(
result = downloadViewModel.createEmptyResultItem(queryList.first()),
givenType = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
)
downloadViewModel.queueDownloads(listOf(downloadItem))
}
}
}, closed = {}) }
resultViewModel.uiState.update {it.copy(errorMessage = null) }
}
loadingItems = res.processing
progressBar.isVisible = loadingItems && resultsList!!.isNotEmpty()
if (res.processing){
recyclerView?.setPadding(0,0,0,0)
shimmerCards!!.startShimmer()
shimmerCards!!.visibility = VISIBLE
}else{
recyclerView?.setPadding(0,0,0,100)
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
downloadAllFab!!.visibility = VISIBLE
}else{
downloadAllFab!!.visibility = GONE
}
}
}
}
}
lifecycleScope.launch {
launch{
downloadViewModel.alreadyExistsUiState.collectLatest { res ->
if (res.isNotEmpty()){
withContext(Dispatchers.Main){
val bundle = bundleOf(
Pair("duplicates", ArrayList(res))
)
delay(500)
findNavController().navigate(R.id.downloadsAlreadyExistDialog, bundle)
}
downloadViewModel.alreadyExistsUiState.value = mutableListOf()
}
}
}
}
}
override fun onResume() {
super.onResume()
if(arguments?.getString("url") == null){
if (!resultViewModel.uiState.value.processing){
resultViewModel.checkTrending()
}
}else{
arguments?.remove("url")
}
if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
notificationUtil?.cancelDownloadNotification(NotificationUtil.FORMAT_UPDATING_FINISHED_NOTIFICATION_ID)
arguments?.remove("showDownloadsWithUpdatedFormats")
CoroutineScope(Dispatchers.IO).launch {
val ids = arguments?.getLongArray("downloadIds") ?: return@launch
downloadViewModel.turnDownloadItemsToProcessingDownloads(ids.toList(), deleteExisting = true)
withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
}
}
}
if(arguments?.getBoolean("search") == true){
arguments?.remove("search")
requireView().post {
searchBar?.performClick()
}
}
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
updateSearchViewItems(searchView?.editText?.text.toString())
}
requireView().post {
checkClipboard().apply {
this?.apply {
clipboardFab?.isVisible = this.isNotEmpty()
clipboardFab?.setOnClickListener {
if (this.size == 1){
searchView?.setText(this.first())
clipboardFab?.isVisible = false
initSearch(searchView!!)
}else{
searchBar?.performClick()
lifecycleScope.launch {
withContext(Dispatchers.IO){
delay(500)
}
this@apply.forEach {
onSearchSuggestionAdd(it)
}
}
}
}
}
lifecycleScope.launch {
clipboardFab?.extend()
delay(1000)
clipboardFab?.shrink()
}
}
}
}
@SuppressLint("ClickableViewAccessibility")
private fun initMenu() {
val queriesConstraint = requireView().findViewById<ConstraintLayout>(R.id.queries_constraint)
val queriesInitStartBtn = queriesConstraint.findViewById<MaterialButton>(R.id.init_search_query)
val isRightToLeft = resources.getBoolean(R.bool.is_right_to_left)
providersChipGroup = searchView!!.findViewById(R.id.providers)
val providers = resources.getStringArray(R.array.search_engines)
val providersValues = resources.getStringArray(R.array.search_engines_values).toMutableList()
for(i in providersValues.indices){
val provider = providers[i]
val providerValue = providersValues[i]
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, providersChipGroup, false) as Chip
tmp.text = provider
tmp.id = i
tmp.tag = providersValues[i]
tmp.setOnClickListener {
val editor = sharedPreferences?.edit()
editor?.putString("search_engine", providerValue)
editor?.apply()
}
providersChipGroup?.addView(tmp)
}
chipGroupDivider = requireView().findViewById(R.id.chipGroupDivider)
searchView!!.addTransitionListener { _, _, newState ->
if (newState == SearchView.TransitionState.SHOWN) {
val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch")
providersChipGroup?.children?.forEach {
val tmp = providersChipGroup?.findViewById<Chip>(it.id)
if (tmp?.tag?.equals(currentProvider) == true) {
tmp.isChecked = true
return@forEach
}
}
if (Patterns.WEB_URL.matcher(searchBar!!.text.toString()).matches() && searchBar!!.text.isNotBlank()){
providersChipGroup?.visibility = GONE
chipGroupDivider?.visibility = GONE
}else{
providersChipGroup?.visibility = VISIBLE
chipGroupDivider?.visibility = VISIBLE
}
updateSearchViewItems(searchView!!.editText.text.toString())
}
}
searchView!!.editText.doAfterTextChanged {
if (searchView!!.currentTransitionState != SearchView.TransitionState.SHOWN) return@doAfterTextChanged
updateSearchViewItems(it.toString())
}
searchView!!.editText.setOnTouchListener(OnTouchListener { _, event ->
try{
val drawableLeft = 0
val drawableRight = 2
if (event.action == MotionEvent.ACTION_UP) {
if (
(isRightToLeft && (event.x < (searchView!!.editText.left - searchView!!.editText.compoundDrawables[drawableLeft].bounds.width()))) ||
(!isRightToLeft && (event.x > (searchView!!.editText.right - searchView!!.editText.compoundDrawables[drawableRight].bounds.width())))
){
val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == searchView!!.editText.text.toString() }
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = searchView!!.editText.text
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
queriesChipGroup!!.removeView(chip)
if (queriesChipGroup!!.childCount == 0) queriesConstraint.visibility = GONE
}
queriesChipGroup!!.addView(chip)
}
if (queriesChipGroup!!.childCount == 0) queriesConstraint.visibility = GONE
else queriesConstraint.visibility = VISIBLE
searchView!!.editText.setText("")
return@OnTouchListener true
}
}
}catch (ignored: Exception){}
false
})
searchView!!.editText.setOnEditorActionListener { _, _, _ ->
initSearch(searchView!!)
true
}
searchBar!!.setOnMenuItemClickListener { m: MenuItem ->
when (m.itemId) {
R.id.delete_results -> {
resultsList = listOf()
lifecycleScope.launch {
withContext(Dispatchers.IO){
resultViewModel.cancelParsingQueries()
}
}
resultViewModel.getTrending()
selectedObjects = ArrayList()
searchBar!!.setText("")
downloadAllFab!!.visibility = GONE
downloadSelectedFab!!.visibility = GONE
}
R.id.delete_search -> {
resultViewModel.deleteAllSearchQueryHistory()
searchSuggestionsAdapter?.submitList(listOf())
}
}
true
}
queriesChipGroup!!.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
if (queriesChipGroup!!.childCount == 0) queriesConstraint.visibility = GONE
else queriesConstraint.visibility = VISIBLE
}
queriesInitStartBtn.setOnClickListener {
initSearch(searchView!!)
}
}
@SuppressLint("InflateParams")
private fun updateSearchViewItems(searchQuery: String) = lifecycleScope.launch(Dispatchers.Main) {
lifecycleScope.launch {
if (searchView!!.editText.text.isEmpty()){
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
}else{
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
}
val combinedList = mutableListOf<SearchSuggestionItem>()
val history = withContext(Dispatchers.IO){
resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery, ignoreCase = true) }
}.map {
SearchSuggestionItem(it, SearchSuggestionType.HISTORY)
}
val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){
withContext(Dispatchers.IO){
resultViewModel.getSearchSuggestions(searchQuery)
}
}else{
emptyList()
}.map {
SearchSuggestionItem(it, SearchSuggestionType.SUGGESTION)
}
combinedList.addAll(history)
combinedList.addAll(suggestions)
val url = checkClipboard()
url?.apply {
var alreadyHasThem = this.all { queriesChipGroup?.children?.any { c -> (c as Chip).text.contains(it) } == true }
if (this.isNotEmpty() && !alreadyHasThem){
combinedList.add(0, SearchSuggestionItem(this.joinToString("\n"), SearchSuggestionType.CLIPBOARD))
}
}
searchSuggestionsAdapter?.submitList(combinedList)
// history.forEach { s ->
// val v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null)
// val textView = v.findViewById<TextView>(R.id.suggestion_text)
// textView.text = s
// textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
// Handler(Looper.getMainLooper()).post {
// searchHistoryLinearLayout!!.addView(
// v
// )
// }
// textView.setOnClickListener {
// searchView!!.setText(s)
// initSearch(searchView!!)
// }
// textView.setOnLongClickListener {
// val deleteDialog = MaterialAlertDialogBuilder(requireContext())
// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + s + "\"!")
// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
// searchHistoryLinearLayout!!.removeView(v)
// resultViewModel.removeSearchQueryFromHistory(s)
// }
// deleteDialog.show()
// true
// }
//
// val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
// mb.setOnClickListener {
// searchView!!.editText.setText(s)
// searchView!!.editText.setSelection(searchView!!.editText.length())
// }
// }
// searchHistoryLinearLayout!!.isVisible = history.isNotEmpty()
// if (linkYouCopied.findViewById<TextView>(R.id.suggestion_text).text.isNotEmpty()){
// linkYouCopied.visibility = VISIBLE
// clipboardFab?.isVisible = true
// }
//
// suggestions.forEach { s ->
// val v = LayoutInflater.from(fragmentContext)
// .inflate(R.layout.search_suggestion_item, null)
// val textView = v.findViewById<TextView>(R.id.suggestion_text)
// textView.text = s
// Handler(Looper.getMainLooper()).post {
// searchSuggestionsLinearLayout!!.addView(
// v
// )
// }
// textView.setOnClickListener {
// searchView!!.setText(s)
// initSearch(searchView!!)
// }
// val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
// mb.setOnClickListener {
// searchView!!.editText.setText(s)
// searchView!!.editText.setSelection(searchView!!.editText.length())
// }
// }
// searchSuggestionsLinearLayout!!.isVisible = suggestions.isNotEmpty()
if (Patterns.WEB_URL.matcher(searchView!!.editText.text).matches()){
providersChipGroup?.visibility = GONE
chipGroupDivider?.visibility = GONE
}else{
providersChipGroup?.visibility = VISIBLE
chipGroupDivider?.visibility = VISIBLE
}
}
}
private fun initSearch(searchView: SearchView){
queryList = mutableListOf()
if (queriesChipGroup!!.childCount > 0){
queriesChipGroup!!.children.forEach {
val query = (it as Chip).text.toString().trim {it2 -> it2 <= ' '}
if (query.isNotEmpty()){
queryList.add(query)
}
}
queriesChipGroup!!.removeAllViews()
}
if (searchView.editText.text.isNotBlank()) {
queryList.add(searchView.editText.text.toString())
}
if (queryList.isEmpty()) return
if (queryList.size == 1){
searchBar!!.setText(searchView.text)
}
searchView.hide()
if(!sharedPreferences!!.getBoolean("incognito", false)){
queryList.forEach { q ->
resultViewModel.addSearchQueryToHistory(q)
}
}
lifecycleScope.launch(Dispatchers.IO){
resultViewModel.deleteAll()
if(sharedPreferences!!.getBoolean("quick_download", false) || sharedPreferences!!.getString("preferred_download_type", "video") == "command"){
if (queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()){
if (sharedPreferences!!.getBoolean("download_card", true)) {
withContext(Dispatchers.Main){
showSingleDownloadSheet(
resultItem = downloadViewModel.createEmptyResultItem(queryList.first()),
type = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
)
}
} else {
val downloadItem = downloadViewModel.createDownloadItemFromResult(
result = downloadViewModel.createEmptyResultItem(queryList.first()),
givenType = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
)
downloadViewModel.queueDownloads(listOf(downloadItem))
}
}else{
resultViewModel.parseQueries(queryList){}
}
}else{
resultViewModel.parseQueries(queryList){}
}
}
}
fun scrollToTop() {
recyclerView!!.scrollToPosition(0)
runCatching { (searchBar!!.parent as AppBarLayout).setExpanded(true, true) }
}
@SuppressLint("ResourceType")
override fun onButtonClick(videoURL: String, type: DownloadViewModel.Type?) {
Log.e(TAG, type.toString() + " " + videoURL)
val item = resultsList!!.find { it?.url == videoURL }
Log.e(TAG, resultsList!![0].toString() + " " + videoURL)
recyclerView!!.findViewWithTag<MaterialButton>("""${item?.url}##$type""")
if (sharedPreferences!!.getBoolean("download_card", true)) {
showSingleDownloadSheet(item!!, type!!)
} else {
lifecycleScope.launch{
val downloadItem = withContext(Dispatchers.IO){
downloadViewModel.createDownloadItemFromResult(
result = item!!,
givenType = type!!)
}
downloadViewModel.queueDownloads(listOf(downloadItem))
}
}
}
override fun onLongButtonClick(videoURL: String, type: DownloadViewModel.Type?) {
Log.e(TAG, type.toString() + " " + videoURL)
val item = resultsList!!.find { it?.url == videoURL }
showSingleDownloadSheet(item!!, type!!)
}
@SuppressLint("RestrictedApi")
private fun showSingleDownloadSheet(
resultItem: ResultItem,
type: DownloadViewModel.Type,
disableUpdateData : Boolean = false
){
if(findNavController().currentBackStack.value.firstOrNull {it.destination.id == R.id.downloadBottomSheetDialog} == null &&
findNavController().currentDestination?.id == R.id.homeFragment
){
//show the fragment if its not in the backstack
val bundle = Bundle()
bundle.putParcelable("result", resultItem)
bundle.putSerializable("type", downloadViewModel.getDownloadType(type, resultItem.url))
if (disableUpdateData) {
bundle.putBoolean("disableUpdateData", true)
}
findNavController().navigate(R.id.downloadBottomSheetDialog, bundle)
}
}
override fun onCardClick(videoURL: String, add: Boolean) {
lifecycleScope.launch {
val item = resultsList?.find { it?.url == videoURL }
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
actionMode?.apply {
if (add) selectedObjects.add(item!!)
else selectedObjects.remove(item!!)
if (selectedObjects.size == 0){
this.finish()
}else{
actionMode?.title = "${selectedObjects.size} ${getString(R.string.selected)}"
this.menu.findItem(R.id.select_between).isVisible = false
if(selectedObjects.size == 2){
val selectedIDs = selectedObjects.sortedBy { it.id }
val resultsInMiddle = withContext(Dispatchers.IO){
resultViewModel.getResultsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
}.toMutableList()
this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty()
}
}
}
}
}
override fun onCardDetailsClick(videoURL: String) {
if (parentFragmentManager.findFragmentByTag("resultDetails") == null && resultsList != null && resultsList!!.isNotEmpty()){
val bundle = Bundle()
bundle.putParcelable("result", resultsList!!.first{it!!.url == videoURL}!!)
findNavController().navigate(R.id.resultCardDetailsDialog, bundle)
}
}
override fun onClick(v: View) {
val viewIdName: String = try {
v.tag.toString()
} catch (e: Exception) {""}
if (viewIdName.isNotEmpty()) {
if (viewIdName == "downloadAll") {
val showDownloadCard = sharedPreferences!!.getBoolean("download_card", true)
downloadViewModel.turnResultItemsToProcessingDownloads(resultsList!!.map { it!!.id }, downloadNow = !showDownloadCard)
if (showDownloadCard){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
}
}
}
}
private val contextualActionBar = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
mode!!.menuInflater.inflate(R.menu.main_menu_context, menu)
mode.title = "${selectedObjects.size} ${getString(R.string.selected)}"
searchBar!!.isEnabled = false
playlistNameFilterChipGroup.children.forEach { it.isEnabled = false }
searchBar!!.menu.forEach { it.isEnabled = false }
(activity as MainActivity).disableBottomNavigation()
return true
}
override fun onPrepareActionMode(
mode: ActionMode?,
menu: Menu?
): Boolean {
return false
}
override fun onActionItemClicked(
mode: ActionMode?,
item: MenuItem?
): Boolean {
return when (item!!.itemId) {
R.id.select_between -> {
lifecycleScope.launch {
val selectedIDs = selectedObjects.sortedBy { it.id }
val resultsInMiddle = withContext(Dispatchers.IO){
resultViewModel.getResultsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
}.toMutableList()
if (resultsInMiddle.isNotEmpty()){
selectedObjects.addAll(resultsInMiddle)
homeAdapter?.checkMultipleItems(selectedObjects.map { it.url })
actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}"
}
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
}
true
}
R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
if (selectedObjects.size == resultsList?.size){
lifecycleScope.launch(Dispatchers.IO){
resultViewModel.deleteAll()
}
}else{
resultViewModel.deleteSelected(selectedObjects.toList())
}
clearCheckedItems()
actionMode?.finish()
}
deleteDialog.show()
true
}
R.id.download -> {
lifecycleScope.launch {
val showDownloadCard = sharedPreferences!!.getBoolean("download_card", true)
if (showDownloadCard && selectedObjects.size == 1) {
showSingleDownloadSheet(
selectedObjects[0],
downloadViewModel.getDownloadType(url = selectedObjects[0].url)
)
}else{
downloadViewModel.turnResultItemsToProcessingDownloads(selectedObjects.map { it.id }, downloadNow = !showDownloadCard)
if (showDownloadCard){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
}
}
clearCheckedItems()
actionMode?.finish()
}
true
}
R.id.select_all -> {
homeAdapter?.checkAll(resultsList)
selectedObjects.clear()
resultsList?.forEach { selectedObjects.add(it!!) }
mode?.title = "(${selectedObjects.size}) ${resources.getString(R.string.all_items_selected)}"
true
}
R.id.invert_selected -> {
homeAdapter?.invertSelected(resultsList)
val invertedList = arrayListOf<ResultItem>()
resultsList?.forEach {
if (!selectedObjects.contains(it)) invertedList.add(it!!)
}
selectedObjects.clear()
selectedObjects.addAll(invertedList)
actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
if (invertedList.isEmpty()) actionMode?.finish()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
actionMode = null
(activity as MainActivity).enableBottomNavigation()
clearCheckedItems()
searchBar!!.isEnabled = true
playlistNameFilterChipGroup.children.forEach { it.isEnabled = true }
searchBar!!.menu.forEach { it.isEnabled = true }
searchBar?.expand(appBarLayout!!)
}
}
private fun clearCheckedItems(){
homeAdapter?.clearCheckedItems()
selectedObjects.forEach {
homeAdapter?.notifyItemChanged(resultsList!!.indexOf(it))
}
selectedObjects.clear()
}
private fun checkClipboard(): List<String>?{
return kotlin.runCatching {
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = clipboard.primaryClip!!.getItemAt(0).text
return clip.split("\r","\n").map { it.trim() }.filter { Patterns.WEB_URL.matcher(it).matches() }
}.getOrNull()
}
override fun onStop() {
actionMode?.finish()
super.onStop()
}
companion object {
private const val TAG = "HomeFragment"
}
override fun onSearchSuggestionClick(text: String) {
val res = text.split("\n")
if (res.size == 1){
clipboardFab?.isVisible = false
searchView!!.setText(text)
initSearch(searchView!!)
}else{
res.forEach {
onSearchSuggestionAdd(it)
}
}
}
override fun onSearchSuggestionAdd(text: String) {
val items = text.split("\n")
items.forEach {t ->
val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == t }
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = t
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE
queriesChipGroup!!.removeView(chip)
}
queriesChipGroup!!.addView(chip)
}
}
searchView!!.editText.setText("")
queriesConstraint?.isVisible = queriesChipGroup?.childCount!! > 0
searchSuggestionsAdapter?.getList()?.apply {
if (this.isNotEmpty()) {
if (this.first().type == SearchSuggestionType.CLIPBOARD){
val newList = this.toMutableList().drop(1)
searchSuggestionsAdapter?.submitList(newList)
}
}
}
}
override fun onSearchSuggestionLongClick(text: String, position: Int) {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + text + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
resultViewModel.removeSearchQueryFromHistory(text)
updateSearchViewItems(searchView!!.editText.text.toString())
}
deleteDialog.show()
}
override fun onSearchSuggestionAddToSearchBar(text: String) {
searchView!!.editText.setText(text)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
private fun updateMultiplePlaylistResults(playlistTitles: List<String>) {
if (playlistTitles.isEmpty() || playlistTitles.size == 1) {
playlistNameFilterScrollView.isVisible = false
playlistNameFilterChipGroup.children.filter { it.tag != "all" }.forEach {
playlistNameFilterChipGroup.removeView(it)
}
return
}
playlistNameFilterChipGroup.children.first().setOnClickListener {
resultViewModel.setPlaylistFilter("")
}
for (t in playlistTitles) {
val exists = playlistNameFilterChipGroup.children.any { it.tag == t }
if (exists) continue
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, playlistNameFilterChipGroup, false) as Chip
tmp.text = t
tmp.tag = t
tmp.setOnClickListener {
resultViewModel.setPlaylistFilter(t)
}
playlistNameFilterChipGroup.addView(tmp)
}
playlistNameFilterScrollView.isVisible = true
}
}