stuff
- added saved downloads in the settings backup - made icon bigger in downloading notification - fixed notification cancel and pausing not working - fixed app not working in preferred home screen for downloads and more - removed 000001 if the user has made only one cut - removed log id from downloads if the user deleted the log - if the user had multiple preferred audio formats the app would choose the last one and not the first - added ability to combine preferred audio formats by writing like 140+251,250. If the download item finds both first audio formats it will merge them together, otherwise get one of them that is available - fixed app file transfer not working. (had to revert to hidden cache folder as downloads folder wasnt good for some phones) - fixed app saving as mkv even though mp4 is set as container - added shortcuts button to the commands tab so you can drop shortcuts to the current command. - added syntax highlighting in commands tab textbox - fixed tiktok videos not saving properly - added ability to combine all possible combinations of preferred video formats, audio formats, codex and container. The app will consider all preferred video ids. For each of them it will consider preferred vcodec and extension. At the same time for each of those elements it will consider all preferred audio commands and/or audio merge. If the format is a generic quality like 1080p or 720p it will also consider that in the long format query. Also the app will add all combos of video + audio only. And all combos of all video + best audio. All video ids alone and best as final fallback. If the user chooses a proper format, the app will not be this descriptive. Preferred video id and audio id combos will only apply on a quick download or best quality generic download
This commit is contained in:
parent
5b3bb3c2ed
commit
df2dee4c83
38 changed files with 665 additions and 430 deletions
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<CookieItem?, CookieAdapter.ViewHolder>(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CommandTemplate?, TemplatesAdapter.ViewHolder>(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String>) : Flow<Int>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
|
||||
fun getActiveAndPausedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
||||
@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<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
|
||||
fun getSavedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@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<DownloadItem>)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,13 @@ interface LogDao {
|
|||
@Query("DELETE FROM logs WHERE id=:itemId")
|
||||
suspend fun delete(itemId: Long)
|
||||
|
||||
@Transaction
|
||||
suspend fun deleteItems(list: List<LogItem>){
|
||||
list.forEach{
|
||||
delete(it.id)
|
||||
}
|
||||
}
|
||||
|
||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun update(item: LogItem)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ data class VideoPreferences (
|
|||
var splitByChapters: Boolean = false,
|
||||
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
||||
var writeSubs: Boolean = false,
|
||||
var subsLanguages: String = "all",
|
||||
var subsLanguages: String = "en.*,.*-orig",
|
||||
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||
var removeAudio: Boolean = false
|
||||
)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
}
|
||||
|
||||
fun getActiveDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getActiveDownloadsList()
|
||||
return downloadDao.getActiveAndPausedDownloadsList()
|
||||
}
|
||||
|
||||
fun getActiveAndQueuedDownloads() : List<DownloadItem> {
|
||||
|
|
@ -110,4 +110,12 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
downloadDao.unPauseActiveAndQueued()
|
||||
}
|
||||
|
||||
fun removeLogID(logID: Long){
|
||||
downloadDao.removeLogID(logID)
|
||||
}
|
||||
|
||||
fun removeAllLogID(){
|
||||
downloadDao.removeAllLogID()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String>()
|
||||
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<DownloadItem> {
|
||||
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<DownloadItem>) {
|
||||
|
|
|
|||
|
|
@ -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<List<LogItem>>
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()){
|
||||
|
|
|
|||
|
|
@ -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<View>(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
|
||||
|
|
|
|||
|
|
@ -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<TextInputLayout>(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<TextInputLayout>(R.id.template)
|
||||
val autoCompleteTextView =
|
||||
requireView().findViewById<AutoCompleteTextView>(R.id.template_textview)
|
||||
populateCommandTemplates(templates, autoCompleteTextView)
|
||||
val commandTemplateCard = view.findViewById<MaterialCardView>(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<android.widget.TextView>(
|
||||
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<CommandTemplate>, 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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<DownloadItem>()
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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<ResultItem?> {
|
||||
items = ArrayList()
|
||||
val webpage_urls = mutableListOf<String>()
|
||||
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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<TextView>(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<TextView>(R.id.format_note).text = title
|
||||
}
|
||||
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
||||
|
|
@ -109,6 +114,12 @@ object UiUtil {
|
|||
|
||||
}
|
||||
|
||||
fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){
|
||||
card.findViewById<TextView>(R.id.title).text = item.title
|
||||
card.findViewById<TextView>(R.id.content).text = item.content
|
||||
card.findViewById<TextView>(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<TextView>(R.id.title).text = template.title
|
||||
item.findViewById<TextView>(R.id.content).text = template.content
|
||||
item.setOnClickListener {
|
||||
|
|
@ -719,8 +730,12 @@ object UiUtil {
|
|||
extraCommandsClicked: () -> Unit
|
||||
){
|
||||
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
||||
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
|
||||
val subtitleLanguages = view.findViewById<Chip>(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<Chip>(R.id.save_subtitles)
|
||||
val subtitleLanguages = view.findViewById<Chip>(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<View>(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String>?
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/adjust"
|
||||
<LinearLayout android:id="@+id/adjust"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:orientation="vertical"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -41,5 +40,12 @@
|
|||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/shortcut"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/shortcuts"
|
||||
app:chipIcon="@drawable/ic_shortcut" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,57 +1,76 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/command_template_item_constraint"
|
||||
android:id="@+id/command_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:padding="15dp">
|
||||
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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="15dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/id"
|
||||
android:layout_height="0dp"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/useInExtraCommands"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:visibility="gone"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/extra_command"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/content" />
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/useInExtraCommands"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:visibility="gone"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/extra_command"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/content" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/format"
|
||||
android:textSize="17sp"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_margin="20dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -23,24 +23,23 @@
|
|||
android:paddingHorizontal="10dp"
|
||||
android:weightSum="100"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="30"
|
||||
android:maxLines="2"
|
||||
android:text="@string/format_id"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_id_value"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -54,26 +53,26 @@
|
|||
android:paddingHorizontal="10dp"
|
||||
android:weightSum="100"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="30"
|
||||
android:maxLines="2"
|
||||
android:text="@string/url"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_url_value"
|
||||
android:layout_width="match_parent"
|
||||
android:gravity="end"
|
||||
android:maxLines="2"
|
||||
android:layout_width="0dp"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -87,23 +86,23 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="30"
|
||||
android:maxLines="2"
|
||||
android:textSize="14sp"
|
||||
|
||||
android:text="@string/container"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/container_value"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -117,22 +116,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="30"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/codec"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codec_value"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -146,22 +145,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="30"
|
||||
android:text="@string/file_size"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize_value"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -175,22 +174,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="0dp"
|
||||
android:text="@string/quality"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_weight="30"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_note_value"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -204,22 +203,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="30"
|
||||
android:text="FPS"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fps_value"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -233,22 +232,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="30"
|
||||
android:text="@string/audio_samplerate"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/asr_value"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="70"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="10dp"
|
||||
|
|
@ -22,32 +22,26 @@
|
|||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:maxLines="10"
|
||||
android:maxLines="99999"
|
||||
android:inputType="textMultiLine"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/template"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="10dp"
|
||||
android:hint="@string/command_templates">
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/command" />
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/template_textview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<include layout="@layout/command_template_item" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/outputPath"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/save_dir"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
|
@ -62,7 +56,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/freespace"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@android:color/tab_indicator_text"
|
||||
|
|
|
|||
|
|
@ -4,11 +4,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@
|
|||
<Preference
|
||||
app:icon="@drawable/baseline_drive_file_move_24"
|
||||
app:key="move_cache"
|
||||
app:isPreferenceVisible="false"
|
||||
android:summary="@string/move_temporary_files_summary"
|
||||
app:title="@string/move_temporary_files" />
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue