added range picker on playlist

This commit is contained in:
deniscerri 2023-04-01 13:56:23 +02:00
parent 3c99f3fdc2
commit 7a8341a3c3
No known key found for this signature in database
GPG key ID: 95C43D517D830350
11 changed files with 375 additions and 164 deletions

View file

@ -76,12 +76,12 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
// CHECKBOX ---------------------------------- // CHECKBOX ----------------------------------
val check = card.findViewById<CheckBox>(R.id.checkBox) val check = card.findViewById<CheckBox>(R.id.checkBox)
check.isChecked = checkedItems.contains(item.url) check.isChecked = checkedItems.contains(item.url)
check.setOnCheckedChangeListener { buttonView, isChecked -> check.setOnClickListener {
checkCard(isChecked, item.url) checkCard(check.isChecked, item.url)
} }
card.setOnClickListener { card.setOnClickListener {
check.isChecked = !check.isChecked check.performClick()
} }
} }
@ -119,6 +119,22 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
} }
} }
fun checkRange(start: Int, end: Int){
checkedItems.clear()
if (start == end ){
val item = getItem(start)
checkedItems.add(item!!.url)
notifyItemChanged(start)
}else{
for (i in start..end){
val item = getItem(i)
checkedItems.add(item!!.url)
notifyItemChanged(i)
}
}
}
fun getCheckedItems() : List<String>{ fun getCheckedItems() : List<String>{
return checkedItems return checkedItems
} }

View file

@ -5,10 +5,14 @@ import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -23,8 +27,10 @@ import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.* import java.util.*
class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>, private val type: DownloadViewModel.Type) : BottomSheetDialogFragment(), PlaylistAdapter.OnItemClickListener { class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>, private val type: DownloadViewModel.Type) : BottomSheetDialogFragment(), PlaylistAdapter.OnItemClickListener {
@ -34,7 +40,8 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
private lateinit var recyclerView: RecyclerView private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var selectedText: TextView private lateinit var selectedText: TextView
private lateinit var fromTextInput: TextInputLayout
private lateinit var toTextInput: TextInputLayout
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -75,41 +82,90 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
selectedText = view.findViewById<Button>(R.id.selected) selectedText = view.findViewById<Button>(R.id.selected)
selectedText.text = "0 ${resources.getString(R.string.selected)}" selectedText.text = "0 ${resources.getString(R.string.selected)}"
fromTextInput = view.findViewById(R.id.from_textinput)
toTextInput = view.findViewById(R.id.to_textinput)
fromTextInput.editText!!.doAfterTextChanged { _text ->
reset()
val start = _text.toString()
val end = toTextInput.editText!!.text.toString()
if (checkRanges(start, end)) {
if (start < end){
var startNr = Integer.parseInt(start)
startNr--
var endNr = Integer.parseInt(end)
endNr--
if (startNr <= 0) startNr = 0
if (endNr > items.size) endNr = items.size
listAdapter.checkRange(startNr, endNr)
selectedText.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
}
}
}
toTextInput.editText!!.doAfterTextChanged { _text ->
reset()
val start = fromTextInput.editText!!.text.toString()
val end = _text.toString()
if (checkRanges(start, end)) {
if (start < end){
var startNr = Integer.parseInt(start)
startNr--
var endNr = Integer.parseInt(end)
endNr--
if (startNr <= 0) startNr = 0
if (endNr > items.size) endNr = items.size
listAdapter.checkRange(startNr, endNr)
selectedText.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
}
}
}
val checkAll = view.findViewById<FloatingActionButton>(R.id.check_all) val checkAll = view.findViewById<FloatingActionButton>(R.id.check_all)
checkAll!!.setOnClickListener { checkAll!!.setOnClickListener {
if (listAdapter.getCheckedItems().size != items.size){ if (listAdapter.getCheckedItems().size != items.size){
fromTextInput.editText!!.setText("1")
toTextInput.editText!!.setText(items.size.toString())
listAdapter.checkAll() listAdapter.checkAll()
selectedText.text = resources.getString(R.string.all_items_selected) selectedText.text = resources.getString(R.string.all_items_selected)
}else{ }else{
listAdapter.clearCheckeditems() reset()
selectedText.text = "0 ${resources.getString(R.string.selected)}" fromTextInput.isEnabled = true
toTextInput.isEnabled = true
fromTextInput.editText!!.setText("")
toTextInput.editText!!.setText("")
} }
} }
val ok = view.findViewById<Button>(R.id.bottomsheet_ok) val ok = view.findViewById<Button>(R.id.bottomsheet_ok)
ok!!.setOnClickListener { ok!!.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO){ lifecycleScope.launch(Dispatchers.IO) {
val checkedItems = listAdapter.getCheckedItems() val checkedItems = listAdapter.getCheckedItems()
val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) } val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) }
val downloadItems = mutableListOf<DownloadItem>() if (checkedResultItems.size == 1){
checkedResultItems.forEach { c -> val resultItem = resultViewModel.getItemByURL(checkedResultItems[0]!!.url)
c!!.id = 0
val i = downloadViewModel.createDownloadItemFromResult(c,type)
if (type == DownloadViewModel.Type.command){
i.format = downloadViewModel.getLatestCommandTemplateAsFormat()
}
downloadItems.add(i)
}
if (downloadItems.size == 1){
val resultItem = resultViewModel.getItemByURL(items[0]!!.url)
val bottomSheet = DownloadBottomSheetDialog(resultItem, type) val bottomSheet = DownloadBottomSheetDialog(resultItem, type)
bottomSheet.show(parentFragmentManager, "downloadSingleSheet") bottomSheet.show(parentFragmentManager, "downloadSingleSheet")
}else{ }else{
val downloadItems = mutableListOf<DownloadItem>()
checkedResultItems.forEach { c ->
c!!.id = 0
val i = downloadViewModel.createDownloadItemFromResult(c,type)
if (type == DownloadViewModel.Type.command){
i.format = downloadViewModel.getLatestCommandTemplateAsFormat()
}
downloadItems.add(i)
}
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadItems) val bottomSheet = DownloadMultipleBottomSheetDialog(downloadItems)
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet") bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} }
dismiss() dismiss()
} }
@ -133,6 +189,15 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
} }
} }
private fun checkRanges(start: String, end: String) : Boolean {
return start.isNotBlank() && end.isNotBlank()
}
private fun reset(){
listAdapter.clearCheckeditems()
selectedText.text = "0 ${resources.getString(R.string.selected)}"
}
override fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>) { override fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>) {
if (checkedItems.size == items.size){ if (checkedItems.size == items.size){
@ -140,6 +205,13 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
}else{ }else{
selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}" selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
} }
if (checkedItems.isEmpty()){
fromTextInput.isEnabled = true
toTextInput.isEnabled = true
}else{
fromTextInput.isEnabled = false
toTextInput.isEnabled = false
}
} }
} }

View file

