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 versionMajor = 1
|
||||||
def versionMinor = 6
|
def versionMinor = 6
|
||||||
def versionPatch = 5
|
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 = ""
|
def versionExt = ""
|
||||||
|
|
||||||
if (versionBuild > 0){
|
if (versionBuild > 0){
|
||||||
|
|
|
||||||
|
|
@ -120,14 +120,14 @@ class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
// val graph = navController.navInflater.inflate(R.navigation.nav_graph)
|
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
|
||||||
// graph.setStartDestination(R.id.homeFragment)
|
graph.setStartDestination(R.id.homeFragment)
|
||||||
// when(sharedPreferences.getString("start_destination", "")) {
|
when(sharedPreferences.getString("start_destination", "")) {
|
||||||
// "History" -> graph.setStartDestination(R.id.historyFragment)
|
"History" -> graph.setStartDestination(R.id.historyFragment)
|
||||||
// "More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment)
|
"More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// navController.graph = graph
|
navController.graph = graph
|
||||||
|
|
||||||
if (navigationView is NavigationBarView){
|
if (navigationView is NavigationBarView){
|
||||||
(navigationView as NavigationBarView).setupWithNavController(navController)
|
(navigationView as NavigationBarView).setupWithNavController(navController)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
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()) {
|
class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CookieItem?, CookieAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
|
|
@ -23,10 +24,10 @@ class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
val item: ConstraintLayout
|
val item: MaterialCardView
|
||||||
|
|
||||||
init {
|
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) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
card.isVisible = item != null
|
|
||||||
if (item == null) return
|
if (item == null) return
|
||||||
card.tag = item.id.toString()
|
card.tag = item.id.toString()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
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()) {
|
class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CommandTemplate?, TemplatesAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
|
|
@ -24,10 +25,10 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
val item: ConstraintLayout
|
val item: MaterialCardView
|
||||||
|
|
||||||
init {
|
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 androidx.room.*
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|
@ -23,6 +22,9 @@ interface DownloadDao {
|
||||||
fun getDownloadsCountByStatus(status : List<String>) : Flow<Int>
|
fun getDownloadsCountByStatus(status : List<String>) : Flow<Int>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
|
@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>
|
fun getActiveDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
@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")
|
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
|
||||||
fun getSavedDownloads() : PagingSource<Int, DownloadItem>
|
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")
|
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||||
fun getDownloadById(id: Long) : DownloadItem
|
fun getDownloadById(id: Long) : DownloadItem
|
||||||
|
|
||||||
|
|
@ -119,6 +124,12 @@ interface DownloadDao {
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun update(item: DownloadItem)
|
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
|
@Upsert
|
||||||
fun updateMultiple(items :List<DownloadItem>)
|
fun updateMultiple(items :List<DownloadItem>)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@ interface LogDao {
|
||||||
@Query("DELETE FROM logs WHERE id=:itemId")
|
@Query("DELETE FROM logs WHERE id=:itemId")
|
||||||
suspend fun delete(itemId: Long)
|
suspend fun delete(itemId: Long)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
suspend fun deleteItems(list: List<LogItem>){
|
||||||
|
list.forEach{
|
||||||
|
delete(it.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun update(item: LogItem)
|
suspend fun update(item: LogItem)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ data class VideoPreferences (
|
||||||
var splitByChapters: Boolean = false,
|
var splitByChapters: Boolean = false,
|
||||||
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
||||||
var writeSubs: Boolean = false,
|
var writeSubs: Boolean = false,
|
||||||
var subsLanguages: String = "all",
|
var subsLanguages: String = "en.*,.*-orig",
|
||||||
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||||
var removeAudio: Boolean = false
|
var removeAudio: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveDownloads() : List<DownloadItem> {
|
fun getActiveDownloads() : List<DownloadItem> {
|
||||||
return downloadDao.getActiveDownloadsList()
|
return downloadDao.getActiveAndPausedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveAndQueuedDownloads() : List<DownloadItem> {
|
fun getActiveAndQueuedDownloads() : List<DownloadItem> {
|
||||||
|
|
@ -110,4 +110,12 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.unPauseActiveAndQueued()
|
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 audioContainer: String?
|
||||||
private var videoContainer: String?
|
private var videoContainer: String?
|
||||||
private var videoCodec: String?
|
private var videoCodec: String?
|
||||||
|
private val dao: DownloadDao
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
audio, video, command
|
audio, video, command
|
||||||
|
|
@ -84,7 +85,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
init {
|
init {
|
||||||
dbManager = DBManager.getInstance(application)
|
dbManager = DBManager.getInstance(application)
|
||||||
val dao = dbManager.downloadDao
|
dao = dbManager.downloadDao
|
||||||
repository = DownloadRepository(dao)
|
repository = DownloadRepository(dao)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||||
|
|
@ -157,13 +158,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
)
|
)
|
||||||
}else{
|
}else{
|
||||||
Format(
|
Format(
|
||||||
audioFormatIDPreference.first(),
|
audioFormatIDPreference.first().split("+").first(),
|
||||||
audioContainer!!,
|
audioContainer!!,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
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 audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
|
||||||
|
|
||||||
val hasPreferredAudioFormats = resultItem.formats.filter { audioFormatIDPreference.contains(it.format_id) }
|
val preferredAudioFormats = arrayListOf<String>()
|
||||||
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, audioFormatIDs = if (hasPreferredAudioFormats.isEmpty()) arrayListOf() else arrayListOf(hasPreferredAudioFormats.map { it.format_id }.first()))
|
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,
|
return DownloadItem(0,
|
||||||
resultItem.url,
|
resultItem.url,
|
||||||
|
|
@ -368,10 +385,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return cloneFormat (
|
return cloneFormat (
|
||||||
try {
|
try {
|
||||||
try{
|
try{
|
||||||
formats.filter { it.format_note.contains("audio", ignoreCase = true)}.first {
|
formats.filter { it.format_note.contains("audio", ignoreCase = true)}.sortedBy { it.format_id }
|
||||||
audioFormatIDPreference.contains(it.format_id) ||
|
.firstOrNull { audioFormatIDPreference.indexOfFirst { a -> a.contains(it.format_id) } >= 0 || it.container == audioContainer} ?: throw Exception()
|
||||||
it.container == audioContainer
|
|
||||||
}
|
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
formats.last { it.format_note.contains("audio", ignoreCase = true) }
|
formats.last { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
}
|
}
|
||||||
|
|
@ -384,14 +399,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
Type.video -> {
|
Type.video -> {
|
||||||
return cloneFormat(
|
return cloneFormat(
|
||||||
try {
|
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) {
|
when (videoQualityPreference) {
|
||||||
"worst" -> {
|
"worst" -> {
|
||||||
theFormats.first()
|
theFormats.first()
|
||||||
}
|
}
|
||||||
else /*best*/ -> {
|
else /*best*/ -> {
|
||||||
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
|
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 -> 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.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
|
||||||
requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) }
|
requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) }
|
||||||
|
|
@ -505,6 +520,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return repository.getErroredDownloads()
|
return repository.getErroredDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSaved() : List<DownloadItem> {
|
||||||
|
return dao.getSavedDownloadsList()
|
||||||
|
}
|
||||||
|
|
||||||
private fun cloneFormat(item: Format) : Format {
|
private fun cloneFormat(item: Format) : Format {
|
||||||
val string = Gson().toJson(item, Format::class.java)
|
val string = Gson().toJson(item, Format::class.java)
|
||||||
return Gson().fromJson(string, 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>) {
|
private suspend fun startDownloadWorker(queuedItems: List<DownloadItem>) {
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,19 @@ import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class LogViewModel(private val application: Application) : AndroidViewModel(application) {
|
class LogViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
private val repository: LogRepository
|
private val repository: LogRepository
|
||||||
|
private val downloadRepository: DownloadRepository
|
||||||
val items: LiveData<List<LogItem>>
|
val items: LiveData<List<LogItem>>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val dao = DBManager.getInstance(application).logDao
|
repository = LogRepository(DBManager.getInstance(application).logDao)
|
||||||
repository = LogRepository(dao)
|
downloadRepository = DownloadRepository(DBManager.getInstance(application).downloadDao)
|
||||||
items = repository.items.asLiveData()
|
items = repository.items.asLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,10 +38,12 @@ class LogViewModel(private val application: Application) : AndroidViewModel(appl
|
||||||
|
|
||||||
fun delete(item: LogItem) = viewModelScope.launch(Dispatchers.IO) {
|
fun delete(item: LogItem) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
repository.delete(item)
|
repository.delete(item)
|
||||||
|
downloadRepository.removeLogID(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
repository.deleteAll()
|
repository.deleteAll()
|
||||||
|
downloadRepository.removeAllLogID()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(newLine: String, id: Long) = viewModelScope.launch(Dispatchers.IO) {
|
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.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
|
@ -15,10 +16,10 @@ import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class CancelDownloadNotificationReceiver : BroadcastReceiver() {
|
class CancelDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(c: Context, intent: Intent) {
|
override fun onReceive(c: Context, intent: Intent) {
|
||||||
val message = intent.getStringExtra("cancel")
|
val id = intent.getIntExtra("itemID", 0)
|
||||||
val id = intent.getIntExtra("workID", 0)
|
if (id > 0) {
|
||||||
if (message != null) {
|
|
||||||
runCatching {
|
runCatching {
|
||||||
|
Log.e("aa", id.toString())
|
||||||
val notificationUtil = NotificationUtil(c)
|
val notificationUtil = NotificationUtil(c)
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
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.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
|
@ -13,14 +14,14 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(c: Context, intent: Intent) {
|
override fun onReceive(c: Context, intent: Intent) {
|
||||||
val id = intent.getIntExtra("workID", 0)
|
val id = intent.getIntExtra("itemID", 0)
|
||||||
if (id != 0) {
|
if (id != 0) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val title = intent.getStringExtra("title")
|
val title = intent.getStringExtra("title")
|
||||||
val notificationUtil = NotificationUtil(c)
|
val notificationUtil = NotificationUtil(c)
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
notificationUtil.createResumeDownload(id, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
notificationUtil.createResumeDownload(id, title)
|
||||||
val dbManager = DBManager.getInstance(c)
|
val dbManager = DBManager.getInstance(c)
|
||||||
CoroutineScope(Dispatchers.IO).launch{
|
CoroutineScope(Dispatchers.IO).launch{
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ class ResumeActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIntents(intent: Intent) {
|
private fun handleIntents(intent: Intent) {
|
||||||
val id = intent.getIntExtra("workID", 0)
|
val id = intent.getIntExtra("itemID", 0)
|
||||||
if (id != 0) {
|
if (id != 0) {
|
||||||
try {
|
try {
|
||||||
val loadingBottomSheet = BottomSheetDialog(this)
|
val loadingBottomSheet = BottomSheetDialog(this)
|
||||||
|
|
@ -71,13 +71,12 @@ class ResumeActivity : BaseActivity() {
|
||||||
loadingBottomSheet.setContentView(R.layout.please_wait_bottom_sheet)
|
loadingBottomSheet.setContentView(R.layout.please_wait_bottom_sheet)
|
||||||
|
|
||||||
loadingBottomSheet.setOnShowListener {
|
loadingBottomSheet.setOnShowListener {
|
||||||
NotificationUtil(this).cancelDownloadNotification(NotificationUtil.DOWNLOAD_RESUME_NOTIFICATION_ID)
|
NotificationUtil(this).cancelDownloadNotification(NotificationUtil.DOWNLOAD_RESUME_NOTIFICATION_ID + id)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val downloadViewModel = ViewModelProvider(this@ResumeActivity)[DownloadViewModel::class.java]
|
val downloadViewModel = ViewModelProvider(this@ResumeActivity)[DownloadViewModel::class.java]
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
val downloadItem = downloadViewModel.getItemByID(id.toLong())
|
downloadViewModel.reQueueDownloadItems(listOf(id.toLong()))
|
||||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
finishAffinity()
|
||||||
finishAffinity()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -427,11 +428,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
private suspend fun updateSearchViewItems(it: Editable?, linkYouCopied: View?){
|
private suspend fun updateSearchViewItems(it: Editable?, linkYouCopied: View?){
|
||||||
|
searchSuggestionsLinearLayout!!.visibility = GONE
|
||||||
|
searchHistoryLinearLayout!!.visibility = GONE
|
||||||
searchSuggestionsLinearLayout!!.removeAllViews()
|
searchSuggestionsLinearLayout!!.removeAllViews()
|
||||||
searchHistoryLinearLayout!!.removeAllViews()
|
searchHistoryLinearLayout!!.removeAllViews()
|
||||||
|
|
||||||
searchSuggestionsLinearLayout!!.visibility = GONE
|
|
||||||
searchHistoryLinearLayout!!.visibility = GONE
|
|
||||||
linkYouCopied!!.visibility = GONE
|
linkYouCopied!!.visibility = GONE
|
||||||
|
|
||||||
if (searchView!!.editText.text.isEmpty()){
|
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.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
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.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
|
@ -95,29 +96,8 @@ class AddExtraCommandsDialog(private val item: DownloadItem?, private val callba
|
||||||
view.findViewById<View>(R.id.current).visibility = View.GONE
|
view.findViewById<View>(R.id.current).visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text.enableTextHighlight()
|
||||||
//init syntax highlighter
|
currentText.enableTextHighlight()
|
||||||
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?.setText(item?.extraCommands)
|
text?.setText(item?.extraCommands)
|
||||||
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
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.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
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.android.material.textfield.TextInputLayout
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.Dispatchers
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
@ -79,7 +82,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
||||||
}
|
}
|
||||||
|
|
||||||
val chosenCommandView = view.findViewById<TextInputLayout>(R.id.content)
|
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.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_delete_all)
|
||||||
chosenCommandView.editText!!.addTextChangedListener(object : TextWatcher {
|
chosenCommandView.editText!!.addTextChangedListener(object : TextWatcher {
|
||||||
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
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()
|
chosenCommandView.editText!!.enableScrollText()
|
||||||
|
|
||||||
val commandTemplates = view.findViewById<TextInputLayout>(R.id.template)
|
val commandTemplateCard = view.findViewById<MaterialCardView>(R.id.command_card)
|
||||||
val autoCompleteTextView =
|
populateCommandCard(commandTemplateCard, templates.first())
|
||||||
requireView().findViewById<AutoCompleteTextView>(R.id.template_textview)
|
|
||||||
populateCommandTemplates(templates, autoCompleteTextView)
|
|
||||||
|
|
||||||
(commandTemplates!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
|
commandTemplateCard.setOnClickListener {
|
||||||
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->
|
lifecycleScope.launch {
|
||||||
chosenCommandView.editText!!.setText(templates[index].content)
|
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){
|
||||||
downloadItem.format = Format(
|
chosenCommandView.editText!!.setText(it.content)
|
||||||
templates[index].title,
|
populateCommandCard(commandTemplateCard, it)
|
||||||
"",
|
downloadItem.format = Format(
|
||||||
"",
|
it.title,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
"",
|
||||||
templates[index].content
|
"",
|
||||||
)
|
0,
|
||||||
|
it.content
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
saveDir = view.findViewById(R.id.outputPath)
|
saveDir = view.findViewById(R.id.outputPath)
|
||||||
saveDir.editText!!.setText(
|
saveDir.editText!!.setText(
|
||||||
FileUtil.formatPath(downloadItem.downloadPath)
|
FileUtil.formatPath(downloadItem.downloadPath)
|
||||||
|
|
@ -151,9 +159,14 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
||||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace
|
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace
|
||||||
))
|
))
|
||||||
|
|
||||||
|
val shortcutCount = withContext(Dispatchers.IO){
|
||||||
|
commandTemplateViewModel.getTotalShortcutNumber()
|
||||||
|
}
|
||||||
|
|
||||||
UiUtil.configureCommand(
|
UiUtil.configureCommand(
|
||||||
view,
|
view,
|
||||||
1,
|
1,
|
||||||
|
shortcutCount,
|
||||||
newTemplateClicked = {
|
newTemplateClicked = {
|
||||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||||
templates.add(it)
|
templates.add(it)
|
||||||
|
|
@ -167,16 +180,24 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
||||||
0,
|
0,
|
||||||
it.content
|
it.content
|
||||||
)
|
)
|
||||||
populateCommandTemplates(templates, autoCompleteTextView)
|
populateCommandCard(commandTemplateCard, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editSelectedClicked = {
|
editSelectedClicked =
|
||||||
var current = templates.find { it.title == autoCompleteTextView.text.toString() }
|
{
|
||||||
if (current == null) current = CommandTemplate(0, "", chosenCommandView.editText!!.text.toString(), false)
|
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) {
|
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||||
templates.add(it)
|
templates.add(it)
|
||||||
chosenCommandView.editText!!.setText(it.content)
|
chosenCommandView.editText!!.setText(it.content)
|
||||||
downloadItem.format = Format(
|
downloadItem.format = com.deniscerri.ytdlnis.database.models.Format(
|
||||||
it.title,
|
it.title,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
|
@ -186,29 +207,22 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
||||||
it.content
|
it.content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
shortcutClicked = {
|
||||||
|
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel) { sh ->
|
||||||
|
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $sh")
|
||||||
|
downloadItem.format.format_note += " $sh"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
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(
|
private var commandPathResultLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
|
|
||||||
|
|
@ -75,34 +75,27 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
pauseResume.setOnClickListener {
|
pauseResume.setOnClickListener {
|
||||||
if (pauseResume.text == requireContext().getString(R.string.pause)){
|
if (pauseResume.text == requireContext().getString(R.string.pause)){
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
workManager.cancelAllWorkByTag("download")
|
||||||
pauseResume.isEnabled = false
|
pauseResume.isEnabled = false
|
||||||
val queued = withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.getQueued()
|
|
||||||
}
|
|
||||||
|
|
||||||
queued.forEach {
|
// pause active
|
||||||
it.status = DownloadRepository.Status.QueuedPaused.toString()
|
withContext(Dispatchers.IO){
|
||||||
runBlocking {
|
|
||||||
downloadViewModel.updateDownload(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
queued.forEach {
|
|
||||||
workManager.cancelAllWorkByTag(it.id.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
val active = withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.getActiveDownloads()
|
downloadViewModel.getActiveDownloads()
|
||||||
}
|
}.forEach {
|
||||||
|
|
||||||
active.forEach {
|
|
||||||
cancelItem(it.id.toInt())
|
cancelItem(it.id.toInt())
|
||||||
it.status = DownloadRepository.Status.Paused.toString()
|
it.status = DownloadRepository.Status.Paused.toString()
|
||||||
downloadViewModel.updateDownload(it)
|
downloadViewModel.updateDownload(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pause queued
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.getQueued()
|
||||||
|
}.forEach {
|
||||||
|
it.status = DownloadRepository.Status.QueuedPaused.toString()
|
||||||
|
downloadViewModel.updateDownload(it)
|
||||||
|
}
|
||||||
|
|
||||||
activeDownloads.notifyDataSetChanged()
|
activeDownloads.notifyDataSetChanged()
|
||||||
pauseResume.isEnabled = true
|
pauseResume.isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
@ -116,17 +109,22 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
val toQueue = active.filter { it.status == DownloadRepository.Status.Paused.toString() }.toMutableList()
|
val toQueue = active.filter { it.status == DownloadRepository.Status.Paused.toString() }.toMutableList()
|
||||||
toQueue.forEach {
|
toQueue.forEach {
|
||||||
it.status = DownloadRepository.Status.Active.toString()
|
it.status = DownloadRepository.Status.Queued.toString()
|
||||||
downloadViewModel.updateDownload(it)
|
downloadViewModel.updateDownload(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(listOf())
|
||||||
|
}
|
||||||
|
|
||||||
val queuedItems = withContext(Dispatchers.IO){
|
val queuedItems = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getQueued()
|
downloadViewModel.getQueued()
|
||||||
}
|
}
|
||||||
queuedItems.map { it.status = DownloadRepository.Status.Queued.toString() }
|
queuedItems.map {
|
||||||
toQueue.addAll(queuedItems)
|
it.status = DownloadRepository.Status.Queued.toString()
|
||||||
runBlocking {
|
downloadViewModel.updateDownload(it)
|
||||||
downloadViewModel.queueDownloads(toQueue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseResume.isEnabled = true
|
pauseResume.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,20 +218,15 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
}
|
}
|
||||||
activeDownloads.notifyItemChanged(position)
|
activeDownloads.notifyItemChanged(position)
|
||||||
|
|
||||||
val count = withContext(Dispatchers.IO){
|
runBlocking {
|
||||||
downloadViewModel.getActiveDownloads()
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
}.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 {
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.queueDownloads(queue)
|
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){
|
private fun cancelItem(id: Int){
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
|
||||||
import com.deniscerri.ytdlnis.work.TerminalDownloadWorker
|
import com.deniscerri.ytdlnis.work.TerminalDownloadWorker
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.bottomappbar.BottomAppBar
|
import com.google.android.material.bottomappbar.BottomAppBar
|
||||||
|
|
@ -230,27 +231,8 @@ class TerminalActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
initMenu()
|
initMenu()
|
||||||
|
|
||||||
//init syntax highlighter
|
input?.enableTextHighlight()
|
||||||
val highlight = Highlight()
|
output?.enableTextHighlight()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMenu() {
|
private fun initMenu() {
|
||||||
|
|
|
||||||
|
|
@ -113,14 +113,14 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||||
audioFilenameTemplate?.dialogTitle = "${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!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||||
clearCache!!.onPreferenceClickListener =
|
clearCache!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
if (activeDownloadCount == 0){
|
if (activeDownloadCount == 0){
|
||||||
File(FileUtil.getCachePath()).deleteRecursively()
|
File(FileUtil.getCachePath(requireContext())).deleteRecursively()
|
||||||
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
|
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)}"
|
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||||
}else{
|
}else{
|
||||||
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
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() )
|
"queued" -> json.add("queued", backupQueuedDownloads() )
|
||||||
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
|
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
|
||||||
"errored" -> json.add("errored", backupErroredDownloads() )
|
"errored" -> json.add("errored", backupErroredDownloads() )
|
||||||
|
"saved" -> json.add("saved", backupSavedDownloads() )
|
||||||
"cookies" -> json.add("cookies", backupCookies() )
|
"cookies" -> json.add("cookies", backupCookies() )
|
||||||
"templates" -> json.add("templates", backupCommandTemplates() )
|
"templates" -> json.add("templates", backupCommandTemplates() )
|
||||||
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
|
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
|
||||||
|
|
@ -293,6 +294,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
return JsonArray()
|
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 {
|
private suspend fun backupCookies() : JsonArray {
|
||||||
runCatching {
|
runCatching {
|
||||||
val items = withContext(Dispatchers.IO) {
|
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
|
//cookies restore
|
||||||
if(json.has("cookies")){
|
if(json.has("cookies")){
|
||||||
val items = json.getAsJsonArray("cookies")
|
val items = json.getAsJsonArray("cookies")
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,9 @@ import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.storage.StorageManager
|
|
||||||
import android.os.storage.StorageVolume
|
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import androidx.core.net.toFile
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.anggrayudi.storage.callback.FileCallback
|
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.copyFileTo
|
||||||
import com.anggrayudi.storage.file.copyFolderTo
|
import com.anggrayudi.storage.file.copyFolderTo
|
||||||
import com.anggrayudi.storage.file.getAbsolutePath
|
import com.anggrayudi.storage.file.getAbsolutePath
|
||||||
import com.anggrayudi.storage.file.getBasePath
|
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.BuildConfig
|
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.internal.closeQuietly
|
import okhttp3.internal.closeQuietly
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.CopyOption
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
@ -91,8 +85,7 @@ object FileUtil {
|
||||||
if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
|
if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
|
||||||
var destFile: DocumentFile
|
var destFile: DocumentFile
|
||||||
try {
|
try {
|
||||||
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)|(live_chat)".toRegex())){
|
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(.*.part-Frag)|(.*.live_chat)".toRegex())){
|
||||||
it.delete()
|
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,8 +262,17 @@ object FileUtil {
|
||||||
return listOf(context.getString(R.string.unfound_file))
|
return listOf(context.getString(R.string.unfound_file))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCachePath() : String {
|
fun getCachePath(context: Context) : String {
|
||||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/.CACHE"
|
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{
|
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.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
|
@ -546,6 +545,7 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
|
val webpage_urls = mutableListOf<String>()
|
||||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||||
try {
|
try {
|
||||||
val request : YoutubeDLRequest
|
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? = ""
|
var thumb: String? = ""
|
||||||
if (jsonObject.has("thumbnail")) {
|
if (jsonObject.has("thumbnail")) {
|
||||||
thumb = jsonObject.getString("thumbnail")
|
thumb = jsonObject.getString("thumbnail")
|
||||||
|
|
@ -652,6 +646,12 @@ class InfoUtil(private val context: Context) {
|
||||||
urls = urlList.joinToString("\n")
|
urls = urlList.joinToString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val url = if (jsonObject.has("url")){
|
||||||
|
jsonObject.getString("url")
|
||||||
|
}else{
|
||||||
|
jsonObject.getString("webpage_url")
|
||||||
|
}
|
||||||
|
|
||||||
items.add(ResultItem(0,
|
items.add(ResultItem(0,
|
||||||
url,
|
url,
|
||||||
title,
|
title,
|
||||||
|
|
@ -665,6 +665,10 @@ class InfoUtil(private val context: Context) {
|
||||||
chapters
|
chapters
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
webpage_urls.add(jsonObject.getString("webpage_url"))
|
||||||
|
}
|
||||||
|
if (items.size == 1){
|
||||||
|
items[0]?.url = webpage_urls.first()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Looper.prepare().run {
|
Looper.prepare().run {
|
||||||
|
|
@ -929,7 +933,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||||
val cacheDir = FileUtil.getCachePath()
|
val cacheDir = FileUtil.getCachePath(context)
|
||||||
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
||||||
tempFileDir.delete()
|
tempFileDir.delete()
|
||||||
tempFileDir.mkdirs()
|
tempFileDir.mkdirs()
|
||||||
|
|
@ -1009,7 +1013,10 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--force-keyframes-at-cuts")
|
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", " ")
|
request.addOption("--output-na-placeholder", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1018,7 +1025,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.extraCommands.isNotBlank()){
|
if (downloadItem.extraCommands.isNotBlank()){
|
||||||
val conf = File(tempFileDir.absolutePath + "/${System.currentTimeMillis()}.txt")
|
val conf = File(FileUtil.getCachePath(context) + "/${System.currentTimeMillis()}.txt")
|
||||||
conf.createNewFile()
|
conf.createNewFile()
|
||||||
conf.writeText(downloadItem.extraCommands)
|
conf.writeText(downloadItem.extraCommands)
|
||||||
request.addOption(
|
request.addOption(
|
||||||
|
|
@ -1097,7 +1104,7 @@ class InfoUtil(private val context: Context) {
|
||||||
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
|
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
|
||||||
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
|
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
|
||||||
try {
|
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)'\\\"\""
|
val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||||
config.writeText(configData)
|
config.writeText(configData)
|
||||||
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
||||||
|
|
@ -1129,39 +1136,18 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
if (downloadItem.videoPreferences.embedSubs) {
|
if (downloadItem.videoPreferences.embedSubs) {
|
||||||
request.addOption("--embed-subs")
|
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 cont = ""
|
||||||
|
|
||||||
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"
|
|
||||||
val outputContainer = downloadItem.container
|
val outputContainer = downloadItem.container
|
||||||
if(
|
if(
|
||||||
outputContainer.isNotEmpty() &&
|
outputContainer.isNotEmpty() &&
|
||||||
outputContainer != "Default" &&
|
outputContainer != "Default" &&
|
||||||
outputContainer != context.getString(R.string.defaultValue) &&
|
outputContainer != context.getString(R.string.defaultValue) &&
|
||||||
supportedContainers.contains(outputContainer) &&
|
supportedContainers.contains(outputContainer)
|
||||||
!outputContainer.equals(downloadItem.format.container, true)
|
|
||||||
){
|
){
|
||||||
|
cont = outputContainer
|
||||||
request.addOption("--merge-output-format", outputContainer.lowercase())
|
request.addOption("--merge-output-format", outputContainer.lowercase())
|
||||||
if (outputContainer != "webm") {
|
if (outputContainer != "webm") {
|
||||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
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){
|
if (downloadItem.videoPreferences.writeSubs){
|
||||||
val subFormat = sharedPreferences.getString("sub_format", "srt")
|
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("--sub-format", "${subFormat}/best")
|
||||||
request.addOption("--convert-subtitles", "srt")
|
request.addOption("--convert-subtitles", "srt")
|
||||||
if (!downloadItem.videoPreferences.embedSubs) {
|
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 -> {
|
DownloadViewModel.Type.command -> {
|
||||||
request.addOption(
|
request.addOption(
|
||||||
"--config-locations",
|
"--config-locations",
|
||||||
File(FileUtil.getCachePath() + "/config[${downloadItem.id}].txt").apply {
|
File(context.cacheDir.absolutePath + "/config[${downloadItem.id}].txt").apply {
|
||||||
writeText(downloadItem.format.format_note)
|
writeText(downloadItem.format.format_note)
|
||||||
}.absolutePath
|
}.absolutePath
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.core.content.FileProvider
|
||||||
import androidx.navigation.NavDeepLinkBuilder
|
import androidx.navigation.NavDeepLinkBuilder
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||||
|
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
|
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.ResumeActivity
|
import com.deniscerri.ytdlnis.receiver.ResumeActivity
|
||||||
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
|
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
|
||||||
|
|
@ -81,7 +82,7 @@ class NotificationUtil(var context: Context) {
|
||||||
return notificationBuilder
|
return notificationBuilder
|
||||||
.setContentTitle(context.getString(R.string.downloading))
|
.setContentTitle(context.getString(R.string.downloading))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
context.resources,
|
||||||
|
|
@ -98,30 +99,11 @@ class NotificationUtil(var context: Context) {
|
||||||
fun createDownloadServiceNotification(
|
fun createDownloadServiceNotification(
|
||||||
pendingIntent: PendingIntent?,
|
pendingIntent: PendingIntent?,
|
||||||
title: String?,
|
title: String?,
|
||||||
workID: Int,
|
itemID: Int,
|
||||||
channel: String
|
|
||||||
): Notification {
|
): 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
|
return notificationBuilder
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
|
|
@ -140,13 +122,11 @@ class NotificationUtil(var context: Context) {
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||||
.clearActions()
|
.clearActions()
|
||||||
.addAction(0, context.getString(R.string.pause), pauseNotificationPendingIntent)
|
|
||||||
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createResumeDownload(workID: Int, title: String?, channel: String){
|
fun createResumeDownload(itemID: Int, title: String?){
|
||||||
val notificationBuilder = getBuilder(channel)
|
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
|
|
@ -162,16 +142,16 @@ class NotificationUtil(var context: Context) {
|
||||||
.clearActions()
|
.clearActions()
|
||||||
|
|
||||||
val intent = Intent(context, ResumeActivity::class.java)
|
val intent = Intent(context, ResumeActivity::class.java)
|
||||||
intent.putExtra("workID", workID)
|
intent.putExtra("itemID", itemID)
|
||||||
val resumeNotificationPendingIntent = PendingIntent.getActivity(
|
val resumeNotificationPendingIntent = PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
workID,
|
itemID,
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
notificationBuilder.addAction(0, context.getString(R.string.resume), resumeNotificationPendingIntent)
|
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){
|
fun createUpdatingItemNotification(channel: String){
|
||||||
|
|
@ -315,10 +295,33 @@ class NotificationUtil(var context: Context) {
|
||||||
var contentText = ""
|
var contentText = ""
|
||||||
if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + "\n"
|
if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + "\n"
|
||||||
contentText += desc.replace("\\[.*?\\] ".toRegex(), "")
|
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 {
|
try {
|
||||||
notificationBuilder.setProgress(100, progress, false)
|
notificationBuilder.setProgress(100, progress, false)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
.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())
|
notificationManager.notify(id, notificationBuilder.build())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
@ -388,8 +391,7 @@ class NotificationUtil(var context: Context) {
|
||||||
fun createFormatsUpdateNotification(workID: Int): Notification {
|
fun createFormatsUpdateNotification(workID: Int): Notification {
|
||||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||||
|
|
||||||
val cancelIntent = Intent(context, CancelDownloadNotificationReceiver::class.java)
|
val cancelIntent = Intent(context, CancelWorkReceiver::class.java)
|
||||||
cancelIntent.putExtra("cancel", "")
|
|
||||||
cancelIntent.putExtra("workID", workID)
|
cancelIntent.putExtra("workID", workID)
|
||||||
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
|
|
@ -473,7 +475,7 @@ class NotificationUtil(var context: Context) {
|
||||||
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
|
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
|
||||||
const val DOWNLOAD_FINISHED_CHANNEL_ID = "3"
|
const val DOWNLOAD_FINISHED_CHANNEL_ID = "3"
|
||||||
const val DOWNLOAD_FINISHED_NOTIFICATION_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 DOWNLOAD_UPDATING_NOTIFICATION_ID = 5
|
||||||
const val FORMAT_UPDATING_NOTIFICATION_ID = 6
|
const val FORMAT_UPDATING_NOTIFICATION_ID = 6
|
||||||
const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7
|
const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ShapeDrawable
|
import android.graphics.drawable.ShapeDrawable
|
||||||
import android.graphics.drawable.shapes.OvalShape
|
import android.graphics.drawable.shapes.OvalShape
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
@ -30,7 +31,6 @@ import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.isVisible
|
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.textfield.TextInputLayout
|
||||||
import com.google.android.material.timepicker.MaterialTimePicker
|
import com.google.android.material.timepicker.MaterialTimePicker
|
||||||
import com.google.android.material.timepicker.TimeFormat
|
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.Dispatchers
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
object UiUtil {
|
object UiUtil {
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
|
|
@ -85,7 +90,7 @@ object UiUtil {
|
||||||
if (audioFormats.isNullOrEmpty()){
|
if (audioFormats.isNullOrEmpty()){
|
||||||
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
|
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
|
||||||
}else{
|
}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_note).text = title
|
||||||
}
|
}
|
||||||
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
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){
|
fun showCommandTemplateCreationOrUpdatingSheet(item: CommandTemplate?, context: Activity, lifeCycle: LifecycleOwner, commandTemplateViewModel: CommandTemplateViewModel, newTemplate: (newTemplate: CommandTemplate) -> Unit){
|
||||||
val bottomSheet = BottomSheetDialog(context)
|
val bottomSheet = BottomSheetDialog(context)
|
||||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
|
@ -658,7 +669,7 @@ object UiUtil {
|
||||||
|
|
||||||
linearLayout!!.removeAllViews()
|
linearLayout!!.removeAllViews()
|
||||||
list.forEach {template ->
|
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.title).text = template.title
|
||||||
item.findViewById<TextView>(R.id.content).text = template.content
|
item.findViewById<TextView>(R.id.content).text = template.content
|
||||||
item.setOnClickListener {
|
item.setOnClickListener {
|
||||||
|
|
@ -719,8 +730,12 @@ object UiUtil {
|
||||||
extraCommandsClicked: () -> Unit
|
extraCommandsClicked: () -> Unit
|
||||||
){
|
){
|
||||||
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
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!!.isChecked = items.all { it.videoPreferences.embedSubs }
|
||||||
embedSubs.setOnClickListener {
|
embedSubs.setOnClickListener {
|
||||||
|
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
|
||||||
embedSubsClicked(embedSubs.isChecked)
|
embedSubsClicked(embedSubs.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -878,8 +893,6 @@ object UiUtil {
|
||||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
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)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
|
@ -890,11 +903,11 @@ object UiUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSubtitles.setOnCheckedChangeListener { _, _ ->
|
saveSubtitles.setOnCheckedChangeListener { _, _ ->
|
||||||
if (saveSubtitles.isChecked) subtitleLanguages.visibility = View.VISIBLE
|
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
|
||||||
else subtitleLanguages.visibility = View.GONE
|
|
||||||
saveSubtitlesClicked(saveSubtitles.isChecked)
|
saveSubtitlesClicked(saveSubtitles.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
|
||||||
subtitleLanguages.setOnClickListener {
|
subtitleLanguages.setOnClickListener {
|
||||||
subtitleLanguagesClicked()
|
subtitleLanguagesClicked()
|
||||||
}
|
}
|
||||||
|
|
@ -1074,8 +1087,10 @@ object UiUtil {
|
||||||
fun configureCommand(
|
fun configureCommand(
|
||||||
view: View,
|
view: View,
|
||||||
size: Int,
|
size: Int,
|
||||||
|
shortcutCount: Int,
|
||||||
newTemplateClicked: () -> Unit,
|
newTemplateClicked: () -> Unit,
|
||||||
editSelectedClicked: () -> Unit,
|
editSelectedClicked: () -> Unit,
|
||||||
|
shortcutClicked: suspend () -> Unit,
|
||||||
){
|
){
|
||||||
val newTemplate : Chip = view.findViewById(R.id.newTemplate)
|
val newTemplate : Chip = view.findViewById(R.id.newTemplate)
|
||||||
newTemplate.setOnClickListener {
|
newTemplate.setOnClickListener {
|
||||||
|
|
@ -1087,6 +1102,14 @@ object UiUtil {
|
||||||
editSelected.setOnClickListener {
|
editSelected.setOnClickListener {
|
||||||
editSelectedClicked()
|
editSelectedClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val shortcuts = view.findViewById<View>(R.id.shortcut)
|
||||||
|
shortcuts.isEnabled = shortcutCount > 0
|
||||||
|
shortcuts.setOnClickListener {
|
||||||
|
runBlocking {
|
||||||
|
shortcutClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1117,6 +1140,31 @@ object UiUtil {
|
||||||
.build()
|
.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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.transform
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -61,29 +59,34 @@ class DownloadWorker(
|
||||||
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
||||||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
||||||
|
|
||||||
runningYTDLInstances.addAll(dao.getActiveDownloadsList().map { it.id })
|
|
||||||
|
|
||||||
val pendingIntent = NavDeepLinkBuilder(context)
|
val pendingIntent = NavDeepLinkBuilder(context)
|
||||||
.setGraph(R.navigation.nav_graph)
|
.setGraph(R.navigation.nav_graph)
|
||||||
.setDestination(R.id.downloadQueueMainFragment)
|
.setDestination(R.id.downloadQueueMainFragment)
|
||||||
.createPendingIntent()
|
.createPendingIntent()
|
||||||
|
|
||||||
var notification = notificationUtil.createDefaultWorkerNotification()
|
val workNotif = notificationUtil.createDefaultWorkerNotification()
|
||||||
val foregroundInfo = ForegroundInfo(Random.nextInt(1000000000), notification)
|
val foregroundInfo = ForegroundInfo(Random.nextInt(1000000000), workNotif)
|
||||||
setForegroundAsync(foregroundInfo)
|
setForegroundAsync(foregroundInfo)
|
||||||
|
|
||||||
queuedItems.collectLatest { items ->
|
queuedItems.collectLatest { items ->
|
||||||
|
runningYTDLInstances.clear()
|
||||||
|
dao.getActiveDownloadsList().forEach {
|
||||||
|
runningYTDLInstances.add(it.id)
|
||||||
|
}
|
||||||
|
|
||||||
val running = ArrayList(runningYTDLInstances)
|
val running = ArrayList(runningYTDLInstances)
|
||||||
if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||||
|
|
||||||
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
||||||
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
|
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)
|
runningYTDLInstances.add(downloadItem.id)
|
||||||
|
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt())
|
||||||
|
notificationUtil.notify(downloadItem.id.toInt(), notification)
|
||||||
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
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)
|
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
||||||
downloadItem.status = DownloadRepository.Status.Active.toString()
|
downloadItem.status = DownloadRepository.Status.Active.toString()
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
|
@ -91,7 +94,7 @@ class DownloadWorker(
|
||||||
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
val cacheDir = FileUtil.getCachePath()
|
val cacheDir = FileUtil.getCachePath(context)
|
||||||
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
||||||
tempFileDir.delete()
|
tempFileDir.delete()
|
||||||
tempFileDir.mkdirs()
|
tempFileDir.mkdirs()
|
||||||
|
|
@ -126,6 +129,7 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
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))
|
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
|
||||||
|
|
@ -143,6 +147,8 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
runningYTDLInstances.remove(downloadItem.id)
|
runningYTDLInstances.remove(downloadItem.id)
|
||||||
|
FileUtil.deleteConfigFiles(request)
|
||||||
|
|
||||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||||
runBlocking {
|
runBlocking {
|
||||||
var finalPaths : List<String>?
|
var finalPaths : List<String>?
|
||||||
|
|
@ -209,6 +215,8 @@ class DownloadWorker(
|
||||||
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
runningYTDLInstances.remove(downloadItem.id)
|
runningYTDLInstances.remove(downloadItem.id)
|
||||||
|
FileUtil.deleteConfigFiles(request)
|
||||||
|
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||||
}
|
}
|
||||||
|
|
@ -246,6 +254,7 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eligibleDownloads.isNotEmpty()){
|
if (eligibleDownloads.isNotEmpty()){
|
||||||
eligibleDownloads.forEach { it.status = DownloadRepository.Status.Active.toString() }
|
eligibleDownloads.forEach { it.status = DownloadRepository.Status.Active.toString() }
|
||||||
dao.updateMultiple(eligibleDownloads)
|
dao.updateMultiple(eligibleDownloads)
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class TerminalDownloadWorker(
|
||||||
|
|
||||||
val intent = Intent(context, TerminalActivity::class.java)
|
val intent = Intent(context, TerminalActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
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)
|
val foregroundInfo = ForegroundInfo(itemId, notification)
|
||||||
setForegroundAsync(foregroundInfo)
|
setForegroundAsync(foregroundInfo)
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ class TerminalDownloadWorker(
|
||||||
|
|
||||||
request.addOption(
|
request.addOption(
|
||||||
"--config-locations",
|
"--config-locations",
|
||||||
File(context.cacheDir.absolutePath + "/config${System.currentTimeMillis()}.txt").apply {
|
File(context.cacheDir.absolutePath + "/config-TERMINAL[${System.currentTimeMillis()}].txt").apply {
|
||||||
writeText(command)
|
writeText(command)
|
||||||
}.absolutePath
|
}.absolutePath
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,6 @@ class UpdateYTDLWorker(
|
||||||
workerParams: WorkerParameters
|
workerParams: WorkerParameters
|
||||||
) : CoroutineWorker(context, workerParams) {
|
) : CoroutineWorker(context, workerParams) {
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val notification = NotificationUtil(context).createYTDLUpdateNotification()
|
|
||||||
val foregroundInfo = ForegroundInfo(System.currentTimeMillis().toInt(), notification)
|
|
||||||
setForeground(foregroundInfo)
|
|
||||||
UpdateUtil(context).updateYoutubeDL()
|
UpdateUtil(context).updateYoutubeDL()
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
<LinearLayout
|
<LinearLayout android:id="@+id/adjust"
|
||||||
android:id="@+id/adjust"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:paddingVertical="10dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
android:orientation="vertical"
|
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
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
@ -41,5 +40,12 @@
|
||||||
|
|
||||||
</com.google.android.material.chip.ChipGroup>
|
</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"
|
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:enabled="false"
|
||||||
android:checked="false"
|
android:checked="false"
|
||||||
android:text="@string/subtitle_languages"/>
|
android:text="@string/subtitle_languages"/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,76 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:checkable="true"
|
||||||
android:orientation="horizontal"
|
android:clickable="true"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:focusable="true"
|
||||||
android:padding="15dp">
|
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
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/title"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:padding="15dp">
|
||||||
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" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/content"
|
android:layout_width="0dp"
|
||||||
android:layout_width="0dp"
|
android:visibility="gone"
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/id"
|
||||||
android:layout_marginEnd="15dp"
|
android:layout_height="0dp"
|
||||||
android:maxLines="2"
|
tools:ignore="MissingConstraints" />
|
||||||
android:ellipsize="end"
|
|
||||||
android:textSize="12sp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/useInExtraCommands"
|
android:id="@+id/title"
|
||||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
android:layout_width="0dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_marginEnd="10dp"
|
||||||
android:background="@drawable/rounded_corner"
|
android:textSize="15sp"
|
||||||
android:backgroundTint="?attr/colorSecondary"
|
android:textStyle="bold"
|
||||||
android:clickable="false"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:visibility="gone"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:ellipsize="end"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
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>
|
<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"
|
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:textAlignment="textStart"
|
android:textAlignment="textStart"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
|
|
||||||
<LinearLayout android:layout_width="match_parent"
|
<LinearLayout android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingBottom="20dp"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="@string/format"
|
android:text="@string/format"
|
||||||
android:textSize="17sp"
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
android:layout_margin="20dp"
|
android:layout_margin="20dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -23,24 +23,23 @@
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="30"
|
||||||
|
android:maxLines="2"
|
||||||
android:text="@string/format_id"
|
android:text="@string/format_id"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold"
|
android:layout_margin="10dp"
|
||||||
android:layout_marginHorizontal="10dp"
|
|
||||||
android:layout_marginVertical="5dp"
|
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/format_id_value"
|
android:id="@+id/format_id_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="70"
|
||||||
android:textSize="18sp"
|
android:textSize="14sp"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_margin="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -54,26 +53,26 @@
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textStyle="bold"
|
android:layout_weight="30"
|
||||||
android:textSize="14sp"
|
android:maxLines="2"
|
||||||
|
|
||||||
android:text="@string/url"
|
android:text="@string/url"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
android:layout_marginVertical="5dp"
|
android:layout_margin="10dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/format_url_value"
|
android:id="@+id/format_url_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:gravity="end"
|
android:maxLines="3"
|
||||||
android:maxLines="2"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:textSize="18sp"
|
android:layout_weight="70"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
@ -87,23 +86,23 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textStyle="bold"
|
android:layout_weight="30"
|
||||||
|
android:maxLines="2"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
|
|
||||||
android:text="@string/container"
|
android:text="@string/container"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_margin="10dp"
|
||||||
android:layout_marginVertical="5dp"
|
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/container_value"
|
android:id="@+id/container_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textSize="18sp"
|
android:layout_weight="70"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -117,22 +116,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textStyle="bold"
|
android:layout_weight="30"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:text="@string/codec"
|
android:text="@string/codec"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_margin="10dp"
|
||||||
android:layout_marginVertical="5dp"
|
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/codec_value"
|
android:id="@+id/codec_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textSize="18sp"
|
android:layout_weight="70"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -146,22 +145,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textStyle="bold"
|
android:layout_weight="30"
|
||||||
android:textSize="14sp"
|
|
||||||
android:text="@string/file_size"
|
android:text="@string/file_size"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
android:layout_marginVertical="5dp"
|
android:layout_margin="10dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/filesize_value"
|
android:id="@+id/filesize_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textSize="18sp"
|
android:layout_weight="70"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -175,22 +174,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:text="@string/quality"
|
android:text="@string/quality"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_weight="30"
|
||||||
android:layout_marginVertical="5dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/format_note_value"
|
android:id="@+id/format_note_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textSize="18sp"
|
android:layout_weight="70"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -204,22 +203,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textStyle="bold"
|
android:layout_weight="30"
|
||||||
android:textSize="14sp"
|
|
||||||
android:text="FPS"
|
android:text="FPS"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
android:layout_marginVertical="5dp"
|
android:layout_margin="10dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/fps_value"
|
android:id="@+id/fps_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textSize="18sp"
|
android:layout_weight="70"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
@ -233,22 +232,22 @@
|
||||||
android:weightSum="100"
|
android:weightSum="100"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textStyle="bold"
|
android:layout_weight="30"
|
||||||
android:textSize="14sp"
|
|
||||||
android:text="@string/audio_samplerate"
|
android:text="@string/audio_samplerate"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
android:layout_marginVertical="5dp"
|
android:layout_margin="10dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/asr_value"
|
android:id="@+id/asr_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:textSize="18sp"
|
android:layout_weight="70"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:textSize="14sp"
|
||||||
|
android:layout_margin="10dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="10dp"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/content"
|
android:id="@+id/content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
|
|
@ -22,32 +22,26 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:maxLines="10"
|
android:maxLines="99999"
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<TextView
|
||||||
android:id="@+id/template"
|
android:layout_width="wrap_content"
|
||||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingBottom="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:hint="@string/command_templates">
|
android:paddingTop="10dp"
|
||||||
|
android:text="@string/command" />
|
||||||
|
|
||||||
<AutoCompleteTextView
|
<include layout="@layout/command_template_item" />
|
||||||
android:id="@+id/template_textview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/outputPath"
|
android:id="@+id/outputPath"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/save_dir"
|
android:hint="@string/save_dir"
|
||||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||||
|
|
@ -62,7 +56,7 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/freespace"
|
android:id="@+id/freespace"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="20dp"
|
||||||
android:paddingVertical="5dp"
|
android:paddingVertical="5dp"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
android:textColor="@android:color/tab_indicator_text"
|
android:textColor="@android:color/tab_indicator_text"
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@
|
||||||
<Preference
|
<Preference
|
||||||
app:icon="@drawable/baseline_drive_file_move_24"
|
app:icon="@drawable/baseline_drive_file_move_24"
|
||||||
app:key="move_cache"
|
app:key="move_cache"
|
||||||
app:isPreferenceVisible="false"
|
|
||||||
android:summary="@string/move_temporary_files_summary"
|
android:summary="@string/move_temporary_files_summary"
|
||||||
app:title="@string/move_temporary_files" />
|
app:title="@string/move_temporary_files" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
android:icon="@drawable/baseline_subject_24"
|
android:icon="@drawable/baseline_subject_24"
|
||||||
app:key="subs_lang"
|
app:key="subs_lang"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:defaultValue="all"
|
app:defaultValue="en.*,.*-orig"
|
||||||
app:title="@string/subtitle_languages" />
|
app:title="@string/subtitle_languages" />
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue