implemented download scheduling

This commit is contained in:
Denis Çerri 2023-02-11 18:26:13 +01:00
parent 2fd96cf3b2
commit d860511ce9
No known key found for this signature in database
GPG key ID: 95C43D517D830350
29 changed files with 420 additions and 597 deletions

View file

@ -0,0 +1 @@
-dontobfuscate

View file

@ -1,3 +1,6 @@
import com.android.build.gradle.internal.dsl.ManagedVirtualDevice
plugins {
id 'com.android.application'
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1'
@ -48,6 +51,19 @@ android {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
testOptions{
managedDevices {
devices{
pixel4Api31(com.android.build.api.dsl.ManagedVirtualDevice){
device = "Pixel 2"
apiLevel = 31
systemImageSource = "aosp"
}
}
}
}
buildTypes {
release {
minifyEnabled false
@ -55,6 +71,16 @@ android {
debuggable true
signingConfig signingConfigs.debug
}
benchmark {
initWith buildTypes.release
proguardFiles 'baseline-profiles-rules.pro'
}
debug {
initWith buildTypes.release
proguardFiles 'baseline-profiles-rules.pro'
}
}
buildFeatures {
@ -95,7 +121,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appCompatVer"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation 'com.google.android.material:material:1.7.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.core:core:1.9.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
@ -104,9 +130,15 @@ dependencies {
implementation 'androidx.navigation:navigation-ui:2.5.3'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.test.ext:junit-ktx:1.1.5'
testImplementation "junit:junit:$junitVer"
androidTestImplementation "junit:junit:$junitVer"
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer"
androidTestImplementation "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha09"
androidTestImplementation "androidx.test:runner:1.5.2"
androidTestImplementation "androidx.test:core:1.5.0"
androidTestImplementation "androidx.test:rules:1.5.0"
implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
implementation "com.devbrackets.android:exomedia:4.3.0"

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "e53569dbe0c02d62eda598075d73d5b1",
"identityHash": "9197d2ce894417365b9012ace8bcf284",
"entities": [
{
"tableName": "results",
@ -160,7 +160,7 @@
},
{
"tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `embedSubs` INTEGER NOT NULL, `addChapters` INTEGER NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued')",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `embedSubs` INTEGER NOT NULL, `addChapters` INTEGER NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
@ -210,13 +210,6 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "removeAudio",
"columnName": "removeAudio",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "downloadPath",
"columnName": "downloadPath",
@ -265,6 +258,13 @@
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'Queued'"
},
{
"fieldPath": "downloadStartTime",
"columnName": "downloadStartTime",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
@ -312,7 +312,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e53569dbe0c02d62eda598075d73d5b1')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9197d2ce894417365b9012ace8bcf284')"
]
}
}

View file

@ -0,0 +1,18 @@
package com.deniscerri.ytdlnis
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule val baselineProfileRule = BaselineProfileRule()
@Test
fun startup() =
baselineProfileRule.collectBaselineProfile(packageName = "com.deniscerri.ytdl") {
startActivityAndWait()
}
}

View file

@ -48,40 +48,13 @@ class App : Application() {
.getInt("concurrent_downloads", 1)))
.build())
configureRxJavaErrorHandler()
Completable.fromAction { initLibraries() }.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : DisposableCompletableObserver() {
override fun onComplete() {
// it worked
}
override fun onError(e: Throwable) {
if (BuildConfig.DEBUG) Log.e(TAG, "failed to initialize youtubedl-android", e)
Toast.makeText(
applicationContext,
"initialization failed: " + e.localizedMessage,
Toast.LENGTH_SHORT
).show()
}
})
}
private fun configureRxJavaErrorHandler() {
var err = Throwable()
RxJavaPlugins.setErrorHandler { e: Throwable ->
if (e is UndeliverableException) {
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
err = e.cause!!
}
if (e is InterruptedException) {
// fine, some blocking code was interrupted by a dispose call
return@setErrorHandler
}
Log.e(TAG, "Undeliverable exception received, not sure what to do", err)
try {
initLibraries()
}catch (e: Exception){
Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show()
e.printStackTrace()
}
}
@Throws(YoutubeDLException::class)
private fun initLibraries() {
YoutubeDL.getInstance().init(this)

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdlnis
import android.Manifest
import android.app.ActivityManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
@ -21,11 +20,9 @@ import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.databinding.ActivityMainBinding
import com.deniscerri.ytdlnis.service.IDownloaderListener
import com.deniscerri.ytdlnis.service.IDownloaderService
import com.deniscerri.ytdlnis.ui.DownloadFragment
import com.deniscerri.ytdlnis.ui.HistoryFragment
import com.deniscerri.ytdlnis.ui.HomeFragment
import com.deniscerri.ytdlnis.ui.MoreFragment
@ -47,7 +44,6 @@ class MainActivity : AppCompatActivity() {
lateinit var context: Context
private lateinit var homeFragment: HomeFragment
private lateinit var historyFragment: HistoryFragment
private lateinit var downloadFragment: DownloadFragment
private lateinit var workManager: WorkManager
override fun onCreate(savedInstanceState: Bundle?) {
@ -63,7 +59,6 @@ class MainActivity : AppCompatActivity() {
homeFragment = HomeFragment()
historyFragment = HistoryFragment()
downloadFragment = DownloadFragment()
moreFragment = MoreFragment()
initFragments()
binding.bottomNavigationView.setOnItemSelectedListener { item: MenuItem ->
@ -79,18 +74,10 @@ class MainActivity : AppCompatActivity() {
R.id.history -> {
if (lastFragment === historyFragment) {
historyFragment.scrollToTop()
} else {
this.title = getString(R.string.history)
}
replaceFragment(historyFragment)
}
R.id.downloads -> {
if (lastFragment === downloadFragment) {
downloadFragment.scrollToTop()
} else {
this.title = getString(R.string.downloads)
}
replaceFragment(downloadFragment)
replaceFragment(historyFragment)
}
R.id.more -> {
if (lastFragment === moreFragment) {
@ -129,11 +116,14 @@ class MainActivity : AppCompatActivity() {
Log.e(TAG, action)
homeFragment = HomeFragment()
historyFragment = HistoryFragment()
downloadFragment = DownloadFragment()
moreFragment = MoreFragment()
if (type.equals("application/txt", ignoreCase = true)) {
try {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
val uri = if (Build.VERSION.SDK_INT >= 33){
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
}else{
intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
val `is` = contentResolver.openInputStream(uri!!)
val textBuilder = StringBuilder()
val reader: Reader = BufferedReader(
@ -164,11 +154,9 @@ class MainActivity : AppCompatActivity() {
fm.beginTransaction()
.replace(R.id.frame_layout, homeFragment)
.add(R.id.frame_layout, historyFragment)
.add(R.id.frame_layout, downloadFragment)
.add(R.id.frame_layout, moreFragment)
.hide(historyFragment)
.hide(moreFragment)
.hide(downloadFragment)
.commit()
lastFragment = homeFragment
listeners = ArrayList()
@ -179,106 +167,10 @@ class MainActivity : AppCompatActivity() {
lastFragment = f
}
fun startDownloadService(
downloadQueue: ArrayList<ResultItem>,
awaitingListener: IDownloaderListener
) {
// addQueueToDownloads(downloadQueue)
// if (isDownloadServiceRunning) {
// iDownloaderService?.updateQueue(downloadQueue)
// return
// }
// if (!listeners.contains(awaitingListener)) listeners.add(awaitingListener)
// val serviceIntent = Intent(context, DownloaderService::class.java)
// serviceIntent.putParcelableArrayListExtra("queue", downloadQueue)
// context.applicationContext.startService(serviceIntent)
// context.applicationContext.bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE)
}
private fun addQueueToDownloads(downloadQueue: ArrayList<ResultItem>) {
// try {
// val sharedPreferences = context.getSharedPreferences("root_preferences", MODE_PRIVATE)
// if (!sharedPreferences.getBoolean("incognito", false)) {
// val databaseManager = DatabaseManager(context)
// for (i in downloadQueue.indices.reversed()) {
// val v = downloadQueue[i]
// v.isQueuedDownload = true
// databaseManager.addToHistory(v)
// }
// databaseManager.close()
// //downloadsFragment.initCards()
// }
// } catch (e: Exception) {
// e.printStackTrace()
// }
}
fun stopDownloadService() {
// if (!isDownloadServiceRunning) return
// try {
// iDownloaderService!!.removeActivity(this)
// context.applicationContext.unbindService(serviceConnection)
// context.applicationContext.stopService(
// Intent(
// context.applicationContext,
// DownloaderService::class.java
// )
// )
// } catch (ignored: Exception) {
// }
// isDownloadServiceRunning = false
}
fun cancelDownloadService() {
if (!isDownloadServiceRunning) return
iDownloaderService!!.cancelDownload(true)
stopDownloadService()
}
fun cancelAllDownloads() {
workManager.cancelAllWork();
}
fun removeItemFromDownloadQueue(video: ResultItem?, type: String?) {
iDownloaderService!!.removeItemFromDownloadQueue(video, type)
}
fun isDownloadServiceRunning(): Boolean {
// val service = getService(DownloaderService::class.java)
// if (service != null) {
// if (service.foreground) {
// isDownloadServiceRunning = true
// return true
// }
// }
return false
}
private fun reconnectDownloadService() {
// val service = getService(DownloaderService::class.java)
// if (service != null) {
// val serviceIntent = Intent(context.applicationContext, DownloaderService::class.java)
// serviceIntent.putExtra("rebind", true)
// context.applicationContext.bindService(
// serviceIntent,
// serviceConnection,
// BIND_AUTO_CREATE
// )
// isDownloadServiceRunning = true
// }
}
private fun getService(className: Class<*>): ActivityManager.RunningServiceInfo? {
val manager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Int.MAX_VALUE)) {
if (className.name == service.service.className) {
return service
}
}
return null
}
private fun checkUpdate() {
val preferences = context.getSharedPreferences("root_preferences", MODE_PRIVATE)
if (preferences.getBoolean("update_app", false)) {

View file

@ -6,6 +6,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import java.util.Calendar
class Converters {

View file

@ -49,4 +49,7 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE url=:url AND (status='Error' OR status='Cancelled') LIMIT 1")
fun checkIfErrorOrCancelled(url: String) : DownloadItem
@Query("SELECT * FROM downloads WHERE url=:url AND format=:format AND (status='Error' OR status='Cancelled') LIMIT 1")
fun getUnfinishedByURLAndFormat(url: String, format: String) : DownloadItem
}

View file

@ -4,6 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import java.util.*
@Entity(tableName = "downloads")
data class DownloadItem(
@ -16,8 +17,6 @@ data class DownloadItem(
val duration: String,
var type: DownloadViewModel.Type,
var format: Format,
@ColumnInfo(defaultValue = "0")
var removeAudio: Boolean,
var downloadPath: String,
val website: String,
val downloadSize: String,
@ -26,5 +25,7 @@ data class DownloadItem(
var addChapters: Boolean,
var SaveThumb: Boolean,
@ColumnInfo(defaultValue = "Queued")
var status: String
var status: String,
@ColumnInfo(defaultValue = "0")
var downloadStartTime: Long
)

View file

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis.database.repository
import androidx.lifecycle.LiveData
import com.deniscerri.ytdlnis.database.Converters
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
@ -49,7 +50,18 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.queueAllProcessing()
}
fun checkIfPresent(item: ResultItem): DownloadItem{
fun checkIfPresentForProcessing(item: ResultItem): DownloadItem{
return downloadDao.checkIfErrorOrCancelled(item.url)
}
fun checkIfReDownloadingErroredOrCancelled(item: DownloadItem) : Long {
val converters = Converters()
val format = converters.formatToString(item.format)
return try {
val i = downloadDao.getUnfinishedByURLAndFormat(item.url, format)
i.id
}catch (e: Exception){
0L
}
}
}

View file

@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Activity
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@ -22,6 +23,8 @@ import com.deniscerri.ytdlnis.work.DownloadWorker
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
import java.util.concurrent.TimeUnit
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
private val repository : DownloadRepository
@ -31,8 +34,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val activeDownloads : LiveData<List<DownloadItem>>
val processingDownloads : LiveData<List<DownloadItem>>
private var bestVideoFormat: Format
private var bestAudioFormat: Format
private var bestVideoFormat : Format
private var bestAudioFormat : Format
enum class Type {
audio, video, command
}
@ -90,8 +93,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
resultItem.thumb,
resultItem.duration,
type,
getFormat(resultItem, type), false,
"", resultItem.website, "", resultItem.playlistTitle, embedSubs, addChapters, saveThumb, DownloadRepository.Status.Processing.toString()
getFormat(resultItem, type),
"", resultItem.website, "", resultItem.playlistTitle, embedSubs, addChapters, saveThumb, DownloadRepository.Status.Processing.toString(), 0
)
}
@ -133,7 +136,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
items.forEachIndexed { i, it ->
val tmpDownloadItem = downloadItems[i]
try {
val item = repository.checkIfPresent(it!!)
val item = repository.checkIfPresentForProcessing(it!!)
tmpDownloadItem.id = item.id
tmpDownloadItem.status = DownloadRepository.Status.Processing.toString()
repository.update(tmpDownloadItem)
@ -162,15 +165,24 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val context = getApplication<App>().applicationContext
items.forEach {
it.status = DownloadRepository.Status.Queued.toString()
it.id = repository.checkIfReDownloadingErroredOrCancelled(it)
if (it.id == 0L){
val id = repository.insert(it)
it.id = id
}else repository.update(it)
val currentTime = System.currentTimeMillis()
var delay = if (it.downloadStartTime != 0L){
it.downloadStartTime - currentTime
} else 0
if (delay < 0L) delay = 0L
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(Data.Builder().putLong("id", it.id).build())
.addTag("download")
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(context).beginUniqueWork(
it.id.toString(),
ExistingWorkPolicy.KEEP,

View file

@ -19,7 +19,7 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() {
if (message != null) {
val notificationUtil = NotificationUtil(c)
YoutubeDL.getInstance().destroyProcessById(id.toString());
YoutubeDL.getInstance().destroyProcessById(id.toString())
WorkManager.getInstance(c).cancelUniqueWork(id.toString())
notificationUtil.cancelDownloadNotification(id)
}

View file

@ -1,282 +0,0 @@
package com.deniscerri.ytdlnis.ui
import android.app.Activity
import android.content.*
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.*
import android.view.View.*
import android.widget.*
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
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.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
import com.deniscerri.ytdlnis.util.FileUtil
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import java.io.File
/**
* A fragment representing a list of Items.
*/
class DownloadFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, QueuedDownloadAdapter.OnItemClickListener,
OnClickListener, OnLongClickListener {
private lateinit var downloadViewModel : DownloadViewModel
private var downloading = false
private var fragmentView: View? = null
private var activity: Activity? = null
private var mainActivity: MainActivity? = null
private var fragmentContext: Context? = null
private var layoutinflater: LayoutInflater? = null
private var shimmerCards: ShimmerFrameLayout? = null
private var topAppBar: MaterialToolbar? = null
private var activeRecyclerView: RecyclerView? = null
private var othersRecyclerView: RecyclerView? = null
private var downloadsAdapter: ActiveDownloadAdapter? = null
private var queuedDownloadsAdapter: QueuedDownloadAdapter? = null
private var bottomSheet: BottomSheetDialog? = null
private var sortSheet: BottomSheetDialog? = null
private var uiHandler: Handler? = null
private var noResults: RelativeLayout? = null
private var websiteGroup: ChipGroup? = null
private var downloadsList: List<DownloadItem?>? = null
private var allDownloadsList: List<DownloadItem?>? = null
private var selectedObjects: ArrayList<DownloadItem>? = null
private var deleteFab: ExtendedFloatingActionButton? = null
private var fileUtil: FileUtil? = null
private var _binding : FragmentHistoryBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentHistoryBinding.inflate(inflater, container, false)
fragmentView = inflater.inflate(R.layout.fragment_downloads, container, false)
activity = getActivity()
mainActivity = activity as MainActivity?
return fragmentView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fragmentContext = context
layoutinflater = LayoutInflater.from(context)
topAppBar = view.findViewById(R.id.downloads_toolbar)
noResults = view.findViewById(R.id.no_results)
websiteGroup = view.findViewById(R.id.website_chip_group)
deleteFab = view.findViewById(R.id.delete_selected_fab)
fileUtil = FileUtil()
deleteFab?.tag = "deleteSelected"
deleteFab?.setOnClickListener(this)
uiHandler = Handler(Looper.getMainLooper())
selectedObjects = ArrayList()
downloading = mainActivity!!.isDownloadServiceRunning()
downloadsList = mutableListOf()
allDownloadsList = mutableListOf()
downloadsAdapter =
ActiveDownloadAdapter(
this,
requireActivity()
)
activeRecyclerView = view.findViewById(R.id.recyclerviewactivedownloads)
activeRecyclerView?.layoutManager = LinearLayoutManager(context)
activeRecyclerView?.adapter = downloadsAdapter
queuedDownloadsAdapter =
QueuedDownloadAdapter(
this,
requireActivity()
)
othersRecyclerView = view.findViewById(R.id.recyclerviewotherdownloads)
othersRecyclerView?.layoutManager = LinearLayoutManager(context)
othersRecyclerView?.adapter = queuedDownloadsAdapter
noResults?.visibility = GONE
shimmerCards?.visibility = GONE
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
downloadViewModel.activeDownloads.observe(viewLifecycleOwner){
// update active
}
downloadViewModel.queuedDownloads.observe(viewLifecycleOwner){
// update queued
}
}
fun scrollToTop() {
activeRecyclerView!!.scrollToPosition(0)
Handler(Looper.getMainLooper()).post {
(topAppBar!!.parent as AppBarLayout).setExpanded(
true,
true
)
}
}
override fun onLongClick(v: View): Boolean {
val id = v.id
if (id == R.id.bottom_sheet_link) {
copyLinkToClipBoard(v.tag as Long)
return true
}
return false
}
private fun removeSelectedItems() {
// if (bottomSheet != null) bottomSheet!!.hide()
// val deleteFile = booleanArrayOf(false)
// val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
// deleteDialog.setMultiChoiceItems(
// arrayOf(getString(R.string.delete_files_too)),
// booleanArrayOf(false)
// ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
// for (item in selectedObjects!!){
// historyViewModel.delete(item, deleteFile[0])
// }
// selectedObjects = ArrayList()
// historyAdapter!!.clearCheckeditems()
// deleteFab!!.visibility = GONE
// }
// deleteDialog.show()
}
private fun removeItem(id: Long) {
// if (bottomSheet != null) bottomSheet!!.hide()
// val deleteFile = booleanArrayOf(false)
// val v = findItem(id)
// val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + v!!.title + "\"!")
// deleteDialog.setMultiChoiceItems(
// arrayOf(getString(R.string.delete_file_too)),
// booleanArrayOf(false)
// ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
// historyViewModel.delete(v, deleteFile[0])
// }
// deleteDialog.show()
}
private fun copyLinkToClipBoard(id: Long) {
val url = findItem(id)?.url
val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.url), url)
clipboard.setPrimaryClip(clip)
if (bottomSheet != null) bottomSheet!!.hide()
Toast.makeText(context, getString(R.string.link_copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
private fun openLinkIntent(id: Long) {
val url = findItem(id)?.url
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
if (bottomSheet != null) bottomSheet!!.hide()
startActivity(i)
}
private fun openFileIntent(id: Long) {
val downloadPath = findItem(id)!!.downloadPath
val file = File(downloadPath)
val uri = FileProvider.getUriForFile(
fragmentContext!!,
fragmentContext!!.packageName + ".fileprovider",
file
)
val mime = mainActivity!!.contentResolver.getType(uri)
val i = Intent(Intent.ACTION_VIEW)
i.setDataAndType(uri, mime)
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(i)
}
override fun onCardClick(videoID: Long, isPresent: Boolean) {
bottomSheet = BottomSheetDialog(fragmentContext!!)
bottomSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet!!.setContentView(R.layout.history_item_details_bottom_sheet)
val video = findItem(videoID)
val title = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_title)
title!!.text = video!!.title
val author = bottomSheet!!.findViewById<TextView>(R.id.bottom_sheet_author)
author!!.text = video.author
val link = bottomSheet!!.findViewById<Button>(R.id.bottom_sheet_link)
val url = video.url
link!!.text = url
link.tag = videoID
link.setOnClickListener(this)
link.setOnLongClickListener(this)
val remove = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = videoID
remove.setOnClickListener(this)
val openFile = bottomSheet!!.findViewById<Button>(R.id.bottomsheet_open_file_button)
openFile!!.tag = videoID
openFile.setOnClickListener(this)
if (!isPresent) openFile.visibility = GONE
bottomSheet!!.show()
bottomSheet!!.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
override fun onCardSelect(videoID: Long, isChecked: Boolean) {
val item = findItem(videoID)
if (isChecked) selectedObjects!!.add(item!!)
else selectedObjects!!.remove(item)
if (selectedObjects!!.size > 1) {
deleteFab!!.visibility = VISIBLE
} else {
deleteFab!!.visibility = GONE
}
}
override fun onButtonClick(position: Int) {
// val vid = downloadsObjects!![position]
// try {
// //mainActivity!!.removeItemFromDownloadQueue(vid, vid!!.downloadedType)
// } catch (e: Exception) {
// val info = DownloadInfo()
// info.video = vid
// info.downloadType = vid!!.downloadedType
// }
}
private fun findItem(id : Long): DownloadItem? {
return downloadsList?.find { it?.id == id }
}
companion object {
private const val TAG = "downloadsFragment"
}
override fun onClick(p0: View?) {
TODO("Not yet implemented")
}
}

View file

@ -330,10 +330,14 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
val deleteFile = booleanArrayOf(false)
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
deleteDialog.setMultiChoiceItems(
arrayOf(getString(R.string.delete_file_too)),
booleanArrayOf(false)
) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
val path = item.downloadPath
val file = File(path)
if (file.exists() && path.isNotEmpty()) {
deleteDialog.setMultiChoiceItems(
arrayOf(getString(R.string.delete_file_too)),
booleanArrayOf(false)
) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
}
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
historyViewModel.delete(item, deleteFile[0])

View file

@ -95,7 +95,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
fileUtil = FileUtil()
uiHandler = Handler(Looper.getMainLooper())
selectedObjects = ArrayList()
downloading = mainActivity!!.isDownloadServiceRunning()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
@ -391,8 +390,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
downloadFabs!!.visibility = VISIBLE
} else {
downloadFabs!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![1]!!.playlistTitle != getString(R.string.trendingPlaylist)) {
if (resultsList!![1]!!.playlistTitle.isNotEmpty() && resultsList!![1]!!.playlistTitle != getString(R.string.trendingPlaylist)){
downloadAllFabCoordinator!!.visibility = VISIBLE
}else{
downloadAllFabCoordinator!!.visibility = GONE
}
}
}

View file

@ -35,7 +35,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
private var fragmentView: View? = null
private var activity: Activity? = null
private var mainActivity: MainActivity? = null
private lateinit var resultViewModel : ResultViewModel
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var fileUtil : FileUtil

View file

@ -1,13 +1,14 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.FrameLayout
import androidx.lifecycle.ViewModelProvider
import androidx.viewpager2.widget.ViewPager2
import com.deniscerri.ytdlnis.R
@ -16,10 +17,10 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout
import java.util.*
class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val type: Type) : BottomSheetDialogFragment() {
@ -103,6 +104,39 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
}
}
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
scheduleBtn.setOnClickListener{
val currentDate = Calendar.getInstance()
val date = Calendar.getInstance()
val datepicker = DatePickerDialog(
requireContext(),
{ view, year, monthOfYear, dayOfMonth ->
date[year, monthOfYear] = dayOfMonth
TimePickerDialog(context,
{ _, hourOfDay, minute ->
date[Calendar.HOUR_OF_DAY] = hourOfDay
date[Calendar.MINUTE] = minute
val item: DownloadItem = getDownloadItem();
item.downloadStartTime = date.timeInMillis
downloadViewModel.queueDownloads(listOf(item))
dismiss()
},
currentDate.get(Calendar.HOUR_OF_DAY),
currentDate.get(Calendar.MINUTE),
false
).show()
},
currentDate.get(Calendar.YEAR),
currentDate.get(Calendar.MONTH),
currentDate.get(Calendar.DATE)
)
datepicker.datePicker.minDate = System.currentTimeMillis() - 1000
datepicker.show()
}
val cancelBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_cancel_button)
cancelBtn.setOnClickListener{
dismiss()
@ -110,25 +144,29 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
download!!.setOnClickListener {
val item: DownloadItem = when(tabLayout.selectedTabPosition){
0 -> {
val f = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
f.downloadItem
}
1 -> {
val f = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment
f.downloadItem
}
else -> {
val f = fragmentManager.findFragmentByTag("f2") as DownloadCommandFragment
f.downloadItem
}
}
val item: DownloadItem = getDownloadItem();
downloadViewModel.queueDownloads(listOf(item))
dismiss()
}
}
private fun getDownloadItem() : DownloadItem{
when(tabLayout.selectedTabPosition){
0 -> {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
return f.downloadItem
}
1 -> {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
return f.downloadItem
}
else -> {
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
return f.downloadItem
}
}
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
cleanUp()

View file

@ -1,25 +1,48 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.launch
class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null
private var activity: Activity? = null
private var mainActivity: MainActivity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var fileUtil : FileUtil
lateinit var downloadItem : DownloadItem
private lateinit var saveDir : TextInputLayout
private lateinit var commandTemplateDao : CommandTemplateDao
lateinit var downloadItem: DownloadItem
override fun onCreateView(
inflater: LayoutInflater,
@ -30,13 +53,113 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
fragmentView = inflater.inflate(R.layout.fragment_download_command, container, false)
activity = getActivity()
mainActivity = activity as MainActivity?
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
downloadItem = downloadViewModel.createDownloadItemFromResult(resultItem, DownloadViewModel.Type.command)
fileUtil = FileUtil()
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
return fragmentView
}
//get formats
// val templates = repository.getTemplates()
// templates.forEach {
// formats.add(Format(0, item.id, it.title, "", 0, it.content))
// }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
val sharedPreferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
try {
val commands = commandTemplateDao.getAllTemplates()
val id = sharedPreferences.getLong("commandTemplate", commands[0].id)
val chosenCommand = commands.find { it.id == id }
downloadItem.format = Format(
chosenCommand!!.title,
"",
0,
chosenCommand.content
)
val templates = commandTemplateDao.getAllTemplates()
val templateTitles = templates.map {it.title}
val commandTemplates = view.findViewById<TextInputLayout>(R.id.template)
val autoCompleteTextView =
view.findViewById<AutoCompleteTextView>(R.id.template_textview)
autoCompleteTextView?.setAdapter(
ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
templateTitles
)
)
if (templateTitles.isNotEmpty()) {
autoCompleteTextView!!.setText(downloadItem.format.format_id, false)
}
(commandTemplates!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->
// TODO
}
val templateContent = view.findViewById<TextView>(R.id.template_content_textview)
templateContent.text = downloadItem.format.format_note
saveDir = view.findViewById(R.id.outputPath)
val downloadPath = sharedPreferences.getString(
"command_path",
getString(R.string.command_path)
)
downloadItem.downloadPath = downloadPath!!
saveDir.editText!!.setText(
fileUtil.formatPath(downloadPath)
)
saveDir.editText!!.isFocusable = false;
saveDir.editText!!.isClickable = true;
saveDir.editText!!.setOnClickListener {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
commandPathResultLauncher.launch(intent)
}
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
embedSubs!!.isChecked = embedSubs.isChecked
embedSubs.setOnClickListener {
downloadItem.embedSubs = embedSubs.isChecked
}
val addChapters = view.findViewById<Chip>(R.id.add_chapters)
addChapters!!.isChecked = addChapters.isChecked
addChapters.setOnClickListener{
downloadItem.addChapters = addChapters.isChecked
}
val saveThumbnail = view.findViewById<Chip>(R.id.save_thumbnail)
saveThumbnail!!.isChecked = saveThumbnail.isChecked
saveThumbnail.setOnClickListener {
downloadItem.SaveThumb = saveThumbnail.isChecked
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private var commandPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let {
activity?.contentResolver?.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
downloadItem.downloadPath = result.data?.data.toString()
//downloadviewmodel.updateDownload(downloadItem)
saveDir.editText?.setText(fileUtil.formatPath(result.data?.data.toString()), TextView.BufferType.EDITABLE)
}
}
}

View file

@ -1,7 +1,9 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
@ -31,12 +33,14 @@ import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener {
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View>
override fun onCreate(savedInstanceState: Bundle?) {
@ -58,6 +62,12 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
dialog.setContentView(view)
view.minimumHeight = resources.displayMetrics.heightPixels
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
behavior.skipCollapsed = true
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
listAdapter =
ConfigureMultipleDownloadsAdapter(
this,
@ -76,6 +86,39 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
}
}
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
scheduleBtn.setOnClickListener{
val currentDate = Calendar.getInstance()
val date = Calendar.getInstance()
val datepicker = DatePickerDialog(
requireContext(),
{ view, year, monthOfYear, dayOfMonth ->
date[year, monthOfYear] = dayOfMonth
TimePickerDialog(context,
{ _, hourOfDay, minute ->
date[Calendar.HOUR_OF_DAY] = hourOfDay
date[Calendar.MINUTE] = minute
items.forEach {
it.downloadStartTime = date.timeInMillis
}
downloadViewModel.queueDownloads(items)
dismiss()
},
currentDate.get(Calendar.HOUR_OF_DAY),
currentDate.get(Calendar.MINUTE),
false
).show()
},
currentDate.get(Calendar.YEAR),
currentDate.get(Calendar.MONTH),
currentDate.get(Calendar.DATE)
)
datepicker.datePicker.minDate = System.currentTimeMillis() - 1000
datepicker.show()
}
val cancelBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_cancel_button)
cancelBtn.setOnClickListener{
dismiss()

View file

@ -174,28 +174,23 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
embedSubs!!.isChecked = embedSubs.isChecked
embedSubs!!.isChecked = downloadItem.embedSubs
embedSubs.setOnClickListener {
downloadItem.embedSubs = embedSubs.isChecked
}
val addChapters = view.findViewById<Chip>(R.id.add_chapters)
addChapters!!.isChecked = addChapters.isChecked
addChapters!!.isChecked = downloadItem.addChapters
addChapters.setOnClickListener{
downloadItem.addChapters = addChapters.isChecked
}
val saveThumbnail = view.findViewById<Chip>(R.id.save_thumbnail)
saveThumbnail!!.isChecked = saveThumbnail.isChecked
saveThumbnail!!.isChecked = downloadItem.SaveThumb
saveThumbnail.setOnClickListener {
downloadItem.SaveThumb = saveThumbnail.isChecked
}
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
removeAudio.setOnClickListener {
downloadItem.removeAudio = removeAudio.isChecked
}
} catch (e: Exception) {
e.printStackTrace()
}

View file

@ -12,6 +12,7 @@ import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
@ -101,8 +102,12 @@ class DownloadWorker(
request.addOption("--sponsorblock-remove", filters)
}
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title));
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author));
if(downloadItem.title.isNotEmpty()){
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title));
}
if (downloadItem.author.isNotEmpty()){
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author));
}
when(type){
DownloadViewModel.Type.audio -> {
@ -152,13 +157,16 @@ class DownloadWorker(
if (embedSubs) {
request.addOption("--embed-subs", "")
}
val defaultFormats = context.resources.getStringArray(R.array.video_formats)
var videoFormatID = downloadItem.format.format_id
Log.e(TAG, videoFormatID)
var formatArgument = "bestvideo+bestaudio/best"
if (videoFormatID.isNotEmpty()) {
if (videoFormatID == "Best Quality") videoFormatID = "bestvideo"
else if (videoFormatID == "Worst Quality") videoFormatID = "worst"
formatArgument = videoFormatID + if (downloadItem.removeAudio) "" else "+bestaudio/$videoFormatID"
else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]"
formatArgument = videoFormatID + "+bestaudio/best/$videoFormatID"
}
Log.e(TAG, formatArgument)
request.addOption("-f", formatArgument)

View file

@ -8,8 +8,9 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:paddingTop="20dp"
android:paddingBottom="0dp"
android:paddingHorizontal="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/bottom_sheet_title"

View file

@ -96,8 +96,7 @@
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/save_dir"
app:errorEnabled="true">
android:hint="@string/save_dir">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"

View file

@ -11,76 +11,15 @@
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/title_textinput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:errorEnabled="true"
android:hint="@string/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/author_textinput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="40"
app:errorEnabled="true"
android:hint="@string/author"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="text"
android:layout_height="wrap_content"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/downloadContainer"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_weight="45"
android:paddingStart="10dp"
android:layout_height="wrap_content"
android:hint="@string/container">
<AutoCompleteTextView
android:id="@+id/container_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
app:simpleItems="@array/audio_containers"
/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/format"
android:id="@+id/template"
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:hint="@string/video_format">
android:hint="@string/commands">
<AutoCompleteTextView
android:id="@+id/format_textview"
android:id="@+id/template_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
@ -89,12 +28,25 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_marginBottom="10dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/template_content_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"/>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/outputPath"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/save_dir"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
@ -108,6 +60,7 @@
<LinearLayout
android:id="@+id/adjust_commands"
android:layout_width="wrap_content"
android:padding="10dp"
android:layout_height="wrap_content"
android:orientation="vertical"
>

View file

@ -95,7 +95,6 @@
android:id="@+id/outputPath"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true"
android:hint="@string/save_dir"
style="@style/Widget.Material3.TextInputLayout.FilledBox">
@ -109,6 +108,7 @@
<LinearLayout
android:id="@+id/adjust_video"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content"
android:orientation="vertical"
>
@ -120,46 +120,45 @@
android:layout_height="wrap_content"
android:text="@string/adjust_video" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
<HorizontalScrollView
android:layout_width="wrap_content"
app:singleLine="false"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/embed_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated"
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/embed_subtitles"/>
app:singleLine="false"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/add_chapters"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/add_chapter"/>
<com.google.android.material.chip.Chip
android:id="@+id/embed_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/embed_subtitles"/>
<com.google.android.material.chip.Chip
android:id="@+id/save_thumbnail"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/save_thumb"/>
<com.google.android.material.chip.Chip
android:id="@+id/add_chapters"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/add_chapter"/>
<com.google.android.material.chip.Chip
android:id="@+id/remove_audio"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/remove_audio"/>
<com.google.android.material.chip.Chip
android:id="@+id/save_thumbnail"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/save_thumb"/>
</com.google.android.material.chip.ChipGroup>
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
</LinearLayout>

View file

@ -18,7 +18,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:title="@string/history"
app:title="@string/downloads"
app:menu="@menu/history_menu"
android:theme="@style/Toolbar"
/>

View file

@ -4,12 +4,9 @@
<item android:title="@string/home"
android:icon="@drawable/ic_home"
android:id="@+id/home"/>
<item android:title="@string/history"
android:icon="@drawable/ic_history"
android:id="@+id/history"/>
<item android:title="@string/downloads"
android:icon="@drawable/ic_downloads"
android:id="@+id/downloads"/>
android:id="@+id/history"/>
<item android:title="@string/more"
android:icon="@drawable/ic_more"
android:id="@+id/more"/>

View file

@ -163,6 +163,6 @@
<string name="defaultValue">Default</string>
<string name="container">Container</string>
<string name="template">Template</string>
<string name="history">History</string>
<string name="logs">Logs</string>
<string name="commands">Commands</string>
</resources>

View file

@ -12,7 +12,7 @@ buildscript {
versionCode = versionMajor * 100000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
// dependency versions
appCompatVer = '1.6.0'
appCompatVer = '1.6.1'
junitVer = '4.13.2'
androidJunitVer = '1.1.5'
espressoVer = '3.5.1'
@ -22,7 +22,7 @@ buildscript {
// supports java 1.6
commonsCompressVer = '1.12'
youtubedlAndroidVer = "a3b971724d"
workVer = "2.7.1"
workVer = "2.8.0"
composeVer = "1.4.0-alpha02"
kotlinVer = "1.7.21"
coroutineVer = "1.6.4"