@ -1,60 +1,43 @@
package com.deniscerri.ytdlnis.ui.more package com.deniscerri.ytdlnis.ui.more
import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.Notification
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.os.Looper
import android.text.format.DateFormat
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.widget.* import android.widget.EditText
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.work.* import androidx.work.Data
import com.deniscerri.ytdlnis.App import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.work.DownloadWorker import com.deniscerri.ytdlnis.work.TerminalDownloadWorker
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomappbar.BottomAppBar
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.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import kotlin.properties.Delegates
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
class TerminalActivity : AppCompatActivity() { class TerminalActivity : AppCompatActivity() {
@ -67,11 +50,15 @@ class TerminalActivity : AppCompatActivity() {
private lateinit var bottomAppBar: BottomAppBar private lateinit var bottomAppBar: BottomAppBar
private lateinit var commandTemplateViewModel: CommandTemplateViewModel private lateinit var commandTemplateViewModel: CommandTemplateViewModel
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
private var downloadID by Delegates.notNull<Int>()
var context: Context? = null var context: Context? = null
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_terminal) setContentView(R.layout.activity_terminal)
downloadID = System.currentTimeMillis().toInt() % 100000
context = baseContext context = baseContext
scrollView = findViewById(R.id.custom_command_scrollview) scrollView = findViewById(R.id.custom_command_scrollview)
topAppBar = findViewById(R.id.custom_command_toolbar) topAppBar = findViewById(R.id.custom_command_toolbar)
@ -112,6 +99,28 @@ class TerminalActivity : AppCompatActivity() {
} }
notificationUtil = NotificationUtil(this) notificationUtil = NotificationUtil(this)
handleIntent(intent) handleIntent(intent)
WorkManager.getInstance(this)
.getWorkInfosForUniqueWorkLiveData(downloadID.toString())
.observe(this){ list ->
list.forEach {work ->
if (work.state == WorkInfo.State.SUCCEEDED || work.state == WorkInfo.State.FAILED || work.state == WorkInfo.State.CANCELLED) {
input!!.visibility = View.VISIBLE
input!!.requestFocus()
hideCancelFab()
}
val line = work.progress.getString("output") ?: return@observe
val id = work.progress.getInt("id", 0)
if(id == 0) return@observe
runOnUiThread {
try {
output!!.text = "${output!!.text}\n$line"
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
}catch (ignored: Exception) {}
}
}
}
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@ -204,121 +213,31 @@ class TerminalActivity : AppCompatActivity() {
val cmd = if (command!!.contains("yt-dlp")) command.replace("yt-dlp", "") val cmd = if (command!!.contains("yt-dlp")) command.replace("yt-dlp", "")
else command else command
downloadID = System.currentTimeMillis().toInt() val workRequest = OneTimeWorkRequestBuilder<TerminalDownloadWorker>()
.setInputData(
val theIntent = Intent(this, TerminalActivity::class.java) Data.Builder()
val pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE) .putInt("id", downloadID)
.putString("command", cmd)
val commandNotification: Notification = .build()
notificationUtil.createDownloadServiceNotification(
pendingIntent,
getString(R.string.terminal),
downloadID,
NotificationUtil.COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID
) )
with(NotificationManagerCompat.from(this)){ .addTag("terminal")
if (ActivityCompat.checkSelfPermission( .build()
context!!,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
notify(downloadID, commandNotification)
}
}
val request = YoutubeDLRequest(emptyList())
val tempFolder = StringBuilder(context!!.cacheDir.absolutePath + """/${System.currentTimeMillis()}##terminal""") WorkManager.getInstance(this).beginUniqueWork(
val tempFileDir = File(tempFolder.toString()) downloadID.toString(),
tempFileDir.delete() ExistingWorkPolicy.KEEP,
tempFileDir.mkdir() workRequest
).enqueue()
request.addOption(
"--config-locations",
File(cacheDir, "config${System.currentTimeMillis()}.txt").apply {
writeText(cmd)
}.absolutePath
)
request.addOption("-P", tempFileDir.absolutePath)
showCancelFab()
val disposable: Disposable = Observable.fromCallable {
try{
YoutubeDL.getInstance().execute(request, downloadID.toString()){ progress, _, line ->
Log.e(TAG, line)
runOnUiThread {
output!!.text = "${output!!.text}\n$line"
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
}
val title: String = getString(R.string.terminal)
notificationUtil.updateDownloadNotification(
downloadID,
line, progress.toInt(), 0, title,
NotificationUtil.COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID
)
}
}catch (e: Exception){
e.printStackTrace()
runOnUiThread {
output!!.text = "${output!!.text}\n${e.message}"
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
input!!.visibility = View.VISIBLE
input!!.requestFocus()
hideCancelFab()
}
}
}
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
input!!.setText("yt-dlp ")
input!!.visibility = View.VISIBLE
input!!.requestFocus()
try{
val fileUtil = FileUtil()
fileUtil.moveFile(tempFileDir.absoluteFile, this, sharedPreferences.getString("command_path", getString(R.string.command))!!) {}
}catch (e: Exception){
output!!.text = "${output!!.text}\n${e.message}"
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
}
hideCancelFab()
notificationUtil.cancelDownloadNotification(downloadID)
}) { e ->
e.printStackTrace()
output!!.text = "${output!!.text}\n${e.message}"
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
input!!.setText("yt-dlp ")
input!!.visibility = View.VISIBLE
hideCancelFab();
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
Log.e(DownloadWorker.TAG, context?.getString(R.string.failed_download), e)
notificationUtil.cancelDownloadNotification(downloadID)
}
compositeDisposable.add(disposable)
} }
private fun cancelDownload() { private fun cancelDownload() {
YoutubeDL.getInstance().destroyProcessById(downloadID.toString()) YoutubeDL.getInstance().destroyProcessById(downloadID.toString())
WorkManager.getInstance(this).cancelUniqueWork(downloadID.toString()) WorkManager.getInstance(this).cancelUniqueWork(downloadID.toString())
notificationUtil.cancelDownloadNotification(downloadID) notificationUtil.cancelDownloadNotification(downloadID.toInt())
} }
companion object { companion object {
private const val TAG = "CustomCommandActivity" private const val TAG = "CustomCommandActivity"
private var downloadID = System.currentTimeMillis().toInt()
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
} }

View file

@ -102,7 +102,7 @@ class DownloadLogListActivity : AppCompatActivity(), DownloadLogsAdapter.OnItemC
fileList = mutableListOf() fileList = mutableListOf()
try{ try{
fileList.addAll(logFolder.listFiles()!!) fileList.addAll(logFolder.listFiles()!!)
fileList.sortByDescending { it.name.split(" -")[0].toInt() } fileList.sortByDescending { it.lastModified()}
}catch (e: Exception){ }catch (e: Exception){
e.printStackTrace() e.printStackTrace()
} }

View file

@ -140,6 +140,10 @@ class FileUtil() {
val titleRegex = Regex("[^A-Za-z\\d ]") val titleRegex = Regex("[^A-Za-z\\d ]")
return File(context.filesDir.absolutePath + """/logs/${item.id} - ${titleRegex.replace(item.title, "")}##${item.type}##${item.format.format_id}.log""") return File(context.filesDir.absolutePath + """/logs/${item.id} - ${titleRegex.replace(item.title, "")}##${item.type}##${item.format.format_id}.log""")
} }
fun getLogFileForTerminal(context: Context, command: String) : File {
val titleRegex = Regex("[^A-Za-z\\d ]")
return File(context.filesDir.absolutePath + """/logs/Terminal - ${titleRegex.replace(command.take(30), "")}##terminal.log""")
}
fun convertFileSize(s: Long): String{ fun convertFileSize(s: Long): String{
if (s <= 0) return "?" if (s <= 0) return "?"

View file

@ -69,7 +69,7 @@ class DownloadWorker(
val type = downloadItem.type val type = downloadItem.type
val downloadLocation = downloadItem.downloadPath val downloadLocation = downloadItem.downloadPath
val tempFileDir = File(context.cacheDir.absolutePath + downloadItem.id) val tempFileDir = File(context.cacheDir.absolutePath + "/" + downloadItem.id)
tempFileDir.delete() tempFileDir.delete()
tempFileDir.mkdir() tempFileDir.mkdir()

View file

@ -0,0 +1,155 @@
package com.deniscerri.ytdlnis.work
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import java.io.File
import java.util.*
class TerminalDownloadWorker(
private val context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
override fun doWork(): Result {
val itemId = inputData.getInt("id", 0)
val command = inputData.getString("command")
if (itemId == 0) return Result.failure()
if (command!!.isEmpty()) return Result.failure()
val notificationUtil = NotificationUtil(context)
val fileUtil = FileUtil()
val handler = Handler(Looper.getMainLooper())
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(20), itemId, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
val foregroundInfo = ForegroundInfo(itemId, notification)
setForegroundAsync(foregroundInfo)
val request = YoutubeDLRequest(emptyList())
val sharedPreferences = context.getSharedPreferences("root_preferences",
Service.MODE_PRIVATE
)
val downloadLocation = sharedPreferences.getString("command_path", context.getString(R.string.command_path))
val tempFileDir = File(context.cacheDir.absolutePath + "/" + itemId)
tempFileDir.delete()
tempFileDir.mkdir()
request.addOption(
"--config-locations",
File(context.cacheDir, "config${System.currentTimeMillis()}.txt").apply {
writeText(command)
}.absolutePath
)
request.addOption("-P", tempFileDir.absolutePath)
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
val logFolder = File(context.filesDir.absolutePath + "/logs")
val logFile = fileUtil.getLogFileForTerminal(context, command)
runCatching {
if (logDownloads){
logFolder.mkdirs()
logFile.createNewFile()
logFile.writeText("Downloading:\n" +
"Terminal Download\n" +
"Command: ${command}\n\n")
}
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line, "id" to itemId, "log" to logDownloads))
val title: String = command.take(20)
notificationUtil.updateDownloadNotification(
itemId,
line, progress.toInt(), 0, title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
if (logDownloads && logFile.exists()){
logFile.appendText("${line}\n")
}
}
}.onSuccess {
//move file from internal to set download directory
var finalPath : String?
try {
finalPath = moveFile(tempFileDir.absoluteFile, downloadLocation!!){ progress ->
setProgressAsync(workDataOf("progress" to progress))
}
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $finalPath", "id" to itemId, "log" to logDownloads))
}catch (e: Exception){
finalPath = context.getString(R.string.unfound_file)
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
}
//put download in history
// val incognito = sharedPreferences.getBoolean("incognito", false)
// if (!incognito) {
// val unixtime = System.currentTimeMillis() / 1000
// val file = File(finalPath!!)
// downloadItem.format.filesize = if (file.exists()) file.length() else 0L
// val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPath, downloadItem.website, downloadItem.format)
// runBlocking {
// historyDao.insert(historyItem)
// }
// }
// runBlocking {
// dao.delete(downloadItem.id)
// }
}.onFailure {
if (it is YoutubeDL.CanceledException) {
return Result.failure()
}
if (logDownloads && logFile.exists()){
logFile.appendText("${it.message}\n")
}
setProgressAsync(workDataOf("progress" to -1, "output" to it.message, "id" to itemId, "log" to logDownloads))
tempFileDir.delete()
Log.e(TAG, context.getString(R.string.failed_download), it)
notificationUtil.updateDownloadNotification(
itemId,
context.getString(R.string.failed_download), 0, 0, command.take(20),
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
return Result.failure()
}
return Result.success()
}
@Throws(Exception::class)
private fun moveFile(originDir: File, downLocation: String, progress: (progress: Int) -> Unit) : String{
val fileUtil = FileUtil()
val path = fileUtil.moveFile(originDir, context, downLocation){ p ->
progress(p)
}
return path
}
companion object {
const val TAG = "DownloadWorker"
}
}

View file

@ -12,4 +12,4 @@
android:fillColor="@color/icon_fg" android:fillColor="@color/icon_fg"
android:strokeWidth="1" android:strokeWidth="1"
android:fillType="evenOdd"/> android:fillType="evenOdd"/>
</vector> </vector>

View file

@ -57,11 +57,12 @@
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp" android:layout_marginHorizontal="10dp"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/cancelButton"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rangeSlider"> app:layout_constraintTop_toBottomOf="@+id/rangeSlider">
@ -69,8 +70,8 @@
android:id="@+id/from_textinput" android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:hint="@string/start" android:layout_height="wrap_content"
android:layout_height="wrap_content"> android:hint="@string/start">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent" android:layout_width="match_parent"
@ -94,8 +95,8 @@
android:id="@+id/to_textinput" android:id="@+id/to_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:hint="@string/end"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/end"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/colon"> app:layout_constraintStart_toEndOf="@+id/colon">

View file

@ -4,17 +4,18 @@
android:id="@+id/playlist_card_constraintLayout" android:id="@+id/playlist_card_constraintLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<FrameLayout <FrameLayout
android:id="@+id/download_card_view" android:id="@+id/download_card_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginStart="10dp" android:padding="10dp"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checkable="true"
android:clickable="true"
android:focusable="true"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/checkBox" app:layout_constraintEnd_toStartOf="@+id/checkBox"

View file

@ -77,17 +77,60 @@
android:autoLink="all" android:autoLink="all"
android:text="@string/ok" android:text="@string/ok"
app:icon="@drawable/ic_check" app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottomsheet_ok"
app:layout_constraintVertical_bias="0.0">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:errorEnabled="true"
android:hint="@string/start">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/to_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:hint="@string/end"
app:errorEnabled="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/colon">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview" android:id="@+id/downloadMultipleRecyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingTop="10dp"
tools:listitem="@layout/playlist_item" tools:listitem="@layout/playlist_item"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"