implemented download queue

This commit is contained in:
Denis Çerri 2023-02-26 19:24:51 +01:00
parent 8eb0370c69
commit 8ff441172e
No known key found for this signature in database
GPG key ID: 95C43D517D830350
15 changed files with 796 additions and 222 deletions

View file

@ -120,7 +120,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appCompatVer"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation 'com.google.android.material:material:1.9.0-alpha02'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.core:core:1.9.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'

View file

@ -65,6 +65,16 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ui.DownloadQueueActivity"
android:configChanges="layoutDirection|locale"
android:parentActivityName=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="ytdlnis.DownloadQueueActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ui.downloadLogs.DownloadLogActivity"
android:configChanges="layoutDirection|locale"

View file

@ -26,6 +26,7 @@ import androidx.work.WorkManager
import com.deniscerri.ytdlnis.databinding.ActivityMainBinding
import com.deniscerri.ytdlnis.service.IDownloaderListener
import com.deniscerri.ytdlnis.service.IDownloaderService
import com.deniscerri.ytdlnis.ui.DownloadQueueActivity
import com.deniscerri.ytdlnis.ui.HistoryFragment
import com.deniscerri.ytdlnis.ui.HomeFragment
import com.deniscerri.ytdlnis.ui.MoreFragment
@ -79,7 +80,8 @@ class MainActivity : AppCompatActivity() {
}
R.id.history -> {
if (lastFragment === historyFragment) {
historyFragment.scrollToTop()
val intent = Intent(context, DownloadQueueActivity::class.java)
startActivity(intent)
} else {
this.title = getString(R.string.downloads)
}

View file

@ -1,56 +1,48 @@
package com.deniscerri.ytdlnis.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.squareup.picasso.Picasso
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<HistoryItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val checkedItems: ArrayList<Long>
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val fileUtil: FileUtil
init {
checkedItems = ArrayList()
this.onItemClickListener = onItemClickListener
this.activity = activity
fileUtil = FileUtil()
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val cardView: MaterialCardView
init {
cardView = itemView.findViewById(R.id.downloads_card_view)
cardView = itemView.findViewById(R.id.active_download_card_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.history_card, parent, false)
.inflate(R.layout.active_download_card, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
@ -58,7 +50,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val item = getItem(position)
val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
val imageURL = item!!.thumb
val uiHandler = Handler(Looper.getMainLooper())
if (imageURL.isNotEmpty()) {
@ -68,126 +60,76 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
}
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
// PROGRESS BAR ----------------------------------------------------
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
progressBar.tag = "${item.id}##progress"
progressBar.progress = 0
progressBar.isIndeterminate = true
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.downloads_title)
val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title
// Bottom Info ----------------------------------
val bottomInfo = card.findViewById<TextView>(R.id.downloads_info_bottom)
// Author ----------------------------------
val author = card.findViewById<TextView>(R.id.author)
var info = item.author
if (item.duration.isNotEmpty()) {
if (item.author.isNotEmpty()) info += ""
info += item.duration
}
bottomInfo.text = info
author.text = info
// TIME DOWNLOADED ----------------------------------
val datetime = card.findViewById<TextView>(R.id.downloads_info_time)
val time = item.time
val downloadedTime: String
if (time == 0L) {
downloadedTime = activity.getString(R.string.currently_downloading) + " " + item.type
} else {
val cal = Calendar.getInstance()
val date = Date(time * 1000L)
cal.time = date
val day = cal[Calendar.DAY_OF_MONTH]
val month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
downloadedTime = "$day $month $year - $timeString"
}
datetime.text = downloadedTime
// BUTTON ----------------------------------
val buttonLayout = card.findViewById<LinearLayout>(R.id.downloads_download_button_layout)
val btn = buttonLayout.findViewById<MaterialButton>(R.id.downloads_download_button_type)
var filePresent = true
//IS IN THE FILE SYSTEM?
val path = item.downloadPath
val file = File(path)
if (!file.exists() && path.isNotEmpty()) {
filePresent = false
thumbnail.colorFilter = ColorMatrixColorFilter(object : ColorMatrix() {
init {
setSaturation(0f)
}
})
thumbnail.alpha = 0.7f
}
if (item.type == DownloadViewModel.Type.audio) {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music)
} else {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video)
}
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
if (checkedItems.contains(item.id)) {
card.isChecked = true
card.strokeWidth = 5
} else {
card.isChecked = false
card.strokeWidth = 0
}
val finalFilePresent = filePresent
card.setOnLongClickListener {
checkCard(card, item.id)
true
}
card.setOnClickListener {
if (checkedItems.size > 0) {
checkCard(card, item.id)
val formatNote = card.findViewById<TextView>(R.id.format_note)
formatNote.text = item.format.format_note
val codec = card.findViewById<TextView>(R.id.codec)
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
onItemClickListener.onCardClick(item.id, finalFilePresent)
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec.visibility = View.GONE
}else{
codec.visibility = View.VISIBLE
codec.text = codecText
}
val fileSize = card.findViewById<TextView>(R.id.file_size)
fileSize.text = fileUtil.convertFileSize(item.format.filesize)
//OUTPUT
val output = card.findViewById<TextView>(R.id.output)
output.tag = "${item.id}##output"
// CANCEL BUTTON ----------------------------------
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_cancel)
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
cancelButton.setOnClickListener {
onItemClickListener.onCancelClick(item.id)
}
}
private fun checkCard(card: MaterialCardView, itemID: Long) {
if (card.isChecked) {
card.strokeWidth = 0
checkedItems.remove(itemID)
} else {
card.strokeWidth = 5
checkedItems.add(itemID)
}
card.isChecked = !card.isChecked
onItemClickListener.onCardSelect(itemID, card.isChecked)
}
interface OnItemClickListener {
fun onCardClick(itemID: Long, isPresent: Boolean)
fun onCardSelect(itemID: Long, isChecked: Boolean)
fun onButtonClick(position: Int)
}
@SuppressLint("NotifyDataSetChanged")
fun clearCheckeditems() {
for (i in 0 until itemCount){
val item = getItem(i)
if (checkedItems.find { it == item?.id } != null){
checkedItems.remove(item?.id)
notifyItemChanged(i)
}
}
checkedItems.clear()
fun onCancelClick(itemID: Long)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<HistoryItem> = object : DiffUtil.ItemCallback<HistoryItem>() {
override fun areItemsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
return oldItem.time == newItem.time
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
return oldItem.id == newItem.id
}
}
}

