added swipes for download cards

This commit is contained in:
deniscerri 2023-04-20 22:35:47 +02:00
parent b236b65446
commit a1655810f0
No known key found for this signature in database
GPG key ID: 95C43D517D830350
16 changed files with 401 additions and 33 deletions

View file

@ -179,5 +179,6 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation "com.google.android.exoplayer:exoplayer:2.18.5" implementation "com.google.android.exoplayer:exoplayer:2.18.5"
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
} }

View file

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis package com.deniscerri.ytdlnis
import android.Manifest import android.Manifest
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -82,6 +83,18 @@ class MainActivity : AppCompatActivity() {
if (isImeVisible) View.GONE else View.VISIBLE if (isImeVisible) View.GONE else View.VISIBLE
view.onApplyWindowInsets(windowInsets) view.onApplyWindowInsets(windowInsets)
} }
var sharedPreferences = getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
val startDestination = sharedPreferences.getString("start_destination", "")
val graph = navHostFragment.navController.navInflater.inflate(R.navigation.nav_graph)
when(startDestination) {
"History" -> graph.setStartDestination(R.id.historyFragment)
"More" -> graph.setStartDestination(R.id.moreFragment)
else -> graph.setStartDestination(R.id.homeFragment)
}
navHostFragment.navController.graph = graph
navigationView.setupWithNavController(navController) navigationView.setupWithNavController(navController)
navigationView.setOnItemReselectedListener { navigationView.setOnItemReselectedListener {

View file

@ -41,23 +41,25 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
} }
fun checkTrending() = viewModelScope.launch(Dispatchers.IO){ fun checkTrending() = viewModelScope.launch(Dispatchers.IO){
if (sharedPreferences.getBoolean("home_recommendations", false)){ try {
try { val item = repository.getFirstResult()
val item = repository.getFirstResult() if (
if ( item.playlistTitle == getApplication<App>().getString(R.string.trendingPlaylist)
item.playlistTitle == getApplication<App>().getString(R.string.trendingPlaylist) && item.creationTime < (System.currentTimeMillis() / 1000) - 86400
&& item.creationTime < (System.currentTimeMillis() / 1000) - 86400 ){
){
getTrending()
}
}catch (e : Exception){
e.printStackTrace()
getTrending() getTrending()
} }
}catch (e : Exception){
e.printStackTrace()
getTrending()
} }
} }
fun getTrending() = viewModelScope.launch(Dispatchers.IO){ fun getTrending() = viewModelScope.launch(Dispatchers.IO){
repository.updateTrending() if (sharedPreferences.getBoolean("home_recommendations", false)){
repository.updateTrending()
}else{
deleteAll()
}
} }
suspend fun parseQueries(inputQueries: List<String>){ suspend fun parseQueries(inputQueries: List<String>){
if (inputQueries.size == 1){ if (inputQueries.size == 1){

View file

@ -94,6 +94,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
private var sharedPreferences: SharedPreferences? = null private var sharedPreferences: SharedPreferences? = null
private var _binding : FragmentHomeBinding? = null private var _binding : FragmentHomeBinding? = null
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private var appBarLayout: AppBarLayout? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@ -126,6 +127,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
//initViews //initViews
shimmerCards = view.findViewById(R.id.shimmer_results_framelayout) shimmerCards = view.findViewById(R.id.shimmer_results_framelayout)
searchBar = view.findViewById(R.id.search_bar) searchBar = view.findViewById(R.id.search_bar)
appBarLayout = view.findViewById(R.id.home_appbarlayout)
queriesChipGroup = view.findViewById(R.id.queries) queriesChipGroup = view.findViewById(R.id.queries)
searchSuggestions = view.findViewById(R.id.search_suggestions_scroll_view) searchSuggestions = view.findViewById(R.id.search_suggestions_scroll_view)
searchSuggestionsLinearLayout = view.findViewById(R.id.search_suggestions_linear_layout) searchSuggestionsLinearLayout = view.findViewById(R.id.search_suggestions_linear_layout)
@ -620,7 +622,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
clearCheckedItems() clearCheckedItems()
searchBar!!.isEnabled = true searchBar!!.isEnabled = true
searchBar!!.menu.forEach { it.isEnabled = true } searchBar!!.menu.forEach { it.isEnabled = true }
searchBar?.expand(searchBar!!.parent as AppBarLayout) searchBar?.expand(appBarLayout!!)
} }
} }

View file

@ -5,6 +5,8 @@ import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -16,6 +18,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.get import androidx.core.view.get
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
@ -34,6 +37,9 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -91,6 +97,8 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview) recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = listAdapter recyclerView.adapter = listAdapter
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button) val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
@ -332,5 +340,70 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
} }
private var simpleCallback: ItemTouchHelper.SimpleCallback =
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.bindingAdapterPosition
when (direction) {
ItemTouchHelper.LEFT -> {
val deletedItem = items[position]
items.remove(deletedItem)
listAdapter.submitList(items.toList())
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
items.add(position, deletedItem)
listAdapter.submitList(items.toList())
}.show()
}
}
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
RecyclerViewSwipeDecorator.Builder(
requireContext(),
c,
recyclerView,
viewHolder,
dX,
dY,
actionState,
isCurrentlyActive
)
.addSwipeLeftBackgroundColor(Color.RED)
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
.addSwipeRightBackgroundColor(
MaterialColors.getColor(
requireContext(),
R.attr.colorOnSurfaceInverse, Color.TRANSPARENT
)
)
.create()
.decorate()
super.onChildDraw(
c,
recyclerView,
viewHolder!!,
dX,
dY,
actionState,
isCurrentlyActive
)
}
}
} }

View file

@ -3,8 +3,9 @@ package com.deniscerri.ytdlnis.ui.downloads
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -16,7 +17,7 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
@ -28,7 +29,10 @@ import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -39,7 +43,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var cancelledRecyclerView : RecyclerView private lateinit var cancelledRecyclerView : RecyclerView
private lateinit var cancelledDownloads : GenericDownloadAdapter private lateinit var cancelledDownloads : GenericDownloadAdapter
private lateinit var items : List<DownloadItem> private lateinit var items : MutableList<DownloadItem>
private lateinit var fileUtil: FileUtil private lateinit var fileUtil: FileUtil
private lateinit var uiUtil : UiUtil private lateinit var uiUtil : UiUtil
@ -52,7 +56,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false) fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
items = listOf() items = mutableListOf()
fileUtil = FileUtil() fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil) uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
@ -69,6 +73,8 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
cancelledRecyclerView = view.findViewById(R.id.download_recyclerview) cancelledRecyclerView = view.findViewById(R.id.download_recyclerview)
cancelledRecyclerView.adapter = cancelledDownloads cancelledRecyclerView.adapter = cancelledDownloads
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(cancelledRecyclerView)
val landScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && ! resources.getBoolean(R.bool.isTablet) val landScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && ! resources.getBoolean(R.bool.isTablet)
if (landScape){ if (landScape){
@ -77,7 +83,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
if (resources.getBoolean(R.bool.isTablet)) cancelledRecyclerView.layoutManager = GridLayoutManager(context, 3) if (resources.getBoolean(R.bool.isTablet)) cancelledRecyclerView.layoutManager = GridLayoutManager(context, 3)
downloadViewModel.cancelledDownloads.observe(viewLifecycleOwner) { downloadViewModel.cancelledDownloads.observe(viewLifecycleOwner) {
items = it items = it.toMutableList()
cancelledDownloads.submitList(it) cancelledDownloads.submitList(it)
} }
} }
@ -202,4 +208,73 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
} }
deleteDialog.show() deleteDialog.show()
} }
private var simpleCallback: ItemTouchHelper.SimpleCallback =
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.bindingAdapterPosition
when (direction) {
ItemTouchHelper.LEFT -> {
val deletedItem = items[position]
items.remove(deletedItem)
cancelledDownloads.submitList(items.toList())
Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
items.add(position, deletedItem)
cancelledDownloads.submitList(items.toList())
}.show()
}
ItemTouchHelper.RIGHT -> {
onActionButtonClick(items[position].id)
}
}
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
RecyclerViewSwipeDecorator.Builder(
requireContext(),
c,
recyclerView,
viewHolder,
dX,
dY,
actionState,
isCurrentlyActive
)
.addSwipeLeftBackgroundColor(Color.RED)
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
.addSwipeRightBackgroundColor(
MaterialColors.getColor(
requireContext(),
R.attr.colorOnSurfaceInverse,Color.TRANSPARENT
)
)
.addSwipeRightActionIcon(R.drawable.ic_refresh)
.create()
.decorate()
super.onChildDraw(
c,
recyclerView,
viewHolder!!,
dX,
dY,
actionState,
isCurrentlyActive
)
}
}
} }

View file

@ -4,6 +4,8 @@ import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
@ -18,6 +20,7 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
@ -31,7 +34,10 @@ import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -42,7 +48,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var erroredRecyclerView : RecyclerView private lateinit var erroredRecyclerView : RecyclerView
private lateinit var erroredDownloads : GenericDownloadAdapter private lateinit var erroredDownloads : GenericDownloadAdapter
private lateinit var items : List<DownloadItem> private lateinit var items : MutableList<DownloadItem>
private lateinit var fileUtil: FileUtil private lateinit var fileUtil: FileUtil
private lateinit var uiUtil : UiUtil private lateinit var uiUtil : UiUtil
override fun onCreateView( override fun onCreateView(
@ -54,7 +60,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false) fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
items = listOf() items = mutableListOf()
fileUtil = FileUtil() fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil) uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
@ -71,6 +77,8 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
erroredRecyclerView = view.findViewById(R.id.download_recyclerview) erroredRecyclerView = view.findViewById(R.id.download_recyclerview)
erroredRecyclerView.adapter = erroredDownloads erroredRecyclerView.adapter = erroredDownloads
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(erroredRecyclerView)
val landScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && ! resources.getBoolean(R.bool.isTablet) val landScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && ! resources.getBoolean(R.bool.isTablet)
if (landScape){ if (landScape){
@ -79,7 +87,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
if (resources.getBoolean(R.bool.isTablet)) erroredRecyclerView.layoutManager = GridLayoutManager(context, 3) if (resources.getBoolean(R.bool.isTablet)) erroredRecyclerView.layoutManager = GridLayoutManager(context, 3)
downloadViewModel.erroredDownloads.observe(viewLifecycleOwner) { downloadViewModel.erroredDownloads.observe(viewLifecycleOwner) {
items = it items = it.toMutableList()
erroredDownloads.submitList(it) erroredDownloads.submitList(it)
} }
} }
@ -205,5 +213,69 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
TODO("Not yet implemented") TODO("Not yet implemented")
} }
private var simpleCallback: ItemTouchHelper.SimpleCallback =
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.bindingAdapterPosition
when (direction) {
ItemTouchHelper.LEFT -> {
val deletedItem = items[position]
items.remove(deletedItem)
erroredDownloads.submitList(items.toList())
Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
items.add(position, deletedItem)
erroredDownloads.submitList(items.toList())
}.show()
}
}
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
RecyclerViewSwipeDecorator.Builder(
requireContext(),
c,
recyclerView,
viewHolder,
dX,
dY,
actionState,
isCurrentlyActive
)
.addSwipeLeftBackgroundColor(Color.RED)
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
.addSwipeRightBackgroundColor(
MaterialColors.getColor(
requireContext(),
R.attr.colorOnSurfaceInverse, Color.TRANSPARENT
)
)
.create()
.decorate()
super.onChildDraw(
c,
recyclerView,
viewHolder!!,
dX,
dY,
actionState,
isCurrentlyActive
)
}
}
} }

View file

@ -2,6 +2,8 @@ package com.deniscerri.ytdlnis.ui.downloads
import android.app.Activity import android.app.Activity
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -17,6 +19,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager import androidx.work.WorkManager
@ -31,7 +34,10 @@ import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -48,7 +54,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
private lateinit var queuedRecyclerView : RecyclerView private lateinit var queuedRecyclerView : RecyclerView
private lateinit var queuedDownloads : GenericDownloadAdapter private lateinit var queuedDownloads : GenericDownloadAdapter
private lateinit var notificationUtil: NotificationUtil private lateinit var notificationUtil: NotificationUtil
private lateinit var items : List<DownloadItem> private lateinit var items : MutableList<DownloadItem>
private lateinit var fileUtil: FileUtil private lateinit var fileUtil: FileUtil
private lateinit var uiUtil: UiUtil private lateinit var uiUtil: UiUtil
@ -62,7 +68,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
activity = getActivity() activity = getActivity()
notificationUtil = NotificationUtil(requireContext()) notificationUtil = NotificationUtil(requireContext())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
items = listOf() items = mutableListOf()
fileUtil = FileUtil() fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil) uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
@ -79,6 +85,8 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
queuedRecyclerView = view.findViewById(R.id.download_recyclerview) queuedRecyclerView = view.findViewById(R.id.download_recyclerview)
queuedRecyclerView.adapter = queuedDownloads queuedRecyclerView.adapter = queuedDownloads
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(queuedRecyclerView)
val landScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && ! resources.getBoolean(R.bool.isTablet) val landScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && ! resources.getBoolean(R.bool.isTablet)
if (landScape){ if (landScape){
@ -87,7 +95,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
if (resources.getBoolean(R.bool.isTablet)) queuedRecyclerView.layoutManager = GridLayoutManager(context, 3) if (resources.getBoolean(R.bool.isTablet)) queuedRecyclerView.layoutManager = GridLayoutManager(context, 3)
downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) { downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) {
items = it items = it.toMutableList()
queuedDownloads.submitList(it) queuedDownloads.submitList(it)
} }
} }
@ -216,4 +224,69 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
} }
private var simpleCallback: ItemTouchHelper.SimpleCallback =
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.bindingAdapterPosition
when (direction) {
ItemTouchHelper.LEFT -> {
val deletedItem = items[position]
cancelDownload(deletedItem.id)
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.undo)) {
lifecycleScope.launch(Dispatchers.IO) {
downloadViewModel.queueDownloads(listOf(deletedItem))
}
}.show()
}
}
}
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
RecyclerViewSwipeDecorator.Builder(
requireContext(),
c,
recyclerView,
viewHolder,
dX,
dY,
actionState,
isCurrentlyActive
)
.addSwipeLeftBackgroundColor(Color.RED)
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
.addSwipeRightBackgroundColor(
MaterialColors.getColor(
requireContext(),
R.attr.colorOnSurfaceInverse, Color.TRANSPARENT
)
)
.create()
.decorate()
super.onChildDraw(
c,
recyclerView,
viewHolder!!,
dX,
dY,
actionState,
isCurrentlyActive
)
}
}
} }

View file

@ -8,6 +8,7 @@ import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.FileObserver import android.os.FileObserver
import android.os.Looper
import android.util.Log import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -21,6 +22,7 @@ import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.work.* import androidx.work.*
@ -129,7 +131,6 @@ class TerminalActivity : AppCompatActivity() {
input!!.visibility = View.GONE input!!.visibility = View.GONE
output!!.text = "${output!!.text}\n~ $ ${input!!.text}\n" output!!.text = "${output!!.text}\n~ $ ${input!!.text}\n"
showCancelFab() showCancelFab()
downloadFile.appendText("~ $ ${input!!.text}\n")
imm.hideSoftInputFromWindow(input?.windowToken, 0) imm.hideSoftInputFromWindow(input?.windowToken, 0)
startDownload( startDownload(
input!!.text.toString() input!!.text.toString()
@ -149,7 +150,8 @@ class TerminalActivity : AppCompatActivity() {
runOnUiThread{ runOnUiThread{
try { try {
val newText = downloadFile.readText() val newText = downloadFile.readText()
output!!.text = newText if (newText.isBlank()) return@runOnUiThread
output!!.append(newText)
output!!.scrollTo(0, output!!.height) output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN) scrollView!!.fullScroll(View.FOCUS_DOWN)
}catch (ignored: Exception) {} }catch (ignored: Exception) {}
@ -163,7 +165,8 @@ class TerminalActivity : AppCompatActivity() {
runOnUiThread{ runOnUiThread{
try { try {
val newText = downloadFile.readText() val newText = downloadFile.readText()
output!!.text = newText if (newText.isBlank()) return@runOnUiThread
output!!.append(newText)
output!!.scrollTo(0, output!!.height) output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN) scrollView!!.fullScroll(View.FOCUS_DOWN)
}catch (ignored: Exception) {} }catch (ignored: Exception) {}
@ -183,7 +186,19 @@ class TerminalActivity : AppCompatActivity() {
input!!.requestFocus() input!!.requestFocus()
input!!.setSelection(input!!.text.length) input!!.setSelection(input!!.text.length)
hideCancelFab() hideCancelFab()
downloadFile.writeText("")
} }
val outputData = work.progress.getString("output")
if (!outputData.isNullOrBlank()){
runOnUiThread{
try {
output!!.append("\n$outputData")
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
}catch (ignored: Exception) {}
}
}
} }
} }
initMenu() initMenu()
@ -227,10 +242,12 @@ class TerminalActivity : AppCompatActivity() {
private fun hideCancelFab() { private fun hideCancelFab() {
fab!!.text = getString(R.string.run_command) fab!!.text = getString(R.string.run_command)
fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24) fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24)
topAppBar!!.menu.forEach { it.isEnabled = true }
} }
private fun showCancelFab() { private fun showCancelFab() {
fab!!.text = getString(R.string.cancel_download) fab!!.text = getString(R.string.cancel_download)
fab!!.setIconResource(R.drawable.ic_cancel) fab!!.setIconResource(R.drawable.ic_cancel)
topAppBar!!.menu.forEach { it.isEnabled = false }
} }
private fun showShortcuts() { private fun showShortcuts() {

View file

@ -42,6 +42,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var incognito: SwitchPreferenceCompat? = null private var incognito: SwitchPreferenceCompat? = null
private var preferredDownloadType : ListPreference? = null private var preferredDownloadType : ListPreference? = null
private var searchEngine : ListPreference? = null private var searchEngine : ListPreference? = null
private var startDestination : ListPreference? = null
private var downloadCard: SwitchPreferenceCompat? = null private var downloadCard: SwitchPreferenceCompat? = null
private var meteredNetwork: SwitchPreferenceCompat? = null private var meteredNetwork: SwitchPreferenceCompat? = null
private var apiKey: EditTextPreference? = null private var apiKey: EditTextPreference? = null
@ -110,6 +111,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
incognito = findPreference("incognito") incognito = findPreference("incognito")
preferredDownloadType = findPreference("preferred_download_type") preferredDownloadType = findPreference("preferred_download_type")
searchEngine = findPreference("search_engine") searchEngine = findPreference("search_engine")
startDestination = findPreference("start_destination")
downloadCard = findPreference("download_card") downloadCard = findPreference("download_card")
meteredNetwork = findPreference("metered_networks") meteredNetwork = findPreference("metered_networks")
apiKey = findPreference("api_key") apiKey = findPreference("api_key")
@ -177,6 +179,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.putBoolean("incognito", incognito!!.isChecked) editor.putBoolean("incognito", incognito!!.isChecked)
editor.putString("preferred_download_type", preferredDownloadType!!.value) editor.putString("preferred_download_type", preferredDownloadType!!.value)
editor.putString("search_engine", searchEngine!!.value) editor.putString("search_engine", searchEngine!!.value)
editor.putString("start_destination", startDestination!!.value)
editor.putBoolean("download_card", downloadCard!!.isChecked) editor.putBoolean("download_card", downloadCard!!.isChecked)
editor.putBoolean("metered_networks", meteredNetwork!!.isChecked) editor.putBoolean("metered_networks", meteredNetwork!!.isChecked)
editor.putString("api_key", apiKey!!.text) editor.putString("api_key", apiKey!!.text)
@ -303,6 +306,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.apply() editor.apply()
true true
} }
startDestination!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
editor.putString("start_destination", newValue.toString())
editor.apply()
true
}
downloadCard!!.onPreferenceChangeListener = downloadCard!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
val enable = newValue as Boolean val enable = newValue as Boolean

View file

@ -80,7 +80,7 @@ class TerminalDownloadWorker(
} }
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line -> YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
outputFile.appendText("${line}\n") setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to itemId, "log" to logDownloads))
val title: String = command.take(65) val title: String = command.take(65)
notificationUtil.updateDownloadNotification( notificationUtil.updateDownloadNotification(
itemId, itemId,

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41z"/>
</vector>

View file

@ -207,4 +207,8 @@
<string name="format_source">Burimi i Formateve</string> <string name="format_source">Burimi i Formateve</string>
<string name="video_recommendations">Rekomendimet e Videove</string> <string name="video_recommendations">Rekomendimet e Videove</string>
<string name="video_recommendations_summary">Merr video Youtube te rekomanduara në faqen kryesore nga Invidious</string> <string name="video_recommendations_summary">Merr video Youtube te rekomanduara në faqen kryesore nga Invidious</string>
<string name="preferred_search_engine">Kërkuesi i Preferuar</string>
<string name="preferred_search_engine_summary">Kërkuesi që do të përdoret për kërkimet në aplikacion</string>
<string name="format_filtering_hint">Të gjithë elementet duhet të jenë të një i njëjti lloj për të përdorur këtë opsion</string>
<string name="preferred_home_screen">Faqja e preferuar kryesore</string>
</resources> </resources>

View file

@ -151,4 +151,16 @@
<item>bilisearch</item> <item>bilisearch</item>
<item>netsearch</item> <item>netsearch</item>
</array> </array>
<array name="start_destination">
<item>@string/home</item>
<item>@string/downloads</item>
<item>@string/more</item>
</array>
<array name="start_destination_values">
<item>Home</item>
<item>History</item>
<item>More</item>
</array>
</resources> </resources>

View file

@ -217,4 +217,6 @@
<string name="preferred_search_engine">Preferred Search Engine</string> <string name="preferred_search_engine">Preferred Search Engine</string>
<string name="preferred_search_engine_summary">The search engine to use for in-app searches</string> <string name="preferred_search_engine_summary">The search engine to use for in-app searches</string>
<string name="format_filtering_hint">All items must be of the same type to use this option</string> <string name="format_filtering_hint">All items must be of the same type to use this option</string>
<string name="preferred_home_screen">Preferred Home Screen</string>
<string name="undo">Undo</string>
</resources> </resources>

View file

@ -92,6 +92,14 @@
app:summary="@string/preferred_search_engine_summary" app:summary="@string/preferred_search_engine_summary"
app:title="@string/preferred_search_engine" /> app:title="@string/preferred_search_engine" />
<ListPreference
android:defaultValue="Home"
android:entries="@array/start_destination"
android:entryValues="@array/start_destination_values"
app:icon="@drawable/baseline_add_to_home_screen_24"
app:key="start_destination"
app:title="@string/preferred_home_screen" />
<EditTextPreference <EditTextPreference
android:icon="@drawable/ic_key" android:icon="@drawable/ic_key"
app:key="api_key" app:key="api_key"
@ -276,6 +284,11 @@
app:summary="@string/ytdl_update_hint" app:summary="@string/ytdl_update_hint"
app:title="@string/update_ytdl" /> app:title="@string/update_ytdl" />
<Preference
app:icon="@drawable/ic_info"
app:key="ytdl-version"
app:title="@string/ytdl_version"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"
android:defaultValue="false" android:defaultValue="false"
@ -366,11 +379,6 @@
android:data="https://github.com/deniscerri/ytdlnis" /> android:data="https://github.com/deniscerri/ytdlnis" />
</Preference> </Preference>
<Preference
app:icon="@drawable/ic_info"
app:key="ytdl-version"
app:title="@string/ytdl_version"/>
<Preference <Preference
app:icon="@drawable/ic_info" app:icon="@drawable/ic_info"
app:key="version" app:key="version"