diff --git a/app/build.gradle b/app/build.gradle index 09936bfe..60d63085 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ def properties = new Properties() def versionMajor = 1 def versionMinor = 6 def versionPatch = 5 -def versionBuild = 0 // bump for dogfood builds, public betas, etc. +def versionBuild = 2 // bump for dogfood builds, public betas, etc. def versionExt = "" if (versionBuild > 0){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index 1fbea725..40af3bc3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -120,14 +120,14 @@ class MainActivity : BaseActivity() { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) -// val graph = navController.navInflater.inflate(R.navigation.nav_graph) -// graph.setStartDestination(R.id.homeFragment) -// when(sharedPreferences.getString("start_destination", "")) { -// "History" -> graph.setStartDestination(R.id.historyFragment) -// "More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment) -// } -// -// navController.graph = graph + val graph = navController.navInflater.inflate(R.navigation.nav_graph) + graph.setStartDestination(R.id.homeFragment) + when(sharedPreferences.getString("start_destination", "")) { + "History" -> graph.setStartDestination(R.id.historyFragment) + "More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment) + } + + navController.graph = graph if (navigationView is NavigationBarView){ (navigationView as NavigationBarView).setupWithNavController(navController) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/CookieAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/CookieAdapter.kt index 3e7ec1c5..c7ec1570 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/CookieAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/CookieAdapter.kt @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.CookieItem +import com.google.android.material.card.MaterialCardView class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) { private val onItemClickListener: OnItemClickListener @@ -23,10 +24,10 @@ class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { - val item: ConstraintLayout + val item: MaterialCardView init { - item = itemView.findViewById(R.id.command_template_item_constraint) + item = itemView.findViewById(R.id.command_card) } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt index 1071d67f..aa59eaad 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt @@ -54,7 +54,6 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView - card.isVisible = item != null if (item == null) return card.tag = item.id.toString() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/TemplatesAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/TemplatesAdapter.kt index 906a8490..2d99590d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/TemplatesAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/TemplatesAdapter.kt @@ -13,6 +13,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.CommandTemplate +import com.google.android.material.card.MaterialCardView class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) { private val onItemClickListener: OnItemClickListener @@ -24,10 +25,10 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { - val item: ConstraintLayout + val item: MaterialCardView init { - item = itemView.findViewById(R.id.command_template_item_constraint) + item = itemView.findViewById(R.id.command_card) } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt index c8fdeb4f..fcbcb661 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt @@ -4,7 +4,6 @@ import androidx.paging.PagingSource import androidx.room.* import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.Format -import com.deniscerri.ytdlnis.database.repository.DownloadRepository import kotlinx.coroutines.flow.Flow @Dao @@ -23,6 +22,9 @@ interface DownloadDao { fun getDownloadsCountByStatus(status : List) : Flow @Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'") + fun getActiveAndPausedDownloadsList() : List + + @Query("SELECT * FROM downloads WHERE status='Active'") fun getActiveDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'") @@ -64,6 +66,9 @@ interface DownloadDao { @Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC") fun getSavedDownloads() : PagingSource + @Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC") + fun getSavedDownloadsList() : List + @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") fun getDownloadById(id: Long) : DownloadItem @@ -119,6 +124,12 @@ interface DownloadDao { @Upsert suspend fun update(item: DownloadItem) + @Query("UPDATE downloads SET logID=null") + fun removeAllLogID() + + @Query("UPDATE downloads SET logID=null WHERE logID=:logID") + fun removeLogID(logID: Long) + @Upsert fun updateMultiple(items :List) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt index c5c84ee1..6214918f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt @@ -22,6 +22,13 @@ interface LogDao { @Query("DELETE FROM logs WHERE id=:itemId") suspend fun delete(itemId: Long) + @Transaction + suspend fun deleteItems(list: List){ + list.forEach{ + delete(it.id) + } + } + @Update(onConflict = OnConflictStrategy.REPLACE) suspend fun update(item: LogItem) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt index 2dd351ae..3661993a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt @@ -6,7 +6,7 @@ data class VideoPreferences ( var splitByChapters: Boolean = false, var sponsorBlockFilters: ArrayList = arrayListOf(), var writeSubs: Boolean = false, - var subsLanguages: String = "all", + var subsLanguages: String = "en.*,.*-orig", var audioFormatIDs : ArrayList = arrayListOf(), var removeAudio: Boolean = false ) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt index 206acc30..d7cbdf2e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt @@ -63,7 +63,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) { } fun getActiveDownloads() : List { - return downloadDao.getActiveDownloadsList() + return downloadDao.getActiveAndPausedDownloadsList() } fun getActiveAndQueuedDownloads() : List { @@ -110,4 +110,12 @@ class DownloadRepository(private val downloadDao: DownloadDao) { downloadDao.unPauseActiveAndQueued() } + fun removeLogID(logID: Long){ + downloadDao.removeLogID(logID) + } + + fun removeAllLogID(){ + downloadDao.removeAllLogID() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt index 66d54ad1..d76accbc 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt @@ -77,6 +77,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application private var audioContainer: String? private var videoContainer: String? private var videoCodec: String? + private val dao: DownloadDao enum class Type { audio, video, command @@ -84,7 +85,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application init { dbManager = DBManager.getInstance(application) - val dao = dbManager.downloadDao + dao = dbManager.downloadDao repository = DownloadRepository(dao) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) commandTemplateDao = DBManager.getInstance(application).commandTemplateDao @@ -157,13 +158,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application ) }else{ Format( - audioFormatIDPreference.first(), + audioFormatIDPreference.first().split("+").first(), audioContainer!!, "", "", "", 0, - audioFormatIDPreference.first() + audioFormatIDPreference.first().split("+").first() ) } @@ -220,8 +221,24 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!)) - val hasPreferredAudioFormats = resultItem.formats.filter { audioFormatIDPreference.contains(it.format_id) } - val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, audioFormatIDs = if (hasPreferredAudioFormats.isEmpty()) arrayListOf() else arrayListOf(hasPreferredAudioFormats.map { it.format_id }.first())) + val preferredAudioFormats = arrayListOf() + for (f in resultItem.formats.sortedBy { it.format_id }){ + val fId = audioFormatIDPreference.sorted().find { it.contains(f.format_id) } + if (fId != null) { + if (fId.split("+").all { resultItem.formats.map { f-> f.format_id }.contains(it) }){ + preferredAudioFormats.addAll(fId.split("+")) + break + } + } + } + + val videoPreferences = VideoPreferences( + embedSubs, + addChapters, false, + ArrayList(sponsorblock), + saveSubs, + audioFormatIDs = preferredAudioFormats + ) return DownloadItem(0, resultItem.url, @@ -368,10 +385,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application return cloneFormat ( try { try{ - formats.filter { it.format_note.contains("audio", ignoreCase = true)}.first { - audioFormatIDPreference.contains(it.format_id) || - it.container == audioContainer - } + formats.filter { it.format_note.contains("audio", ignoreCase = true)}.sortedBy { it.format_id } + .firstOrNull { audioFormatIDPreference.indexOfFirst { a -> a.contains(it.format_id) } >= 0 || it.container == audioContainer} ?: throw Exception() }catch (e: Exception){ formats.last { it.format_note.contains("audio", ignoreCase = true) } } @@ -384,14 +399,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application Type.video -> { return cloneFormat( try { - val theFormats = formats.filter { !it.format_note.contains("audio", true) }.ifEmpty { defaultVideoFormats } + val theFormats = formats.filter { !it.format_note.contains("audio", true) }.sortedBy { it.format_id }.ifEmpty { defaultVideoFormats } when (videoQualityPreference) { "worst" -> { theFormats.first() } else /*best*/ -> { val requirements: MutableList<(Format) -> Boolean> = mutableListOf() - requirements.add { it: Format -> formatIDPreference.contains(it.format_id) } + requirements.add { it: Format -> formatIDPreference.sorted().contains(it.format_id) } requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)} requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) } requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) } @@ -505,6 +520,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application return repository.getErroredDownloads() } + fun getSaved() : List { + return dao.getSavedDownloadsList() + } + private fun cloneFormat(item: Format) : Format { val string = Gson().toJson(item, Format::class.java) return Gson().fromJson(string, Format::class.java) @@ -567,9 +586,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } } - if (queuedItems.isNotEmpty()){ - startDownloadWorker(queuedItems) - } + startDownloadWorker(queuedItems) } private suspend fun startDownloadWorker(queuedItems: List) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt index 4229e3b2..80f8f09f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/LogViewModel.kt @@ -7,17 +7,19 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.models.LogItem +import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.LogRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class LogViewModel(private val application: Application) : AndroidViewModel(application) { private val repository: LogRepository + private val downloadRepository: DownloadRepository val items: LiveData> init { - val dao = DBManager.getInstance(application).logDao - repository = LogRepository(dao) + repository = LogRepository(DBManager.getInstance(application).logDao) + downloadRepository = DownloadRepository(DBManager.getInstance(application).downloadDao) items = repository.items.asLiveData() } @@ -36,10 +38,12 @@ class LogViewModel(private val application: Application) : AndroidViewModel(appl fun delete(item: LogItem) = viewModelScope.launch(Dispatchers.IO) { repository.delete(item) + downloadRepository.removeLogID(item.id) } fun deleteAll() = viewModelScope.launch(Dispatchers.IO) { repository.deleteAll() + downloadRepository.removeAllLogID() } fun update(newLine: String, id: Long) = viewModelScope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt index 8cf273bc..a242c19e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt @@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.util.Log import androidx.work.WorkManager import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.repository.DownloadRepository @@ -15,10 +16,10 @@ import kotlinx.coroutines.runBlocking class CancelDownloadNotificationReceiver : BroadcastReceiver() { override fun onReceive(c: Context, intent: Intent) { - val message = intent.getStringExtra("cancel") - val id = intent.getIntExtra("workID", 0) - if (message != null) { + val id = intent.getIntExtra("itemID", 0) + if (id > 0) { runCatching { + Log.e("aa", id.toString()) val notificationUtil = NotificationUtil(c) YoutubeDL.getInstance().destroyProcessById(id.toString()) notificationUtil.cancelDownloadNotification(id) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelWorkReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelWorkReceiver.kt new file mode 100644 index 00000000..3632fde5 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelWorkReceiver.kt @@ -0,0 +1,27 @@ +package com.deniscerri.ytdlnis.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.work.WorkManager +import com.deniscerri.ytdlnis.database.DBManager +import com.deniscerri.ytdlnis.database.repository.DownloadRepository +import com.deniscerri.ytdlnis.util.NotificationUtil +import com.yausername.youtubedl_android.YoutubeDL +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +class CancelWorkReceiver : BroadcastReceiver() { + override fun onReceive(c: Context, intent: Intent) { + val id = intent.getStringExtra("workID") + if (!id.isNullOrBlank()) { + runCatching { + WorkManager.getInstance(c).cancelAllWorkByTag(id) + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt index b1fb0ecc..54a21825 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt @@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.util.Log import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.util.NotificationUtil @@ -13,14 +14,14 @@ import kotlinx.coroutines.launch class PauseDownloadNotificationReceiver : BroadcastReceiver() { override fun onReceive(c: Context, intent: Intent) { - val id = intent.getIntExtra("workID", 0) + val id = intent.getIntExtra("itemID", 0) if (id != 0) { runCatching { val title = intent.getStringExtra("title") val notificationUtil = NotificationUtil(c) notificationUtil.cancelDownloadNotification(id) YoutubeDL.getInstance().destroyProcessById(id.toString()) - notificationUtil.createResumeDownload(id, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID) + notificationUtil.createResumeDownload(id, title) val dbManager = DBManager.getInstance(c) CoroutineScope(Dispatchers.IO).launch{ runCatching { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ResumeActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ResumeActivity.kt index 1492c117..d98ed547 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ResumeActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ResumeActivity.kt @@ -63,7 +63,7 @@ class ResumeActivity : BaseActivity() { } private fun handleIntents(intent: Intent) { - val id = intent.getIntExtra("workID", 0) + val id = intent.getIntExtra("itemID", 0) if (id != 0) { try { val loadingBottomSheet = BottomSheetDialog(this) @@ -71,13 +71,12 @@ class ResumeActivity : BaseActivity() { loadingBottomSheet.setContentView(R.layout.please_wait_bottom_sheet) loadingBottomSheet.setOnShowListener { - NotificationUtil(this).cancelDownloadNotification(NotificationUtil.DOWNLOAD_RESUME_NOTIFICATION_ID) + NotificationUtil(this).cancelDownloadNotification(NotificationUtil.DOWNLOAD_RESUME_NOTIFICATION_ID + id) lifecycleScope.launch { val downloadViewModel = ViewModelProvider(this@ResumeActivity)[DownloadViewModel::class.java] withContext(Dispatchers.IO){ - val downloadItem = downloadViewModel.getItemByID(id.toLong()) - downloadViewModel.queueDownloads(listOf(downloadItem)) - finishAffinity() + downloadViewModel.reQueueDownloadItems(listOf(id.toLong())) + finishAffinity() } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt index 37ec610f..a0239791 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -23,6 +23,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.children import androidx.core.view.forEach +import androidx.core.view.isVisible import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider @@ -427,11 +428,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene @SuppressLint("InflateParams") private suspend fun updateSearchViewItems(it: Editable?, linkYouCopied: View?){ + searchSuggestionsLinearLayout!!.visibility = GONE + searchHistoryLinearLayout!!.visibility = GONE searchSuggestionsLinearLayout!!.removeAllViews() searchHistoryLinearLayout!!.removeAllViews() - searchSuggestionsLinearLayout!!.visibility = GONE - searchHistoryLinearLayout!!.visibility = GONE linkYouCopied!!.visibility = GONE if (searchView!!.editText.text.isEmpty()){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt index eb72b99f..08f2e02a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt @@ -24,6 +24,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil +import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment @@ -95,29 +96,8 @@ class AddExtraCommandsDialog(private val item: DownloadItem?, private val callba view.findViewById(R.id.current).visibility = View.GONE } - - //init syntax highlighter - val highlight = Highlight() - val highlightWatcher = HighlightTextWatcher() - - val schemes = listOf( - ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")), - ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#00FF00")), - ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")), - ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564")) - ) - - highlight.addScheme( - *schemes.map { it }.toTypedArray() - ) - highlightWatcher.addScheme( - *schemes.map { it }.toTypedArray() - ) - highlight.setSpan(currentText) - currentText.addTextChangedListener(highlightWatcher) - - highlight.setSpan(text) - text.addTextChangedListener(highlightWatcher) + text.enableTextHighlight() + currentText.enableTextHighlight() text?.setText(item?.extraCommands) val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt index b003ad27..d6303e4a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt @@ -28,7 +28,9 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.UiUtil -import com.google.android.material.chip.Chip +import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight +import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard +import com.google.android.material.card.MaterialCardView import com.google.android.material.textfield.TextInputLayout import com.google.gson.Gson import kotlinx.coroutines.Dispatchers @@ -61,6 +63,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) lifecycleScope.launch { @@ -79,7 +82,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu } val chosenCommandView = view.findViewById(R.id.content) - chosenCommandView.editText!!.setText(downloadItem.format.format_note) + chosenCommandView.editText?.setText(downloadItem.format.format_note) + chosenCommandView.editText?.enableTextHighlight() chosenCommandView.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_delete_all) chosenCommandView.editText!!.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} @@ -113,25 +117,29 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu } chosenCommandView.editText!!.enableScrollText() - val commandTemplates = view.findViewById(R.id.template) - val autoCompleteTextView = - requireView().findViewById(R.id.template_textview) - populateCommandTemplates(templates, autoCompleteTextView) + val commandTemplateCard = view.findViewById(R.id.command_card) + populateCommandCard(commandTemplateCard, templates.first()) - (commandTemplates!!.editText as AutoCompleteTextView?)!!.onItemClickListener = - AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long -> - chosenCommandView.editText!!.setText(templates[index].content) - downloadItem.format = Format( - templates[index].title, - "", - "", - "", - "", - 0, - templates[index].content - ) + commandTemplateCard.setOnClickListener { + lifecycleScope.launch { + UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ + chosenCommandView.editText!!.setText(it.content) + populateCommandCard(commandTemplateCard, it) + downloadItem.format = Format( + it.title, + "", + "", + "", + "", + 0, + it.content + ) + } } + + } + saveDir = view.findViewById(R.id.outputPath) saveDir.editText!!.setText( FileUtil.formatPath(downloadItem.downloadPath) @@ -151,9 +159,14 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace )) + val shortcutCount = withContext(Dispatchers.IO){ + commandTemplateViewModel.getTotalShortcutNumber() + } + UiUtil.configureCommand( view, 1, + shortcutCount, newTemplateClicked = { UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) { templates.add(it) @@ -167,16 +180,24 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu 0, it.content ) - populateCommandTemplates(templates, autoCompleteTextView) + populateCommandCard(commandTemplateCard, it) } }, - editSelectedClicked = { - var current = templates.find { it.title == autoCompleteTextView.text.toString() } - if (current == null) current = CommandTemplate(0, "", chosenCommandView.editText!!.text.toString(), false) + editSelectedClicked = + { + var current = templates.find { it.id == commandTemplateCard.findViewById( + R.id.id).toString().toLong() } + if (current == null) current = + CommandTemplate( + 0, + "", + chosenCommandView.editText!!.text.toString(), + false + ) UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) { templates.add(it) chosenCommandView.editText!!.setText(it.content) - downloadItem.format = Format( + downloadItem.format = com.deniscerri.ytdlnis.database.models.Format( it.title, "", "", @@ -186,29 +207,22 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu it.content ) } + }, + shortcutClicked = { + UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel) { sh -> + chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $sh") + downloadItem.format.format_note += " $sh" + } } ) + + } catch (e: Exception) { e.printStackTrace() } } } - private fun populateCommandTemplates(templates: List, autoCompleteTextView: AutoCompleteTextView?){ - val templateTitles = templates.map {it.title} - - - autoCompleteTextView?.setAdapter( - ArrayAdapter( - requireContext(), - android.R.layout.simple_dropdown_item_1line, - templateTitles - ) - ) - if (templateTitles.isNotEmpty()) { - autoCompleteTextView!!.setText(downloadItem.format.format_id, false) - } - } private var commandPathResultLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt index 337e344d..50bfec2b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt @@ -75,34 +75,27 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis pauseResume.setOnClickListener { if (pauseResume.text == requireContext().getString(R.string.pause)){ - lifecycleScope.launch { + workManager.cancelAllWorkByTag("download") pauseResume.isEnabled = false - val queued = withContext(Dispatchers.IO){ - downloadViewModel.getQueued() - } - queued.forEach { - it.status = DownloadRepository.Status.QueuedPaused.toString() - runBlocking { - downloadViewModel.updateDownload(it) - } - } - - queued.forEach { - workManager.cancelAllWorkByTag(it.id.toString()) - } - - val active = withContext(Dispatchers.IO){ + // pause active + withContext(Dispatchers.IO){ downloadViewModel.getActiveDownloads() - } - - active.forEach { + }.forEach { cancelItem(it.id.toInt()) it.status = DownloadRepository.Status.Paused.toString() downloadViewModel.updateDownload(it) } + // pause queued + withContext(Dispatchers.IO){ + downloadViewModel.getQueued() + }.forEach { + it.status = DownloadRepository.Status.QueuedPaused.toString() + downloadViewModel.updateDownload(it) + } + activeDownloads.notifyDataSetChanged() pauseResume.isEnabled = true } @@ -116,17 +109,22 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis val toQueue = active.filter { it.status == DownloadRepository.Status.Paused.toString() }.toMutableList() toQueue.forEach { - it.status = DownloadRepository.Status.Active.toString() + it.status = DownloadRepository.Status.Queued.toString() downloadViewModel.updateDownload(it) } + + runBlocking { + downloadViewModel.queueDownloads(listOf()) + } + val queuedItems = withContext(Dispatchers.IO){ downloadViewModel.getQueued() } - queuedItems.map { it.status = DownloadRepository.Status.Queued.toString() } - toQueue.addAll(queuedItems) - runBlocking { - downloadViewModel.queueDownloads(toQueue) + queuedItems.map { + it.status = DownloadRepository.Status.Queued.toString() + downloadViewModel.updateDownload(it) } + pauseResume.isEnabled = true } } @@ -220,20 +218,15 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis } activeDownloads.notifyItemChanged(position) - val count = withContext(Dispatchers.IO){ - downloadViewModel.getActiveDownloads() - }.count() - - val queue = if (count > 1) listOf(item) - else withContext(Dispatchers.IO){ - val list = downloadViewModel.getQueued().toMutableList() - list.map { it.status = DownloadRepository.Status.Queued.toString() } - list.add(0, item) - list + runBlocking { + downloadViewModel.queueDownloads(listOf(item)) } - runBlocking { - downloadViewModel.queueDownloads(queue) + withContext(Dispatchers.IO){ + downloadViewModel.getQueued().filter { it.status != DownloadRepository.Status.Queued.toString() }.forEach { + it.status = DownloadRepository.Status.Queued.toString() + downloadViewModel.updateDownload(it) + } } } } @@ -255,7 +248,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis private fun cancelItem(id: Int){ YoutubeDL.getInstance().destroyProcessById(id.toString()) - WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString()) notificationUtil.cancelDownloadNotification(id) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt index 2502a49e..7ca68dfb 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt @@ -35,6 +35,7 @@ import com.deniscerri.ytdlnis.ui.BaseActivity import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.UiUtil +import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight import com.deniscerri.ytdlnis.work.TerminalDownloadWorker import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomappbar.BottomAppBar @@ -230,27 +231,8 @@ class TerminalActivity : BaseActivity() { } initMenu() - //init syntax highlighter - val highlight = Highlight() - val highlightWatcher = HighlightTextWatcher() - - val schemes = listOf( - ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")), - ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#00FF00")), - ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")), - ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564")) - ) - - highlight.addScheme( - *schemes.map { it }.toTypedArray() - ) - highlightWatcher.addScheme( - *schemes.map { it }.toTypedArray() - ) - highlight.setSpan(output) - highlight.setSpan(input) - input?.addTextChangedListener(highlightWatcher) - output?.addTextChangedListener(highlightWatcher) + input?.enableTextHighlight() + output?.enableTextHighlight() } private fun initMenu() { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt index 73b199b7..423d79ef 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt @@ -113,14 +113,14 @@ class FolderSettingsFragment : BaseSettingsFragment() { audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]" audioFilenameTemplate?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]" - var cacheSize = File(FileUtil.getCachePath()).walkBottomUp().fold(0L) { acc, file -> acc + file.length() } + var cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() } clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}" clearCache!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { if (activeDownloadCount == 0){ - File(FileUtil.getCachePath()).deleteRecursively() + File(FileUtil.getCachePath(requireContext())).deleteRecursively() Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show() - cacheSize = File(FileUtil.getCachePath()).walkBottomUp().fold(0L) { acc, file -> acc + file.length() } + cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() } clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}" }else{ Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt index d79515b1..41a9274d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt @@ -157,6 +157,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() { "queued" -> json.add("queued", backupQueuedDownloads() ) "cancelled" -> json.add("cancelled", backupCancelledDownloads() ) "errored" -> json.add("errored", backupErroredDownloads() ) + "saved" -> json.add("saved", backupSavedDownloads() ) "cookies" -> json.add("cookies", backupCookies() ) "templates" -> json.add("templates", backupCommandTemplates() ) "shortcuts" -> json.add("shortcuts", backupShortcuts() ) @@ -293,6 +294,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() { return JsonArray() } + private suspend fun backupSavedDownloads() : JsonArray { + runCatching { + val items = withContext(Dispatchers.IO) { + downloadViewModel.getSaved() + } + val arr = JsonArray() + items.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + private suspend fun backupCookies() : JsonArray { runCatching { val items = withContext(Dispatchers.IO) { @@ -484,6 +499,28 @@ class MainSettingsFragment : PreferenceFragmentCompat() { } } + //erorred downloads restore + if(json.has("saved")){ + val items = json.getAsJsonArray("saved") + val saved = mutableListOf() + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java) + item.id = 0L + saved.add(item) + + } + + saved.asReversed().forEach { f -> + withContext(Dispatchers.IO){ + downloadViewModel.insert(f) + } + } + + if(saved.isNotEmpty()){ + finalMessage.append("${getString(R.string.saved)}: ${saved.count()}\n") + } + } + //cookies restore if(json.has("cookies")){ val items = json.getAsJsonArray("cookies") diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt index 26353632..78a20f77 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt @@ -5,13 +5,9 @@ import android.media.MediaScannerConnection import android.net.Uri import android.os.Build import android.os.Environment -import android.os.storage.StorageManager -import android.os.storage.StorageVolume import android.provider.DocumentsContract import android.util.Log import android.webkit.MimeTypeMap -import androidx.core.content.FileProvider -import androidx.core.net.toFile import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import com.anggrayudi.storage.callback.FileCallback @@ -19,17 +15,15 @@ import com.anggrayudi.storage.callback.FolderCallback import com.anggrayudi.storage.file.copyFileTo import com.anggrayudi.storage.file.copyFolderTo import com.anggrayudi.storage.file.getAbsolutePath -import com.anggrayudi.storage.file.getBasePath import com.deniscerri.ytdlnis.App -import com.deniscerri.ytdlnis.BuildConfig import com.deniscerri.ytdlnis.R +import com.yausername.youtubedl_android.YoutubeDLRequest import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import java.io.File import java.net.URLDecoder import java.nio.charset.StandardCharsets -import java.nio.file.CopyOption import java.nio.file.Files import java.nio.file.StandardCopyOption import java.text.DecimalFormat @@ -91,8 +85,7 @@ object FileUtil { if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach var destFile: DocumentFile try { - if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)|(live_chat)".toRegex())){ - it.delete() + if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(.*.part-Frag)|(.*.live_chat)".toRegex())){ return@forEach } @@ -269,8 +262,17 @@ object FileUtil { return listOf(context.getString(R.string.unfound_file)) } - fun getCachePath() : String { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/.CACHE" + fun getCachePath(context: Context) : String { + return context.cacheDir.absolutePath + "/downloads/" + } + + fun deleteConfigFiles(request: YoutubeDLRequest) { + request.getArguments("--config")?.forEach { + if (it != null) File(it).delete() + } + request.getArguments("--config-locations")?.forEach { + if (it != null) File(it).delete() + } } fun getDefaultAudioPath() : String{ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt index 02903f87..fb11ce81 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -13,7 +13,6 @@ 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.work.DownloadWorker import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.yausername.youtubedl_android.YoutubeDL @@ -546,6 +545,7 @@ class InfoUtil(private val context: Context) { fun getFromYTDL(query: String): ArrayList { items = ArrayList() + val webpage_urls = mutableListOf() val searchEngine = sharedPreferences.getString("search_engine", "ytsearch") try { val request : YoutubeDLRequest @@ -610,12 +610,6 @@ class InfoUtil(private val context: Context) { } } - val url = if (jsonObject.has("url")){ - jsonObject.getString("url") - }else{ - jsonObject.getString("webpage_url") - } - var thumb: String? = "" if (jsonObject.has("thumbnail")) { thumb = jsonObject.getString("thumbnail") @@ -652,6 +646,12 @@ class InfoUtil(private val context: Context) { urls = urlList.joinToString("\n") } + val url = if (jsonObject.has("url")){ + jsonObject.getString("url") + }else{ + jsonObject.getString("webpage_url") + } + items.add(ResultItem(0, url, title, @@ -665,6 +665,10 @@ class InfoUtil(private val context: Context) { chapters ) ) + webpage_urls.add(jsonObject.getString("webpage_url")) + } + if (items.size == 1){ + items[0]?.url = webpage_urls.first() } } catch (e: Exception) { Looper.prepare().run { @@ -929,7 +933,7 @@ class InfoUtil(private val context: Context) { } fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{ - val cacheDir = FileUtil.getCachePath() + val cacheDir = FileUtil.getCachePath(context) val tempFileDir = File(cacheDir, downloadItem.id.toString()) tempFileDir.delete() tempFileDir.mkdirs() @@ -1009,7 +1013,10 @@ class InfoUtil(private val context: Context) { request.addOption("--force-keyframes-at-cuts") } } - downloadItem.customFileNameTemplate += " %(section_title|)s %(autonumber)s" + downloadItem.customFileNameTemplate += " %(section_title|)s " + if (downloadItem.downloadSections.split(";").size > 1){ + downloadItem.customFileNameTemplate += "%(autonumber)s" + } request.addOption("--output-na-placeholder", " ") } @@ -1018,7 +1025,7 @@ class InfoUtil(private val context: Context) { } if (downloadItem.extraCommands.isNotBlank()){ - val conf = File(tempFileDir.absolutePath + "/${System.currentTimeMillis()}.txt") + val conf = File(FileUtil.getCachePath(context) + "/${System.currentTimeMillis()}.txt") conf.createNewFile() conf.writeText(downloadItem.extraCommands) request.addOption( @@ -1097,7 +1104,7 @@ class InfoUtil(private val context: Context) { if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg") if (sharedPreferences.getBoolean("crop_thumbnail", true)){ try { - val config = File(tempFileDir.absolutePath + "/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt") + val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt") val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"" config.writeText(configData) request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1") @@ -1129,39 +1136,18 @@ class InfoUtil(private val context: Context) { } if (downloadItem.videoPreferences.embedSubs) { request.addOption("--embed-subs") - request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages) + request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" }) } - val defaultFormats = context.resources.getStringArray(R.array.video_formats) - if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()) request.addOption("--audio-multistreams") - - var videoFormatID = downloadItem.format.format_id - Log.e(DownloadWorker.TAG, videoFormatID) - var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best" - if (videoFormatID.isNotEmpty()) { - if (videoFormatID == context.resources.getString(R.string.best_quality) || videoFormatID == "best") videoFormatID = "bestvideo" - else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst" - else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]" - - formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty() && ! downloadItem.videoPreferences.removeAudio){ - val audioIds = downloadItem.videoPreferences.audioFormatIDs.joinToString("+") - "$videoFormatID+$audioIds/$videoFormatID/best" - }else{ - "$videoFormatID+bestaudio/$videoFormatID/best" - } - } - Log.e(DownloadWorker.TAG, formatArgument) - request.addOption("-f", formatArgument) - - if (downloadItem.format.container.equals("mpeg_4", true)) downloadItem.format.container = "mp4" + var cont = "" val outputContainer = downloadItem.container if( outputContainer.isNotEmpty() && outputContainer != "Default" && outputContainer != context.getString(R.string.defaultValue) && - supportedContainers.contains(outputContainer) && - !outputContainer.equals(downloadItem.format.container, true) + supportedContainers.contains(outputContainer) ){ + cont = outputContainer request.addOption("--merge-output-format", outputContainer.lowercase()) if (outputContainer != "webm") { val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) @@ -1171,6 +1157,103 @@ class InfoUtil(private val context: Context) { } } + //format logic + + val defaultFormats = context.resources.getStringArray(R.array.video_formats) + var usingGenericFormat = false + + var videof = downloadItem.format.format_id + if (videof == context.resources.getString(R.string.best_quality) || videof == "best") { + videof = "bestvideo" + usingGenericFormat = true + }else if (videof == context.resources.getString(R.string.worst_quality) || videof == "worst") { + videof = "worst" + }else if (defaultFormats.contains(videof)) { + videof = "bestvideo[height<="+videof.substring(0, videof.length -1)+"]" + } + + val preferredFormatIDs = sharedPreferences.getString("format_id", "").toString() + .split(",") + .filter { it.isNotEmpty() } + .ifEmpty { listOf(videof) } + + var audiof = "bestaudio" + if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()){ + audiof = downloadItem.videoPreferences.audioFormatIDs.joinToString("+") + } + if (downloadItem.videoPreferences.removeAudio) audiof = "" + + val preferredAudioFormatIDs = sharedPreferences.getString("format_id_audio", "") + .toString() + .split(",") + .filter { it.isNotEmpty() } + .ifEmpty { listOf(audiof) } + + val preferredVideoFormats = if (usingGenericFormat) preferredFormatIDs else listOf(videof) + val preferredAudioFormats = if (usingGenericFormat && downloadItem.videoPreferences.audioFormatIDs.isEmpty()) { + preferredAudioFormatIDs + } else listOf(audiof) + + if (preferredAudioFormats.any{it.contains("+")}){ + request.addOption("--audio-multistreams") + } + + val preferredCodec = sharedPreferences.getString("video_codec", "") + val extPref = if (cont.isNotBlank()) "[ext=$cont]" else "" + val vCodecPref = if (preferredCodec!!.isNotBlank()) "[vcodec^=$preferredCodec]" else "" + + val f = StringBuilder() + + //build format with extension and vcodec + preferredVideoFormats.forEach { v -> + preferredAudioFormats.forEach { a -> + val aa = if (a.isNotBlank()) "+$a" else "" + f.append("$v$extPref$vCodecPref$aa/") + } + } + + //build format with vcodec + if (extPref.isNotBlank()){ + preferredVideoFormats.forEach { v -> + preferredAudioFormats.forEach { a -> + val aa = if (a.isNotBlank()) "+$a" else "" + f.append("$videof$vCodecPref$aa/") + } + } + } + + if (vCodecPref.isNotBlank()){ + preferredVideoFormats.forEach { v -> + //build format with vcodec + preferredAudioFormats.forEach {a -> + val aa = if (a.isNotBlank()) "+$a" else "" + f.append("$v$aa/") + } + } + } + + //build format with best audio + preferredVideoFormats.forEach { v -> + if(!f.contains("$v+bestaudio/")){ + f.append("$v+bestaudio/") + } + } + + + //build formats with standalone video + preferredVideoFormats.forEach { v -> + if(!f.contains("/$v/")){ + f.append("$v/") + } + } + + if(!f.endsWith("best/")){ + //last fallback + f.append("best") + } + + request.addOption("-f", f.toString().replace("/$".toRegex(), "")) + if (downloadItem.videoPreferences.writeSubs){ val subFormat = sharedPreferences.getString("sub_format", "srt") @@ -1179,7 +1262,7 @@ class InfoUtil(private val context: Context) { request.addOption("--sub-format", "${subFormat}/best") request.addOption("--convert-subtitles", "srt") if (!downloadItem.videoPreferences.embedSubs) { - request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages) + request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" }) } } @@ -1202,7 +1285,7 @@ class InfoUtil(private val context: Context) { DownloadViewModel.Type.command -> { request.addOption( "--config-locations", - File(FileUtil.getCachePath() + "/config[${downloadItem.id}].txt").apply { + File(context.cacheDir.absolutePath + "/config[${downloadItem.id}].txt").apply { writeText(downloadItem.format.format_note) }.absolutePath ) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt index adf5bda1..21176eb6 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -15,6 +15,7 @@ import androidx.core.content.FileProvider import androidx.navigation.NavDeepLinkBuilder import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver +import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver import com.deniscerri.ytdlnis.receiver.ResumeActivity import com.deniscerri.ytdlnis.receiver.ShareFileActivity @@ -81,7 +82,7 @@ class NotificationUtil(var context: Context) { return notificationBuilder .setContentTitle(context.getString(R.string.downloading)) .setOngoing(true) - .setSmallIcon(R.drawable.ic_launcher_foreground) + .setSmallIcon(R.drawable.ic_launcher_foreground_large) .setLargeIcon( BitmapFactory.decodeResource( context.resources, @@ -98,30 +99,11 @@ class NotificationUtil(var context: Context) { fun createDownloadServiceNotification( pendingIntent: PendingIntent?, title: String?, - workID: Int, - channel: String + itemID: Int, ): Notification { - val notificationBuilder = getBuilder(channel) + val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID) - val pauseIntent = Intent(context, PauseDownloadNotificationReceiver::class.java) - pauseIntent.putExtra("workID", workID) - pauseIntent.putExtra("title", title) - val pauseNotificationPendingIntent = PendingIntent.getBroadcast( - context, - workID, - pauseIntent, - PendingIntent.FLAG_IMMUTABLE - ) - val cancelIntent = Intent(context, CancelDownloadNotificationReceiver::class.java) - cancelIntent.putExtra("cancel", "") - cancelIntent.putExtra("workID", workID) - val cancelNotificationPendingIntent = PendingIntent.getBroadcast( - context, - workID, - cancelIntent, - PendingIntent.FLAG_IMMUTABLE - ) return notificationBuilder .setContentTitle(title) .setOngoing(true) @@ -140,13 +122,11 @@ class NotificationUtil(var context: Context) { .setContentIntent(pendingIntent) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) .clearActions() - .addAction(0, context.getString(R.string.pause), pauseNotificationPendingIntent) - .addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent) .build() } - fun createResumeDownload(workID: Int, title: String?, channel: String){ - val notificationBuilder = getBuilder(channel) + fun createResumeDownload(itemID: Int, title: String?){ + val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID) notificationBuilder .setContentTitle(title) @@ -162,16 +142,16 @@ class NotificationUtil(var context: Context) { .clearActions() val intent = Intent(context, ResumeActivity::class.java) - intent.putExtra("workID", workID) + intent.putExtra("itemID", itemID) val resumeNotificationPendingIntent = PendingIntent.getActivity( context, - workID, + itemID, intent, PendingIntent.FLAG_IMMUTABLE ) notificationBuilder.addAction(0, context.getString(R.string.resume), resumeNotificationPendingIntent) - notificationManager.notify(DOWNLOAD_RESUME_NOTIFICATION_ID, notificationBuilder.build()) + notificationManager.notify(DOWNLOAD_RESUME_NOTIFICATION_ID + itemID, notificationBuilder.build()) } fun createUpdatingItemNotification(channel: String){ @@ -315,10 +295,33 @@ class NotificationUtil(var context: Context) { var contentText = "" if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + "\n" contentText += desc.replace("\\[.*?\\] ".toRegex(), "") + + val pauseIntent = Intent(context, PauseDownloadNotificationReceiver::class.java) + pauseIntent.putExtra("itemID", id) + pauseIntent.putExtra("title", title) + val pauseNotificationPendingIntent = PendingIntent.getBroadcast( + context, + id, + pauseIntent, + PendingIntent.FLAG_IMMUTABLE + ) + + val cancelIntent = Intent(context, CancelDownloadNotificationReceiver::class.java) + cancelIntent.putExtra("itemID", id) + val cancelNotificationPendingIntent = PendingIntent.getBroadcast( + context, + id, + cancelIntent, + PendingIntent.FLAG_IMMUTABLE + ) + try { notificationBuilder.setProgress(100, progress, false) .setContentTitle(title) .setStyle(NotificationCompat.BigTextStyle().bigText(contentText)) + .clearActions() + .addAction(0, context.getString(R.string.pause), pauseNotificationPendingIntent) + .addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent) notificationManager.notify(id, notificationBuilder.build()) } catch (e: Exception) { e.printStackTrace() @@ -388,8 +391,7 @@ class NotificationUtil(var context: Context) { fun createFormatsUpdateNotification(workID: Int): Notification { val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID) - val cancelIntent = Intent(context, CancelDownloadNotificationReceiver::class.java) - cancelIntent.putExtra("cancel", "") + val cancelIntent = Intent(context, CancelWorkReceiver::class.java) cancelIntent.putExtra("workID", workID) val cancelNotificationPendingIntent = PendingIntent.getBroadcast( context, @@ -473,7 +475,7 @@ class NotificationUtil(var context: Context) { const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2" const val DOWNLOAD_FINISHED_CHANNEL_ID = "3" const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3 - const val DOWNLOAD_RESUME_NOTIFICATION_ID = 4 + const val DOWNLOAD_RESUME_NOTIFICATION_ID = 40000 const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 5 const val FORMAT_UPDATING_NOTIFICATION_ID = 6 const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7 diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt index eefc09a4..46de315a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt @@ -7,6 +7,7 @@ import android.content.ClipboardManager import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.graphics.Color import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.OvalShape import android.net.Uri @@ -30,7 +31,6 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources -import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.view.isVisible @@ -62,13 +62,18 @@ import com.google.android.material.materialswitch.MaterialSwitch import com.google.android.material.textfield.TextInputLayout import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat +import com.neo.highlight.core.Highlight +import com.neo.highlight.util.listener.HighlightTextWatcher +import com.neo.highlight.util.scheme.ColorScheme import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import me.zhanghai.android.fastscroll.FastScrollerBuilder import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale +import java.util.regex.Pattern object UiUtil { @SuppressLint("SetTextI18n") @@ -85,7 +90,7 @@ object UiUtil { if (audioFormats.isNullOrEmpty()){ formatCard.findViewById(R.id.format_note).text = formatNote.uppercase() }else{ - val title = "${formatNote.uppercase()} + [${audioFormats.joinToString("/") { it.format_note }}]" + val title = "${formatNote.uppercase()} + [${audioFormats.joinToString("/") { "(${it.format_id}) ${it.format_note}" }}]" formatCard.findViewById(R.id.format_note).text = title } formatCard.findViewById(R.id.format_id).text = "id: ${chosenFormat.format_id}" @@ -109,6 +114,12 @@ object UiUtil { } + fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){ + card.findViewById(R.id.title).text = item.title + card.findViewById(R.id.content).text = item.content + card.findViewById(R.id.id).text = item.id.toString() + } + fun showCommandTemplateCreationOrUpdatingSheet(item: CommandTemplate?, context: Activity, lifeCycle: LifecycleOwner, commandTemplateViewModel: CommandTemplateViewModel, newTemplate: (newTemplate: CommandTemplate) -> Unit){ val bottomSheet = BottomSheetDialog(context) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) @@ -658,7 +669,7 @@ object UiUtil { linearLayout!!.removeAllViews() list.forEach {template -> - val item = activity.layoutInflater.inflate(R.layout.command_template_item, linearLayout, false) as ConstraintLayout + val item = activity.layoutInflater.inflate(R.layout.command_template_item, linearLayout, false) as MaterialCardView item.findViewById(R.id.title).text = template.title item.findViewById(R.id.content).text = template.content item.setOnClickListener { @@ -719,8 +730,12 @@ object UiUtil { extraCommandsClicked: () -> Unit ){ val embedSubs = view.findViewById(R.id.embed_subtitles) + val saveSubtitles = view.findViewById(R.id.save_subtitles) + val subtitleLanguages = view.findViewById(R.id.subtitle_languages) + embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs } embedSubs.setOnClickListener { + subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked embedSubsClicked(embedSubs.isChecked) } @@ -878,8 +893,6 @@ object UiUtil { dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START } - val saveSubtitles = view.findViewById(R.id.save_subtitles) - val subtitleLanguages = view.findViewById(R.id.subtitle_languages) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -890,11 +903,11 @@ object UiUtil { } saveSubtitles.setOnCheckedChangeListener { _, _ -> - if (saveSubtitles.isChecked) subtitleLanguages.visibility = View.VISIBLE - else subtitleLanguages.visibility = View.GONE + subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked saveSubtitlesClicked(saveSubtitles.isChecked) } + subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked subtitleLanguages.setOnClickListener { subtitleLanguagesClicked() } @@ -1074,8 +1087,10 @@ object UiUtil { fun configureCommand( view: View, size: Int, + shortcutCount: Int, newTemplateClicked: () -> Unit, editSelectedClicked: () -> Unit, + shortcutClicked: suspend () -> Unit, ){ val newTemplate : Chip = view.findViewById(R.id.newTemplate) newTemplate.setOnClickListener { @@ -1087,6 +1102,14 @@ object UiUtil { editSelected.setOnClickListener { editSelectedClicked() } + + val shortcuts = view.findViewById(R.id.shortcut) + shortcuts.isEnabled = shortcutCount > 0 + shortcuts.setOnClickListener { + runBlocking { + shortcutClicked() + } + } } @@ -1117,6 +1140,31 @@ object UiUtil { .build() } + private var textHighLightSchemes = listOf( + ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")), + ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#00FF00")), + ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")), + ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564")) + ) + + fun View.enableTextHighlight(){ + if (this is EditText || this is TextView){ + //init syntax highlighter + val highlight = Highlight() + val highlightWatcher = HighlightTextWatcher() + + highlight.addScheme( + *textHighLightSchemes.map { it }.toTypedArray() + ) + highlightWatcher.addScheme( + *textHighLightSchemes.map { it }.toTypedArray() + ) + + highlight.setSpan(this as TextView) + this.addTextChangedListener(highlightWatcher) + } + } + diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt index 6c5f5a48..28127192 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -31,8 +31,6 @@ import com.yausername.youtubedl_android.YoutubeDL import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.transform import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -61,29 +59,34 @@ class DownloadWorker( val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await() if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success() - runningYTDLInstances.addAll(dao.getActiveDownloadsList().map { it.id }) - val pendingIntent = NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_graph) .setDestination(R.id.downloadQueueMainFragment) .createPendingIntent() - var notification = notificationUtil.createDefaultWorkerNotification() - val foregroundInfo = ForegroundInfo(Random.nextInt(1000000000), notification) + val workNotif = notificationUtil.createDefaultWorkerNotification() + val foregroundInfo = ForegroundInfo(Random.nextInt(1000000000), workNotif) setForegroundAsync(foregroundInfo) queuedItems.collectLatest { items -> + runningYTDLInstances.clear() + dao.getActiveDownloadsList().forEach { + runningYTDLInstances.add(it.id) + } + val running = ArrayList(runningYTDLInstances) if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id) + val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running } - eligibleDownloads.forEach {downloadItem -> + + eligibleDownloads.forEach{downloadItem -> runningYTDLInstances.add(downloadItem.id) + val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt()) + notificationUtil.notify(downloadItem.id.toInt(), notification) + + CoroutineScope(Dispatchers.IO).launch { - withContext(Dispatchers.Main){ - notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID) - notificationUtil.notify(downloadItem.id.toInt(), notification) - } val request = infoUtil.buildYoutubeDLRequest(downloadItem) downloadItem.status = DownloadRepository.Status.Active.toString() CoroutineScope(Dispatchers.IO).launch { @@ -91,7 +94,7 @@ class DownloadWorker( updateDownloadItem(downloadItem, infoUtil, dao, resultDao) } - val cacheDir = FileUtil.getCachePath() + val cacheDir = FileUtil.getCachePath(context) val tempFileDir = File(cacheDir, downloadItem.id.toString()) tempFileDir.delete() tempFileDir.mkdirs() @@ -126,6 +129,7 @@ class DownloadWorker( } } + runCatching { YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line -> setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id)) @@ -143,6 +147,8 @@ class DownloadWorker( } }.onSuccess { runningYTDLInstances.remove(downloadItem.id) + FileUtil.deleteConfigFiles(request) + val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao) runBlocking { var finalPaths : List? @@ -209,6 +215,8 @@ class DownloadWorker( }.onFailure { runningYTDLInstances.remove(downloadItem.id) + FileUtil.deleteConfigFiles(request) + withContext(Dispatchers.Main){ notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) } @@ -246,6 +254,7 @@ class DownloadWorker( } } } + if (eligibleDownloads.isNotEmpty()){ eligibleDownloads.forEach { it.status = DownloadRepository.Status.Active.toString() } dao.updateMultiple(eligibleDownloads) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt index d103f6f8..8b8170bd 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt @@ -51,7 +51,7 @@ class TerminalDownloadWorker( val intent = Intent(context, TerminalActivity::class.java) val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) - val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), itemId, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID) + val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), itemId) val foregroundInfo = ForegroundInfo(itemId, notification) setForegroundAsync(foregroundInfo) @@ -65,7 +65,7 @@ class TerminalDownloadWorker( request.addOption( "--config-locations", - File(context.cacheDir.absolutePath + "/config${System.currentTimeMillis()}.txt").apply { + File(context.cacheDir.absolutePath + "/config-TERMINAL[${System.currentTimeMillis()}].txt").apply { writeText(command) }.absolutePath ) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/UpdateYTDLWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/UpdateYTDLWorker.kt index 5f99b578..b85fc73e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/UpdateYTDLWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/UpdateYTDLWorker.kt @@ -13,9 +13,6 @@ class UpdateYTDLWorker( workerParams: WorkerParameters ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { - val notification = NotificationUtil(context).createYTDLUpdateNotification() - val foregroundInfo = ForegroundInfo(System.currentTimeMillis().toInt(), notification) - setForeground(foregroundInfo) UpdateUtil(context).updateYoutubeDL() return Result.success() } diff --git a/app/src/main/res/layout/adjust_command.xml b/app/src/main/res/layout/adjust_command.xml index 4a2507e6..0a7020d5 100644 --- a/app/src/main/res/layout/adjust_command.xml +++ b/app/src/main/res/layout/adjust_command.xml @@ -1,11 +1,10 @@ - + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + - \ No newline at end of file + diff --git a/app/src/main/res/layout/adjust_video.xml b/app/src/main/res/layout/adjust_video.xml index a632881a..03b0cd43 100644 --- a/app/src/main/res/layout/adjust_video.xml +++ b/app/src/main/res/layout/adjust_video.xml @@ -179,7 +179,7 @@ style="@style/Widget.Material3.Chip.Assist.Elevated" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:visibility="gone" + android:enabled="false" android:checked="false" android:text="@string/subtitle_languages"/> diff --git a/app/src/main/res/layout/command_template_item.xml b/app/src/main/res/layout/command_template_item.xml index 52caf6f6..37b155b6 100644 --- a/app/src/main/res/layout/command_template_item.xml +++ b/app/src/main/res/layout/command_template_item.xml @@ -1,57 +1,76 @@ - + android:checkable="true" + android:clickable="true" + android:focusable="true" + android:backgroundTint="@android:color/transparent" + app:checkedIcon="@null" + app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" + app:strokeWidth="0dp" + app:cardPreventCornerOverlap="true" + android:layout_height="wrap_content"> - + - + - + - + + + + + + + diff --git a/app/src/main/res/layout/configure_download_bottom_sheet.xml b/app/src/main/res/layout/configure_download_bottom_sheet.xml index da21bd3a..ebfc8e37 100644 --- a/app/src/main/res/layout/configure_download_bottom_sheet.xml +++ b/app/src/main/res/layout/configure_download_bottom_sheet.xml @@ -92,7 +92,7 @@ style="@style/Widget.Material3.Button.TextButton.Icon" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="10dp" + android:layout_marginHorizontal="10dp" android:textAlignment="textStart" android:singleLine="true" android:text="@string/app_name" diff --git a/app/src/main/res/layout/format_details_sheet.xml b/app/src/main/res/layout/format_details_sheet.xml index 0c0eaf08..cf6e87a4 100644 --- a/app/src/main/res/layout/format_details_sheet.xml +++ b/app/src/main/res/layout/format_details_sheet.xml @@ -5,13 +5,13 @@ @@ -23,24 +23,23 @@ android:paddingHorizontal="10dp" android:weightSum="100" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > @@ -54,26 +53,26 @@ android:paddingHorizontal="10dp" android:weightSum="100" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > @@ -87,23 +86,23 @@ android:weightSum="100" android:paddingHorizontal="10dp" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > @@ -117,22 +116,22 @@ android:weightSum="100" android:paddingHorizontal="10dp" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > @@ -146,22 +145,22 @@ android:weightSum="100" android:paddingHorizontal="10dp" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > @@ -175,22 +174,22 @@ android:weightSum="100" android:paddingHorizontal="10dp" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > @@ -204,22 +203,22 @@ android:weightSum="100" android:paddingHorizontal="10dp" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > @@ -233,22 +232,22 @@ android:weightSum="100" android:paddingHorizontal="10dp" android:background="?android:attr/selectableItemBackground" - android:orientation="vertical" > + android:orientation="horizontal" > diff --git a/app/src/main/res/layout/fragment_download_command.xml b/app/src/main/res/layout/fragment_download_command.xml index fa4e287b..b200e5dc 100644 --- a/app/src/main/res/layout/fragment_download_command.xml +++ b/app/src/main/res/layout/fragment_download_command.xml @@ -7,11 +7,11 @@ - + android:paddingHorizontal="10dp" + android:paddingTop="10dp" + android:text="@string/command" /> - - - + @@ -62,7 +56,7 @@ diff --git a/app/src/main/res/xml/folders_preference.xml b/app/src/main/res/xml/folders_preference.xml index 13f9ba76..c2bb225d 100644 --- a/app/src/main/res/xml/folders_preference.xml +++ b/app/src/main/res/xml/folders_preference.xml @@ -49,7 +49,6 @@ diff --git a/app/src/main/res/xml/processing_preferences.xml b/app/src/main/res/xml/processing_preferences.xml index e788f766..7b1a5afd 100644 --- a/app/src/main/res/xml/processing_preferences.xml +++ b/app/src/main/res/xml/processing_preferences.xml @@ -56,7 +56,7 @@ android:icon="@drawable/baseline_subject_24" app:key="subs_lang" app:useSimpleSummaryProvider="true" - app:defaultValue="all" + app:defaultValue="en.*,.*-orig" app:title="@string/subtitle_languages" />