View file

@ -7,6 +7,7 @@ import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -19,38 +20,40 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso
import org.w3c.dom.Text
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<HistoryItem?, QueuedDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val checkedItems: ArrayList<Long>
class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, QueuedDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private var fileUtil: FileUtil
init {
checkedItems = ArrayList()
this.onItemClickListener = onItemClickListener
this.activity = activity
fileUtil = FileUtil()
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val cardView: MaterialCardView
init {
cardView = itemView.findViewById(R.id.downloads_card_view)
cardView = itemView.findViewById(R.id.queued_download_card_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.history_card, parent, false)
.inflate(R.layout.queued_download_card, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
@ -58,7 +61,7 @@ class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val item = getItem(position)
val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
val imageURL = item!!.thumb
val uiHandler = Handler(Looper.getMainLooper())
if (imageURL.isNotEmpty()) {
@ -69,29 +72,48 @@ class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.downloads_title)
val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title
// Bottom Info ----------------------------------
val bottomInfo = card.findViewById<TextView>(R.id.downloads_info_bottom)
// Author ----------------------------------
val author = card.findViewById<TextView>(R.id.author)
var info = item.author
if (item.duration.isNotEmpty()) {
if (item.author.isNotEmpty()) info += ""
info += item.duration
}
bottomInfo.text = info
author.text = info
// TIME DOWNLOADED ----------------------------------
val datetime = card.findViewById<TextView>(R.id.downloads_info_time)
val time = item.time
val downloadedTime: String
val formatNote = card.findViewById<TextView>(R.id.format_note)
formatNote.text = item.format.format_note
val codec = card.findViewById<TextView>(R.id.codec)
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec.visibility = View.GONE
}else{
codec.visibility = View.VISIBLE
codec.text = codecText
}
val fileSize = card.findViewById<TextView>(R.id.file_size)
fileSize.text = fileUtil.convertFileSize(item.format.filesize)
val downloadStartTime = card.findViewById<TextView>(R.id.time)
val time = item.downloadStartTime
if (time == 0L) {
downloadedTime = activity.getString(R.string.currently_downloading) + " " + item.type
downloadStartTime.visibility = View.GONE
} else {
downloadStartTime.visibility = View.VISIBLE
val cal = Calendar.getInstance()
val date = Date(time * 1000L)
cal.time = date
@ -100,94 +122,31 @@ class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val year = cal[Calendar.YEAR]
val formatter: DateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
val timeString = formatter.format(date)
downloadedTime = "$day $month $year - $timeString"
downloadStartTime.text = "$day $month $year - $timeString"
}
datetime.text = downloadedTime
// BUTTON ----------------------------------
val buttonLayout = card.findViewById<LinearLayout>(R.id.downloads_download_button_layout)
val btn = buttonLayout.findViewById<MaterialButton>(R.id.downloads_download_button_type)
var filePresent = true
// CANCEL BUTTON ----------------------------------
val cancelButton = card.findViewById<MaterialButton>(R.id.queued_download_cancel)
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
//IS IN THE FILE SYSTEM?
val path = item.downloadPath
val file = File(path)
if (!file.exists() && path.isNotEmpty()) {
filePresent = false
thumbnail.colorFilter = ColorMatrixColorFilter(object : ColorMatrix() {
init {
setSaturation(0f)
}
})
thumbnail.alpha = 0.7f
}
if (item.type == DownloadViewModel.Type.audio) {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music)
} else {
if (filePresent) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded) else btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video)
}
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
if (checkedItems.contains(item.id)) {
card.isChecked = true
card.strokeWidth = 5
} else {
card.isChecked = false
card.strokeWidth = 0
}
val finalFilePresent = filePresent
card.setOnLongClickListener {
checkCard(card, item.id)
true
}
card.setOnClickListener {
if (checkedItems.size > 0) {
checkCard(card, item.id)
} else {
onItemClickListener.onCardClick(item.id, finalFilePresent)
}
cancelButton.setOnClickListener {
onItemClickListener.onQueuedCancelClick(item.id)
}
}
private fun checkCard(card: MaterialCardView, itemID: Long) {
if (card.isChecked) {
card.strokeWidth = 0
checkedItems.remove(itemID)
} else {
card.strokeWidth = 5
checkedItems.add(itemID)
}
card.isChecked = !card.isChecked
onItemClickListener.onCardSelect(itemID, card.isChecked)
}
interface OnItemClickListener {
fun onCardClick(itemID: Long, isPresent: Boolean)
fun onCardSelect(itemID: Long, isChecked: Boolean)
fun onButtonClick(position: Int)
}
@SuppressLint("NotifyDataSetChanged")
fun clearCheckeditems() {
for (i in 0 until itemCount){
val item = getItem(i)
if (checkedItems.find { it == item?.id } != null){
checkedItems.remove(item?.id)
notifyItemChanged(i)
}
}
checkedItems.clear()
fun onQueuedCancelClick(itemID: Long)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<HistoryItem> = object : DiffUtil.ItemCallback<HistoryItem>() {
override fun areItemsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
return oldItem.time == newItem.time
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
return oldItem.url == newItem.url && oldItem.format.format_id == newItem.format.format_id
}
}
}

View file

@ -0,0 +1,176 @@
package com.deniscerri.ytdlnis.ui
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.adapter.QueuedDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class DownloadQueueActivity : AppCompatActivity(), ActiveDownloadAdapter.OnItemClickListener, QueuedDownloadAdapter.OnItemClickListener {
private lateinit var activeRecyclerView: RecyclerView
private lateinit var activeDownloads: ActiveDownloadAdapter
private lateinit var queuedRecyclerView: RecyclerView
private lateinit var queuedDownloads: QueuedDownloadAdapter
private lateinit var noResults: RelativeLayout
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var topAppBar: MaterialToolbar
private lateinit var notificationUtil: NotificationUtil
private lateinit var runningLayout: LinearLayout
private lateinit var queuedLayout: LinearLayout
var context: Context? = null
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_download_queue)
context = baseContext
val view : View = window.decorView.findViewById(android.R.id.content)
topAppBar = findViewById(R.id.logs_toolbar)
topAppBar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
activeDownloads =
ActiveDownloadAdapter(
this,
this@DownloadQueueActivity
)
queuedDownloads =
QueuedDownloadAdapter(
this,
this@DownloadQueueActivity
)
activeRecyclerView = findViewById(R.id.active_recyclerview)
activeRecyclerView.layoutManager = LinearLayoutManager(context)
activeRecyclerView.adapter = activeDownloads
queuedRecyclerView = findViewById(R.id.queued_recyclerview)
queuedRecyclerView.layoutManager = LinearLayoutManager(context)
queuedRecyclerView.adapter = queuedDownloads
noResults = findViewById(R.id.no_results)
noResults.visibility = View.GONE
runningLayout = findViewById(R.id.running_layout)
queuedLayout = findViewById(R.id.queued_layout)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
notificationUtil = NotificationUtil(this)
//active downloads livedata
downloadViewModel.activeDownloads.observe(this) {
activeDownloads.submitList(it)
if (it.isEmpty()){
if (queuedLayout.visibility == View.GONE) noResults.visibility = View.VISIBLE
runningLayout.visibility = View.GONE
}else{
noResults.visibility = View.GONE
runningLayout.visibility = View.VISIBLE
it.forEach{item ->
WorkManager.getInstance(this)
.getWorkInfosForUniqueWorkLiveData(item.id.toString())
.observe(this){ list ->
list.forEach {work ->
if (work == null) return@observe
val id = work.progress.getLong("id", 0L)
if(id == 0L) return@observe
val progress = work.progress.getInt("progress", 0)
val output = work.progress.getString("output")
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
val outputText = view.findViewWithTag<TextView>("$id##output")
runOnUiThread {
progressBar.setProgressCompat(progress, true)
outputText.text = output
}
}
}
}
}
}
//queued downloads livedata
downloadViewModel.queuedDownloads.observe(this) {
queuedDownloads.submitList(it)
if (it.isEmpty()){
if (runningLayout.visibility == View.GONE) noResults.visibility = View.VISIBLE
queuedLayout.visibility = View.GONE
}else{
noResults.visibility = View.GONE
queuedLayout.visibility = View.VISIBLE
}
}
}
private fun initMenu(logFolder: File) {
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId
if (itemId == R.id.remove_logs) {
try{
logFolder.listFiles()!!.forEach {
it.delete()
}
}catch (e: Exception){
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
}
true
}
}
companion object {
private const val TAG = "DownloadQueueActivity"
}
override fun onCancelClick(itemID: Long) {
cancelDownload(itemID)
}
override fun onQueuedCancelClick(itemID: Long) {
lifecycleScope.launch{
withContext(Dispatchers.IO){
downloadViewModel.deleteDownload(downloadViewModel.getItemByID(itemID))
}
}
cancelDownload(itemID)
}
private fun cancelDownload(itemID: Long){
val id = itemID.toInt()
YoutubeDL.getInstance().destroyProcessById(id.toString())
WorkManager.getInstance(this).cancelUniqueWork(id.toString())
notificationUtil.cancelDownloadNotification(id)
}
}

View file

@ -184,15 +184,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
}
try {
Handler(Looper.getMainLooper()).post {
// DOWNLOAD ALL BUTTON
if (resultsList!!.size > 1 || inputQueriesLength > 1) {
downloadAllFabCoordinator!!.visibility = VISIBLE
}
// databaseManager = DatabaseManager(context)
// databaseManager!!.clearResults()
// for (v in resultsList!!) v!!.isPlaylistItem = 1
// databaseManager!!.addToResults(resultsList)
}
} catch (ignored: Exception) {
}
@ -202,14 +197,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
resultViewModel.checkTrending()
}
WorkManager.getInstance(requireContext())
.getWorkInfosByTagLiveData("download")
.observe(viewLifecycleOwner){ list ->
list.forEach {
//Toast.makeText(context, """${it.progress.getInt("progress", 0)} ${it.progress.getString("output")}""", Toast.LENGTH_SHORT).show()
}
}
// fragmentView!!.post{
// if (shimmerCards!!.visibility == VISIBLE) return@post
// val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager

View file

@ -209,10 +209,8 @@ class DownloadWorker(
"Format: ${downloadItem.format}\n\n")
}
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt()))
setProgressAsync(workDataOf("output" to line))
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line, "id" to downloadItem.id))
val title: String = downloadItem.title
notificationUtil.updateDownloadNotification(
downloadItem.id.toInt(),

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/downloads_card_constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:id="@+id/active_download_relative_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,2:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/active_download_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="10dp"
app:cardElevation="10dp"
app:cardMaxElevation="12dp"
android:checkable="true"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_margin="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:alpha="0.3"
android:scaleY="100"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:paddingBottom="10dp"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="2"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/active_download_cancel"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/author"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:paddingBottom="10dp"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="1.5"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/linearLayout2"
app:layout_constraintEnd_toStartOf="@+id/active_download_cancel"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
android:id="@+id/format_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingVertical="10dp"
android:paddingStart="10dp"
android:paddingEnd="0dp"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintStart_toEndOf="@+id/format_note">
<TextView
android:id="@+id/codec"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/file_size"
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<TextView
android:id="@+id/output"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:paddingBottom="10dp"
android:shadowRadius="1.5"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/active_download_cancel"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_margin="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
tools:context="com.deniscerri.ytdlnis.ui.CustomCommandActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false"
android:background="@android:color/transparent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/logs_toolbar"
android:elevation="0dp"
app:title="@string/command_templates"
android:layout_width="match_parent"
app:navigationIcon="@drawable/ic_back"
android:layout_height="match_parent"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/template_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
>
</androidx.recyclerview.widget.RecyclerView>
<include layout="@layout/history_no_results"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
tools:context="com.deniscerri.ytdlnis.ui.CustomCommandActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false"
android:background="@android:color/transparent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/logs_toolbar"
android:elevation="0dp"
app:title="@string/download_queue"
android:layout_width="match_parent"
app:navigationIcon="@drawable/ic_back"
android:layout_height="match_parent"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:scrollbars="none"
android:layout_height="wrap_content">
<LinearLayout
android:padding="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/running_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:padding="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="@string/running"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/active_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
>
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
<LinearLayout
android:id="@+id/queued_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:padding="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="@string/added_to_queue"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/queued_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
>
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<include layout="@layout/history_no_results"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/downloads_card_constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:id="@+id/queued_download_relative_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,2.66:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/queued_download_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="10dp"
app:cardElevation="10dp"
app:cardMaxElevation="12dp"
android:checkable="true"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_margin="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:paddingBottom="10dp"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="2"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:paddingBottom="10dp"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="1.5"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<com.google.android.material.button.MaterialButton
android:layout_margin="15dp"
android:id="@+id/queued_download_cancel"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_baseline_delete_outline_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/format_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/author"
app:layout_constraintVertical_bias="1.0" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/format_note">
<TextView
android:id="@+id/codec"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/file_size"
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/time"
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorPrimary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -4,5 +4,5 @@
<color name="black">#000000</color>
<color name="grey">#323232</color>
<color name="light_grey">#A6A6A6</color>
<color name="icon_bg">@android:color/background_light</color>
<color name="icon_bg">@android:color/background_dark</color>
</resources>

View file

@ -181,4 +181,6 @@
<string name="log_downloads">Log Downloads</string>
<string name="log_downloads_summary">Create a log file for each download</string>
<string name="new_update">New Update</string>
<string name="download_queue">Download Queue</string>
<string name="running">Running</string>
</resources>

View file

@ -41,6 +41,15 @@
<intent android:action="ytdlnis.CommandTemplatesActivity" />
</Preference>
<Preference
app:icon="@drawable/ic_down"
app:key="download_queue"
app:allowDividerBelow="true"
app:title="@string/download_queue">
<intent android:action="ytdlnis.DownloadQueueActivity" />
</Preference>
<Preference
app:icon="@drawable/ic_settings"
app:key="open_settings"