CHANGELOG

Added option to turn off usage of cookies
Some fixes with the cut screen
Added ability to save command templates as extra commands by default on every downloadcard/AddExtraCommandsDialog
fixed app not saving the proper youtube link and having to refetch data unnecessarily
removed question mark from settings restore dialog
fixed app scrolling in the tablayout in the download queue screen while you are dragging the vertical scrollbar
fixed app not destroying active downloads when you terminate the app
pull/248/head
deniscerri 3 years ago
parent bc3bcbf58b
commit c44929d5cb
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -10,7 +10,7 @@ def properties = new Properties()
def versionMajor = 1
def versionMinor = 6
def versionPatch = 3
def versionBuild = 10 // bump for dogfood builds, public betas, etc.
def versionBuild = 13 // bump for dogfood builds, public betas, etc.
def versionExt = ""
if (versionBuild > 0){
@ -152,18 +152,13 @@ dependencies {
implementation 'androidx.preference:preference:1.2.0'
implementation "androidx.navigation:navigation-fragment-ktx:$navVer"
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.test.ext:junit-ktx:1.1.5'
implementation 'androidx.benchmark:benchmark-macro:1.1.1'
testImplementation "junit:junit:$junitVer"
androidTestImplementation "junit:junit:$junitVer"
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer"
androidTestImplementation "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha16"
androidTestImplementation "androidx.test:runner:1.5.2"
androidTestImplementation "androidx.test:core:1.5.0"
androidTestImplementation "androidx.test:rules:1.5.0"
androidTestImplementation "androidx.navigation:navigation-testing:$navVer"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "com.devbrackets.android:exomedia:4.3.0"

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "bcd20f64b54fbc6e470be0d15059a66e",
"identityHash": "d29e09789b7c96b5551b80da253b7330",
"entities": [
{
"tableName": "results",
@ -180,7 +180,7 @@
},
{
"tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER, `workID` BLOB)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER)",
"fields": [
{
"fieldPath": "id",
@ -324,12 +324,6 @@
"columnName": "logID",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "workID",
"columnName": "workID",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
@ -343,7 +337,7 @@
},
{
"tableName": "commandTemplates",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT false)",
"fields": [
{
"fieldPath": "id",
@ -362,6 +356,13 @@
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "useAsExtraCommand",
"columnName": "useAsExtraCommand",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
@ -511,7 +512,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bcd20f64b54fbc6e470be0d15059a66e')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd29e09789b7c96b5551b80da253b7330')"
]
}
}

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis
import android.app.Application
import android.os.Looper
import android.widget.Toast
import androidx.core.content.edit
import androidx.preference.PreferenceManager
@ -66,7 +67,9 @@ class App : Application() {
)
}catch (e: Exception){
Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show()
Looper.prepare().runCatching {
Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show()
}
e.printStackTrace()
}
}

@ -41,6 +41,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.navOptions
import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
@ -56,7 +57,10 @@ import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.navigation.NavigationView
import com.google.android.material.navigationrail.NavigationRailView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.Reader
@ -116,14 +120,14 @@ class MainActivity : BaseActivity() {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
graph.setStartDestination(R.id.homeFragment)
when(sharedPreferences.getString("start_destination", "")) {
"History" -> graph.setStartDestination(R.id.historyFragment)
"More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment)
}
navController.graph = graph
// val graph = navController.navInflater.inflate(R.navigation.nav_graph)
// graph.setStartDestination(R.id.homeFragment)
// when(sharedPreferences.getString("start_destination", "")) {
// "History" -> graph.setStartDestination(R.id.historyFragment)
// "More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment)
// }
//
// navController.graph = graph
if (navigationView is NavigationBarView){
(navigationView as NavigationBarView).setupWithNavController(navController)
@ -172,11 +176,24 @@ class MainActivity : BaseActivity() {
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
terminateDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
if (doNotShowAgain){
sharedPreferences.edit().putBoolean("ask_terminate_app", false).apply()
runBlocking {
val job : Job = lifecycleScope.launch(Dispatchers.IO) {
val activeDownloads = downloadViewModel.getActiveDownloads().toMutableList()
activeDownloads.map { it.status = DownloadRepository.Status.Queued.toString() }
activeDownloads.forEach {
downloadViewModel.updateDownload(it)
}
}
runBlocking {
job.join()
if (doNotShowAgain){
sharedPreferences.edit().putBoolean("ask_terminate_app", false).apply()
}
finishAndRemoveTask()
finishAffinity()
exitProcess(0)
}
}
finishAndRemoveTask()
exitProcess(0)
}
terminateDialog.show()
}else{
@ -216,6 +233,14 @@ class MainActivity : BaseActivity() {
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putBundle("nav_state", navController.saveState())
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
navController.restoreState(savedInstanceState.getBundle("nav_state"))
}
private fun View.visibilityChanged(action: (View) -> Unit) {
this.viewTreeObserver.addOnGlobalLayoutListener {

@ -148,16 +148,10 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
pauseButton.setOnClickListener {
if (pauseButton.tag == ActiveDownloadAction.Pause){
onItemClickListener.onPauseClick(item.id, ActiveDownloadAction.Pause)
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
if (progressBar.progress == 0) progressBar.isIndeterminate = false
cancelButton.visibility = View.VISIBLE
pauseButton.tag = ActiveDownloadAction.Resume
}else{
onItemClickListener.onPauseClick(item.id, ActiveDownloadAction.Resume)
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
progressBar.isIndeterminate = true
cancelButton.visibility = View.GONE
pauseButton.tag = ActiveDownloadAction.Pause
}
}

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis.adapter
import android.app.Activity
import android.opengl.Visibility
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -46,18 +47,16 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
val content = card.findViewById<TextView>(R.id.content)
content.text = item?.content
// val check = card.findViewById<Button>(R.id.check)
//
// check.setOnClickListener {
// onItemClickListener.onSelected(item!!)
// }
val useInExtraCommands = card.findViewById<TextView>(R.id.useInExtraCommands)
if (item!!.useAsExtraCommand) useInExtraCommands.visibility = View.VISIBLE
else useInExtraCommands.visibility = View.GONE
card.setOnClickListener {
onItemClickListener.onItemClick(item!!, position)
onItemClickListener.onItemClick(item, position)
}
card.setOnLongClickListener {
onItemClickListener.onDelete(item!!); true
onItemClickListener.onDelete(item); true
}
}

@ -8,7 +8,7 @@ import com.deniscerri.ytdlnis.database.models.*
@TypeConverters(Converters::class)
@Database(
entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class, SearchHistoryItem::class, TemplateShortcut::class, CookieItem::class, LogItem::class],
version = 8,
version = 9,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
@ -16,7 +16,8 @@ import com.deniscerri.ytdlnis.database.models.*
AutoMigration (from = 4, to = 5),
AutoMigration (from = 5, to = 6),
AutoMigration (from = 6, to = 7),
AutoMigration (from = 7, to = 8)
AutoMigration (from = 7, to = 8),
AutoMigration (from = 8, to = 9)
]
)
abstract class DBManager : RoomDatabase(){

@ -12,6 +12,8 @@ interface CommandTemplateDao {
@Query("SELECT * FROM commandTemplates ORDER BY id DESC")
fun getAllTemplatesFlow() : Flow<List<CommandTemplate>>
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1")
fun getAllTemplatesAsExtraCommands() : List<String>
@Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>>

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@ -10,5 +11,7 @@ data class CommandTemplate(
@PrimaryKey(autoGenerate = true)
var id: Long,
var title: String,
var content: String
var content: String,
@ColumnInfo(defaultValue = "false")
var useAsExtraCommand: Boolean
)

@ -8,7 +8,7 @@ import androidx.room.PrimaryKey
data class ResultItem(
@PrimaryKey(autoGenerate = true)
var id: Long,
val url: String,
var url: String,
var title: String,
var author: String,
val duration: String,

@ -48,7 +48,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
suspend fun getOne(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
val infoUtil = InfoUtil(context)
try {
val v = infoUtil.getVideo(inputQuery)
val v = infoUtil.getYoutubeVideo(inputQuery)
if (resetResults) {
deleteAll()
itemCount.value = v.size

@ -8,6 +8,7 @@ import android.content.res.Resources
import android.net.ConnectivityManager
import android.os.Looper
import android.util.DisplayMetrics
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
@ -18,8 +19,10 @@ import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkRequest
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
@ -42,8 +45,11 @@ import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Locale
import java.util.concurrent.TimeUnit
@ -71,6 +77,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private val formatIDPreference: List<String>
private val audioFormatIDPreference: List<String>
private val resources : Resources
private var extraCommands: String = ""
enum class Type {
audio, video, command
}
@ -90,6 +97,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
savedDownloads = repository.savedDownloads.asLiveData()
cancelledDownloads = repository.cancelledDownloads.asLiveData()
erroredDownloads = repository.erroredDownloads.asLiveData()
viewModelScope.launch(Dispatchers.IO){
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands = commandTemplateDao.getAllTemplatesAsExtraCommands().joinToString(" ")
}
}
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",")
@ -190,7 +202,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
container!!,
"",
resultItem.formats,
downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Queued.toString(), 0, null
downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences, extraCommands, customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Queued.toString(), 0, null
)
}
@ -312,7 +324,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
container,
"",
ArrayList(),
path, historyItem.website, "", "", audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Queued.toString(), 0, null
path, historyItem.website, "", "", audioPreferences, videoPreferences, extraCommands, customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Queued.toString(), 0, null
)
}
@ -415,6 +427,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
repository.insert(item)
}
fun insertAll(items: List<DownloadItem>)= viewModelScope.launch(Dispatchers.IO){
items.forEach{
repository.insert(it)
}
}
fun deleteCancelled() = viewModelScope.launch(Dispatchers.IO) {
repository.deleteCancelled()
}
@ -473,10 +491,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
exists = true
}else{
if (it.id == 0L){
val insert = async {repository.insert(it)}
val id = insert.await()
val id = runBlocking {repository.insert(it)}
it.id = id
}else{
}else if (it.status == DownloadRepository.Status.Queued.toString()){
repository.update(it)
}
@ -489,7 +506,6 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}
}
queuedItems.forEach {
val currentTime = System.currentTimeMillis()
var delay = if (it.downloadStartTime != 0L){

@ -51,6 +51,10 @@ class ShareActivity : BaseActivity() {
private lateinit var sharedPreferences: SharedPreferences
private var quickDownload by Delegates.notNull<Boolean>()
override fun onPause() {
super.onPause()
finishAndRemoveTask()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

@ -0,0 +1,169 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class AddExtraCommandsDialog(private val item: DownloadItem, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {
private lateinit var infoUtil: InfoUtil
private lateinit var sharedPreferences: SharedPreferences
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
infoUtil = InfoUtil(requireActivity().applicationContext)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout to use as dialog or embedded fragment
return inflater.inflate(R.layout.extra_commands_bottom_sheet, container, false)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
return dialog
}
@SuppressLint("RestrictedApi", "SetTextI18n", "UseGetLayoutInflater")
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE or
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val view = LayoutInflater.from(context).inflate(R.layout.result_card_details, null)
dialog.setContentView(view)
}
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val behavior = BottomSheetBehavior.from(view.parent as View)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
val text = view.findViewById<EditText>(R.id.command)
val templates = view.findViewById<Button>(R.id.commands)
val shortcuts = view.findViewById<Button>(R.id.shortcuts)
val currentCommand = infoUtil.buildYoutubeDLRequest(item).buildCommand().joinToString(" ")
view.findViewById<TextView>(R.id.currentText)?.text = currentCommand
text?.setText(item.extraCommands)
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
text!!.postDelayed({
text.setSelection(text.length())
text.requestFocus()
imm.showSoftInput(text, 0)
}, 300)
var templateCount = 0
var shortcutCount = 0
lifecycleScope.launch {
templateCount = withContext(Dispatchers.IO){
commandTemplateViewModel.getTotalNumber()
}
templates.isEnabled = templateCount != 0
shortcutCount = withContext(Dispatchers.IO){
commandTemplateViewModel.getTotalShortcutNumber()
}
shortcuts.isEnabled = shortcutCount != 0
}
templates.setOnClickListener {
if (templateCount == 0){
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ template ->
text.text.insert(text.selectionStart, "${template.content} ")
text.postDelayed({
text.requestFocus()
imm.showSoftInput(text, 0)
}, 200)
}
}
}
}
shortcuts.setOnClickListener {
lifecycleScope.launch {
if (shortcutCount > 0){
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel) {sh ->
text.text.insert(text.selectionStart, " $sh ")
}
}
}
}
val ok = view.findViewById<Button>(R.id.okButton)
ok?.setOnClickListener {
callback.onChangeExtraCommand(text.text.toString())
this.dismiss()
}
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
cleanUp()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
cleanUp()
}
private fun cleanUp(){
kotlin.runCatching {
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("extraCommands")!!).commit()
}
}
}
interface ExtraCommandsListener {
fun onChangeExtraCommand(c: String)
}

@ -8,6 +8,7 @@ import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Range
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
@ -58,7 +59,7 @@ import java.util.concurrent.Executors
import kotlin.properties.Delegates
class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls : String?, private var chapters: List<ChapterItem>?, private val listener: VideoCutListener) : BottomSheetDialogFragment() {
class CutVideoBottomSheetDialog(private val item: DownloadItem, private var urls : String?, private var chapters: List<ChapterItem>?, private val listener: VideoCutListener) : BottomSheetDialogFragment() {
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var infoUtil: InfoUtil
private lateinit var player: ExoPlayer
@ -84,7 +85,6 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
private var timeSeconds by Delegates.notNull<Int>()
private lateinit var selectedCuts: MutableList<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
infoUtil = InfoUtil(requireActivity().applicationContext)
@ -157,12 +157,12 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
if (urls.isNullOrEmpty()) {
infoUtil.getStreamingUrlAndChapters(item.url)
}else {
urls.split("\n").toMutableList()
urls!!.split("\n").toMutableList()
}
}
if (data.isEmpty()) throw Exception("No Data found!")
if (chapters!!.isEmpty()){
if (chapters!!.isEmpty() && urls!!.isBlank()){
try{
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
chapters = Gson().fromJson(data.first().toString(), listType)
@ -239,26 +239,31 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
fromTextInput.editText!!.setText("0:00")
toTextInput.editText!!.setText(item.duration)
rangeSlider.addOnChangeListener { rangeSlider, _, _ ->
val values = rangeSlider.values
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
rangeSlider.addOnSliderTouchListener(object : RangeSlider.OnSliderTouchListener {
override fun onStartTrackingTouch(slider: RangeSlider) {
val startTimestampString = infoUtil.formatIntegerDuration(startTimestamp, Locale.US)
val endTimestampString = infoUtil.formatIntegerDuration(endTimestamp, Locale.US)
}
fromTextInput.editText!!.setText(startTimestampString)
toTextInput.editText!!.setText(endTimestampString)
override fun onStopTrackingTouch(slider: RangeSlider) {
val values = rangeSlider.values
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
val startTimestampString = infoUtil.formatIntegerDuration(startTimestamp, Locale.US)
val endTimestampString = infoUtil.formatIntegerDuration(endTimestamp, Locale.US)
okBtn.isEnabled = !(values[0] == 0F && values[1] == 100F)
fromTextInput.editText!!.setText(startTimestampString)
toTextInput.editText!!.setText(endTimestampString)
try {
player.seekTo((startTimestamp * 1000).toLong())
player.play()
}catch (ignored: Exception) {}
}
okBtn.isEnabled = !(values[0] == 0F && values[1] == 100F)
try {
player.seekTo((startTimestamp * 1000).toLong())
player.play()
}catch (ignored: Exception) {}
}
})
fromTextInput.editText!!.setOnKeyListener(object : View.OnKeyListener {
@ -271,20 +276,23 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
fromTextInput.editText!!.clearFocus()
val seconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
if (seconds == 0) {
fromTextInput.editText!!.setText(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
return true
}
val endSeconds = convertStringToTimestamp(toTextInput.editText!!.text.toString())
val startValue = (seconds.toFloat() / endTimestamp) * 100
if (startValue > 100){
val endValue = (endSeconds.toFloat() / endTimestamp) * 100
if (seconds == 0) {
fromTextInput.editText!!.setText(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
}else if (startValue > 100){
fromTextInput.editText!!.setText(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
return true
}
rangeSlider.setValues(startValue, rangeSlider.valueTo)
val startValueTimeStampSeconds = (startValue.toInt() * timeSeconds) / 100
fromTextInput.editText!!.setText(infoUtil.formatIntegerDuration(startValueTimeStampSeconds, Locale.US))
rangeSlider.setValues(startValue, endValue)
okBtn.isEnabled = !(startValue == 0F && endValue == 100F)
try {
player.seekTo((((startValue * timeSeconds) / 100) * 1000).toLong())
player.play()
}catch (ignored: Exception) {}
return true
}
@ -299,24 +307,28 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
if ((event!!.action == KeyEvent.ACTION_DOWN) &&
(keyCode == KeyEvent.KEYCODE_ENTER)) {
val values = rangeSlider.values
val endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100
toTextInput.editText!!.clearFocus()
val startSeconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
val seconds = convertStringToTimestamp(toTextInput.editText!!.text.toString())
if (seconds == 0) {
toTextInput.editText!!.setText(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
return true
}
val startValue = (startSeconds.toFloat() / endTimestamp) * 100
val endValue = (seconds.toFloat() / endTimestamp) * 100
if (endValue > 100 || endValue <= rangeSlider.valueFrom.toInt()){
if (seconds == 0) {
toTextInput.editText!!.setText(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
}else if (endValue > 100 || endValue <= rangeSlider.valueFrom.toInt()){
toTextInput.editText!!.setText(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
return true
}
rangeSlider.setValues(rangeSlider.valueFrom, endValue)
val endValueTimeStampSeconds = (endValue.toInt() * timeSeconds) / 100
toTextInput.editText!!.setText(infoUtil.formatIntegerDuration(endValueTimeStampSeconds, Locale.US))
rangeSlider.setValues(startValue, endValue)
okBtn.isEnabled = !(startValue == 0F && endValue == 100F)
try {
player.seekTo((((startValue * timeSeconds) / 100) * 1000).toLong())
player.play()
}catch (ignored: Exception) {}
return true
}

@ -36,6 +36,7 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.databinding.ExtraCommandsBottomSheetBinding
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
@ -348,37 +349,14 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE
extraCommands.setOnClickListener {
val bottomSheet = BottomSheetDialog(requireContext())
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.extra_commands_bottom_sheet)
val text = bottomSheet.findViewById<EditText>(R.id.command)
val currentCommand = infoUtil.buildYoutubeDLRequest(downloadItem).buildCommand().joinToString(" ")
bottomSheet.findViewById<TextView>(R.id.currentText)?.text = currentCommand
text?.setText(downloadItem.extraCommands)
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
text!!.postDelayed({
text.setSelection(text.length())
text.requestFocus()
imm.showSoftInput(text, 0)
}, 300)
val ok = bottomSheet.findViewById<Button>(R.id.okButton)
ok?.setOnClickListener {
downloadItem.extraCommands = text.text.toString()
bottomSheet.dismiss()
val callback = object : ExtraCommandsListener {
override fun onChangeExtraCommand(c: String) {
downloadItem.extraCommands = c
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
}
}else{
extraCommands.visibility = View.GONE

@ -172,7 +172,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
val editSelected : Chip = view.findViewById(R.id.editSelected)
editSelected.setOnClickListener {
var current = templates.find { it.title == autoCompleteTextView.text.toString() }
if (current == null) current = CommandTemplate(0, "", chosenCommandView.editText!!.text.toString())
if (current == null) current = CommandTemplate(0, "", chosenCommandView.editText!!.text.toString(), false)
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
templates.add(it)
chosenCommandView.editText!!.setText(it.content)

@ -76,7 +76,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -145,10 +145,10 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
dd.setTitle(getString(R.string.save_for_later))
dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch(Dispatchers.IO){
items.forEach {
it.status = DownloadRepository.Status.Saved.toString()
downloadViewModel.updateDownload(it)
lifecycleScope.launch{
items.map { it.status = DownloadRepository.Status.Saved.toString() }
withContext(Dispatchers.IO){
downloadViewModel.insertAll(items)
}
dismiss()
}

@ -408,37 +408,14 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE
extraCommands.setOnClickListener {
val bottomSheet = BottomSheetDialog(requireContext())
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.extra_commands_bottom_sheet)
val text = bottomSheet.findViewById<EditText>(R.id.command)
val currentCommand = infoUtil.buildYoutubeDLRequest(downloadItem).buildCommand().joinToString(" ")
bottomSheet.findViewById<TextView>(R.id.currentText)?.text = currentCommand
text?.setText(downloadItem.extraCommands)
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
text!!.postDelayed({
text.setSelection(text.length())
text.requestFocus()
imm.showSoftInput(text, 0)
}, 300)
val ok = bottomSheet.findViewById<Button>(R.id.okButton)
ok?.setOnClickListener {
downloadItem.extraCommands = text.text.toString()
bottomSheet.dismiss()
val callback = object : ExtraCommandsListener {
override fun onChangeExtraCommand(c: String) {
downloadItem.extraCommands = c
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
}
}else{
extraCommands.visibility = View.GONE

@ -1,15 +1,20 @@
package com.deniscerri.ytdlnis.ui.downloads
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.allViews
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -20,11 +25,15 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, OnClickListener {
@ -62,45 +71,47 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
)
activeRecyclerView = view.findViewById(R.id.download_recyclerview)
activeRecyclerView.forceFastScrollMode()
activeRecyclerView.adapter = activeDownloads
activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
pauseResume = view.findViewById(R.id.pause_resume)
pauseResume.setOnClickListener {
if (pauseResume.text == requireContext().getString(R.string.pause)){
list.forEach {
cancelItem(it.id.toInt())
it.status = DownloadRepository.Status.Paused.toString()
downloadViewModel.updateDownload(it)
val card = view.findViewWithTag<MaterialCardView>("${it.id}##card")
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_delete)
progressBar.isIndeterminate = false
pauseButton.icon = ContextCompat.getDrawable(requireContext(), R.drawable.exomedia_ic_play_arrow_white)
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
cancelButton.visibility = View.VISIBLE
lifecycleScope.launch {
runBlocking {
val queued = withContext(Dispatchers.IO){
downloadViewModel.getQueued()
}
queued.forEach {
WorkManager.getInstance(requireContext()).cancelUniqueWork(it.id.toString())
}
}
list.forEach {
cancelItem(it.id.toInt())
it.status = DownloadRepository.Status.Paused.toString()
downloadViewModel.updateDownload(it)
}
activeDownloads.notifyDataSetChanged()
}
}else{
val toQueue = list.filter { it.status == DownloadRepository.Status.Paused.toString() }
toQueue.forEach {
it.status = DownloadRepository.Status.Active.toString()
downloadViewModel.updateDownload(it)
val card = view.findViewWithTag<MaterialCardView>("${it.id}##card")
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_delete)
pauseButton.icon = ContextCompat.getDrawable(requireContext(), R.drawable.exomedia_ic_pause_white)
progressBar.isIndeterminate = true
cancelButton.visibility = View.GONE
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause
}
runBlocking {
downloadViewModel.queueDownloads(toQueue)
lifecycleScope.launch {
val toQueue = list.filter { it.status == DownloadRepository.Status.Paused.toString() }.toMutableList()
toQueue.forEach {
it.status = DownloadRepository.Status.Active.toString()
downloadViewModel.updateDownload(it)
}
val queuedItems = withContext(Dispatchers.IO){
downloadViewModel.getQueued()
}
toQueue.addAll(queuedItems)
runBlocking {
downloadViewModel.queueDownloads(toQueue)
}
}
}
}
@ -109,7 +120,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
list = it
activeDownloads.submitList(it)
if (list.isNotEmpty()){
if (it.size > 1){
pauseResume.visibility = View.VISIBLE
if (list.all { l -> l.status == DownloadRepository.Status.Paused.toString() }){
pauseResume.text = requireContext().getString(R.string.resume)
@ -117,6 +128,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
pauseResume.text = requireContext().getString(R.string.pause)
}
}else{
activeDownloads.notifyDataSetChanged()
pauseResume.visibility = View.GONE
}
@ -131,7 +143,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
val progress = work.progress.getInt("progress", 0)
val output = work.progress.getString("output")
val log = work.progress.getBoolean("log", false)
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
val outputText = view.findViewWithTag<TextView>("$id##output")

@ -1,14 +1,17 @@
package com.deniscerri.ytdlnis.ui.downloads
import android.annotation.SuppressLint
import android.app.Activity
import android.content.DialogInterface
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.Window
@ -32,6 +35,7 @@ import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
@ -75,6 +79,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
)
cancelledRecyclerView = view.findViewById(R.id.download_recyclerview)
cancelledRecyclerView.forceFastScrollMode()
cancelledRecyclerView.adapter = cancelledDownloads
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){

@ -115,7 +115,7 @@ class DownloadQueueMainFragment : Fragment(){
when(m.itemId){
R.id.clear_queue -> {
showDeleteDialog {
downloadViewModel.cancelQueued()
cancelAllDownloads()
}
}
R.id.clear_cancelled -> {

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.ui.downloads
import android.annotation.SuppressLint
import android.app.Activity
import android.content.DialogInterface
import android.graphics.Canvas
@ -9,6 +10,7 @@ import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.Window
@ -33,6 +35,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
@ -75,6 +78,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
)
erroredRecyclerView = view.findViewById(R.id.download_recyclerview)
erroredRecyclerView.forceFastScrollMode()
erroredRecyclerView.adapter = erroredDownloads
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){

@ -8,13 +8,16 @@ import android.graphics.Color
import android.os.Bundle
import android.text.format.DateFormat
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
@ -37,6 +40,7 @@ import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
@ -91,6 +95,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
)
queuedRecyclerView = view.findViewById(R.id.download_recyclerview)
queuedRecyclerView.forceFastScrollMode()
queuedRecyclerView.adapter = queuedDownloads
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
@ -427,5 +432,4 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
)
}
}
}

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.ui.downloads
import android.annotation.SuppressLint
import android.app.Activity
import android.content.DialogInterface
import android.graphics.Canvas
@ -9,6 +10,7 @@ import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.Window
@ -31,6 +33,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
@ -74,6 +77,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
)
savedRecyclerView = view.findViewById(R.id.download_recyclerview)
savedRecyclerView.forceFastScrollMode()
savedRecyclerView.adapter = savedDownloads
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){

@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.ui.more
import android.content.Context.INPUT_METHOD_SERVICE
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle
@ -14,6 +15,7 @@ import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.RelativeLayout
import androidx.appcompat.app.AlertDialog
import androidx.core.view.children
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -30,6 +32,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputLayout
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
@ -42,22 +45,26 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
private lateinit var recyclerView: RecyclerView
private lateinit var listAdapter: CookieAdapter
private lateinit var topAppBar: MaterialToolbar
private lateinit var useCookies : MaterialSwitch
private lateinit var cookiesViewModel: CookieViewModel
private lateinit var cookiesList: List<CookieItem>
private lateinit var noResults : RelativeLayout
private lateinit var mainActivity: MainActivity
private lateinit var preferences: SharedPreferences
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mainActivity = activity as MainActivity
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return inflater.inflate(R.layout.fragment_cookies, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
topAppBar = view.findViewById(R.id.logs_toolbar)
useCookies = view.findViewById(R.id.use_cookies)
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
cookiesList = listOf()
noResults = view.findViewById(R.id.no_results)
@ -67,6 +74,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
this,
mainActivity
)
val newCookie = view.findViewById<Chip>(R.id.newCookie)
recyclerView = view.findViewById(R.id.template_recyclerview)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = listAdapter
@ -85,6 +93,16 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
}
initMenu()
initChips()
useCookies.setOnCheckedChangeListener { compoundButton, b ->
newCookie.isEnabled = useCookies.isChecked
preferences.edit().putBoolean("use_cookies", useCookies.isChecked).apply()
}
val useCookiesPref = preferences.getBoolean("use_cookies", false)
useCookies.isChecked = useCookiesPref
newCookie.isEnabled = useCookiesPref
}
private fun initMenu() {

@ -10,12 +10,24 @@ import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.system.exitProcess
class MoreFragment : Fragment() {
@ -29,12 +41,14 @@ class MoreFragment : Fragment() {
private lateinit var terminateApp: TextView
private lateinit var settings: TextView
private lateinit var mainActivity: MainActivity
private lateinit var downloadViewModel: DownloadViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mainActivity = activity as MainActivity
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return inflater.inflate(R.layout.fragment_more, container, false)
}
@ -97,14 +111,27 @@ class MoreFragment : Fragment() {
}
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
terminateDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
if (doNotShowAgain){
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false)
mainSharedPreferencesEditor.commit()
terminateDialog.setPositiveButton(getString(R.string.ok)) { diag: DialogInterface?, _: Int ->
runBlocking {
val job : Job = lifecycleScope.launch(Dispatchers.IO) {
val activeDownloads = downloadViewModel.getActiveDownloads().toMutableList()
activeDownloads.map { it.status = DownloadRepository.Status.Queued.toString() }
activeDownloads.forEach {
downloadViewModel.updateDownload(it)
}
}
runBlocking {
job.join()
if (doNotShowAgain){
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false)
mainSharedPreferencesEditor.commit()
}
mainActivity.finishAndRemoveTask()
mainActivity.finishAffinity()
exitProcess(0)
}
}
mainActivity.finishAndRemoveTask()
mainActivity.finishAffinity()
exitProcess(0)
}
terminateDialog.show()
}else{

@ -129,8 +129,12 @@ class TerminalActivity : BaseActivity() {
}
}
R.id.shortcuts -> {
if (shortcutCount > 0){
showShortcuts()
lifecycleScope.launch {
if (shortcutCount > 0){
UiUtil.showShortcuts(this@TerminalActivity, commandTemplateViewModel){sh ->
input!!.text.insert(input!!.selectionStart, "$sh ")
}
}
}
}
R.id.folder -> {
@ -295,35 +299,6 @@ class TerminalActivity : BaseActivity() {
topAppBar!!.menu.forEach { it.isEnabled = false }
}
private fun showShortcuts() {
lifecycleScope.launch {
val bottomSheet = BottomSheetDialog(this@TerminalActivity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.template_shortcuts_list)
val chipGroup = bottomSheet.findViewById<ChipGroup>(R.id.shortcutsChipGroup)
val shortcutList = withContext(Dispatchers.IO){
commandTemplateViewModel.getAllShortcuts()
}
chipGroup!!.removeAllViews()
shortcutList.forEach {shortcut ->
val chip = layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
chip.text = shortcut.content
chip.setOnClickListener {
input!!.text.insert(input!!.selectionStart, shortcut.content + " ")
}
chipGroup.addView(chip)
}
bottomSheet.show()
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
}
private var commandPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->

@ -550,8 +550,6 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
}
finalMessage.append("${getString(R.string.restart)}?")
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.restore))
builder.setMessage(finalMessage)

@ -8,7 +8,6 @@ import android.util.Log
import android.widget.Toast
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.Converters
import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
@ -134,15 +133,17 @@ class InfoUtil(private val context: Context) {
}
@Throws(JSONException::class)
fun getVideo(url: String): List<ResultItem?> {
fun getYoutubeVideo(url: String): List<ResultItem?> {
return try {
val id = getIDFromYoutubeURL(url)
val res = genericRequest("$pipedURL/streams/$id")
if (res.length() == 0) getFromYTDL(url) else listOf(createVideoFromPipedJSON(
res, id
res, url
))
}catch (e: Exception){
getFromYTDL(url)
val v = getFromYTDL(url)
v.forEach { it!!.url = url }
v
}
}
@ -172,9 +173,11 @@ class InfoUtil(private val context: Context) {
}
return video
}
private fun createVideoFromPipedJSON(obj: JSONObject, id: String): ResultItem? {
private fun createVideoFromPipedJSON(obj: JSONObject, url: String): ResultItem? {
var video: ResultItem? = null
try {
val id = getIDFromYoutubeURL(url)
val title = Html.fromHtml(obj.getString("title").toString()).toString()
val author = try {
Html.fromHtml(obj.getString("uploader").toString()).toString()
@ -184,7 +187,6 @@ class InfoUtil(private val context: Context) {
val duration = formatIntegerDuration(obj.getInt("duration"), Locale.US)
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
val url = "https://www.youtube.com/watch?v=$id"
val formats : ArrayList<Format> = ArrayList()
if(sharedPreferences.getString("formats_source", "yt-dlp") == "piped"){
@ -452,9 +454,12 @@ class InfoUtil(private val context: Context) {
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
val cookiesFile = File(context.cacheDir, "cookies.txt")
if (cookiesFile.exists()) {
request.addOption("--cookies", cookiesFile.absolutePath)
if (sharedPreferences.getBoolean("use_cookies", false)){
val cookiesFile = File(context.cacheDir, "cookies.txt")
if (cookiesFile.exists()) {
request.addOption("--cookies", cookiesFile.absolutePath)
}
}
val proxy = sharedPreferences.getString("proxy", "")
@ -626,7 +631,16 @@ class InfoUtil(private val context: Context) {
}
var urls = "";
if(jsonObject.has("urls")) urls = jsonObject.getString("urls");
if(jsonObject.has("requested_formats")) {
val requestedFormats = jsonObject.getJSONArray("requested_formats")
val urlList = mutableListOf<String>()
val length = requestedFormats.length()-1
for (i in length downTo 0) {
urlList.add(requestedFormats.getJSONObject(i).getString("url"))
}
urls = urlList.joinToString("\n")
}
items.add(ResultItem(0,
url,
@ -927,7 +941,7 @@ class InfoUtil(private val context: Context) {
if(downloadItem.type != DownloadViewModel.Type.command){
if (downloadItem.SaveThumb) {
request.addOption("--write-thumbnail")
request.addOption("--convert-thumbnails", "png")
request.addOption("--convert-thumbnails", "jpg")
}
if (!sharedPreferences.getBoolean("mtime", false)){
request.addOption("--no-mtime")
@ -991,6 +1005,10 @@ class InfoUtil(private val context: Context) {
conf.absolutePath
)
}
if (sharedPreferences.getBoolean("write_description", false)){
request.addOption("--write-description")
}
}
if (sharedPreferences.getBoolean("restrict_filenames", true)) {
@ -1041,7 +1059,7 @@ class InfoUtil(private val context: Context) {
if (downloadItem.audioPreferences.embedThumb) {
request.addOption("--embed-thumbnail")
request.addOption("--convert-thumbnails", "jpg")
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
try {
val config = File(context.cacheDir.absolutePath + "/downloads/${downloadItem.id}/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
@ -1132,6 +1150,7 @@ class InfoUtil(private val context: Context) {
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters")
request.addOption("-o", "chapter:%(section_title)s.%(ext)s")
}else{
if (downloadItem.customFileNameTemplate.isNotBlank()){
request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s")

@ -13,6 +13,7 @@ import android.text.TextWatcher
import android.util.DisplayMetrics
import android.util.Log
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.Window
@ -31,6 +32,8 @@ import androidx.core.content.FileProvider
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.Format
@ -45,10 +48,12 @@ import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Calendar
@ -100,6 +105,7 @@ object UiUtil {
val ok : Button = bottomSheet.findViewById(R.id.template_create)!!
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
val content : TextInputLayout = bottomSheet.findViewById(R.id.content)!!
val extraCommandsSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extra_command_switch)!!
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
@ -148,6 +154,8 @@ object UiUtil {
}
}
extraCommandsSwitch.isChecked = item!!.useAsExtraCommand
commandTemplateViewModel.shortcuts.observe(lifeCycle){
shortcutsChipGroup.removeAllViews()
it.forEach {shortcut ->
@ -166,7 +174,7 @@ object UiUtil {
ok.setOnClickListener {
if (item == null){
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString())
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked)
commandTemplateViewModel.insert(t)
newTemplate(t)
}else{
@ -507,4 +515,48 @@ object UiUtil {
ViewGroup.LayoutParams.MATCH_PARENT
)
}
suspend fun showShortcuts(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: String) -> Unit){
val bottomSheet = BottomSheetDialog(activity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.template_shortcuts_list)
val chipGroup = bottomSheet.findViewById<ChipGroup>(R.id.shortcutsChipGroup)
val shortcutList = withContext(Dispatchers.IO){
commandTemplateViewModel.getAllShortcuts()
}
chipGroup!!.removeAllViews()
shortcutList.forEach {shortcut ->
val chip = activity.layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
chip.text = shortcut.content
chip.setOnClickListener {
itemSelected(shortcut.content)
}
chipGroup.addView(chip)
}
bottomSheet.show()
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
@SuppressLint("ClickableViewAccessibility")
fun RecyclerView.forceFastScrollMode()
{
overScrollMode = View.OVER_SCROLL_ALWAYS
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
isVerticalScrollBarEnabled = true
setOnTouchListener { view, event ->
if (event.x >= this.width - 30) {
view.parent.requestDisallowInterceptTouchEvent(true)
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_UP -> view.parent.requestDisallowInterceptTouchEvent(false)
}
}
false
}
}
}

@ -39,8 +39,7 @@ class DownloadWorker(
) : Worker(context, workerParams) {
override fun doWork(): Result {
itemId = inputData.getLong("id", 0)
if (itemId == 0L) return Result.failure()
if (itemId == 0L || isStopped) return Result.failure()
val notificationUtil = NotificationUtil(context)
val infoUtil = InfoUtil(context)
@ -74,6 +73,7 @@ class DownloadWorker(
Log.e(TAG, downloadItem.toString())
runBlocking{
YoutubeDL.getInstance().destroyProcessById(itemId.toString())
repository.setDownloadStatus(downloadItem, DownloadRepository.Status.Active)
}
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
@ -118,7 +118,7 @@ class DownloadWorker(
}
runCatching {
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id, "log" to logDownloads))
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
val title: String = downloadItem.title
notificationUtil.updateDownloadNotification(
downloadItem.id.toInt(),
@ -137,16 +137,16 @@ class DownloadWorker(
runBlocking {
var finalPaths : List<String>?
//move file from internal to set download directory
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
try {
finalPaths = withContext(Dispatchers.IO){
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
}
}
if (finalPaths.isNotEmpty()){
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
}else{
finalPaths = listOf(context.getString(R.string.unfound_file))
}
@ -177,7 +177,7 @@ class DownloadWorker(
if (wasQuickDownloaded){
runCatching {
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false))
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id))
runBlocking {
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
if (res != null) {
@ -242,7 +242,8 @@ class DownloadWorker(
var wasQuickDownloaded = false
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
runCatching {
setProgressAsync(workDataOf("progress" to 0, "output" to context.getString(R.string.updating_download_data), "id" to downloadItem.id, "log" to false))
if (isStopped) destroyYTDLProcess()
setProgressAsync(workDataOf("progress" to 0, "output" to context.getString(R.string.updating_download_data), "id" to downloadItem.id))
val info = infoUtil.getMissingInfo(downloadItem.url)
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString()
@ -259,10 +260,14 @@ class DownloadWorker(
}
override fun onStopped() {
YoutubeDL.getInstance().destroyProcessById(itemId.toInt().toString())
destroyYTDLProcess()
super.onStopped()
}
private fun destroyYTDLProcess(){
YoutubeDL.getInstance().destroyProcessById(itemId.toInt().toString())
}
companion object {
var itemId: Long = 0
const val TAG = "DownloadWorker"

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z"/>
</vector>

@ -1,18 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="65%"
android:pivotY="20%"
android:toDegrees="250"
android:fromDegrees="270">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<shape android:shape="rectangle">
<solid android:color="?android:colorAccent"/>
<size
android:width="40dp"
android:height="20dp"/>
<corners
android:topLeftRadius="20dp"
android:topRightRadius="20dp"/>
</shape>
<corners
android:radius="50dp" />
</rotate>
<padding
android:paddingLeft="22dp"
android:paddingRight="22dp" />
<solid android:color="?android:colorAccent" />
</shape>

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/command_template_item_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -31,24 +32,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<!-- <View-->
<!-- android:id="@+id/divider"-->
<!-- style="@style/Divider.Vertical"-->
<!-- android:layout_height="0dp"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toStartOf="@+id/check"-->
<!-- app:layout_constraintStart_toEndOf="@+id/title"-->
<!-- app:layout_constraintTop_toTopOf="parent" />-->
<!-- <RadioButton-->
<!-- android:id="@+id/check"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:gravity="center"-->
<!-- android:minWidth="0dp"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintVertical_bias="0.558" />-->
<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>

@ -102,6 +102,18 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/extra_command_switch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checked="false"
android:layout_marginHorizontal="5dp"
android:textSize="15sp"
android:text="@string/extra_command"
app:layout_constraintEnd_toEndOf="parent" />
<View
style="@style/Divider.Horizontal"
android:layout_marginVertical="10dp" />

@ -129,7 +129,7 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="80dp"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:hint="@string/start">
@ -154,7 +154,7 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/to_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="80dp"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:hint="@string/end"
app:layout_constraintBottom_toBottomOf="parent"

@ -1,90 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical">
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/use_extra_commands"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/use_extra_commands"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/current"
</androidx.constraintlayout.widget.ConstraintLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_marginVertical="10dp"
app:layout_constraintTop_toBottomOf="@+id/title"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/linear"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/currentTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/current"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/currentText"
android:layout_width="wrap_content"
<com.google.android.material.card.MaterialCardView
android:id="@+id/current"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_marginHorizontal="20dp"
android:layout_marginVertical="5dp"
app:layout_constraintTop_toBottomOf="@+id/title">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/currentTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/current"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/currentText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</ScrollView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_marginVertical="10dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="5dp"
app:layout_constraintTop_toBottomOf="@+id/current">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/command"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:fontFamily="monospace"
android:inputType="textMultiLine"
android:maxLines="2000" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/okButton"
android:layout_marginVertical="10dp"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ok"
app:icon="@drawable/ic_check"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<Button
android:id="@+id/shortcuts"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
app:icon="@drawable/ic_shortcut"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/okButton"
app:layout_constraintStart_toEndOf="@+id/commands"
app:layout_constraintTop_toBottomOf="@+id/linearLayout2" />
<Button
android:id="@+id/commands"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_marginStart="20dp"
android:layout_height="wrap_content"
app:icon="@drawable/ic_terminal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout2" />
<Button
android:id="@+id/okButton"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:layout_margin="20dp"
android:text="@string/ok"
app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout2" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,25 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="100dp"
android:orientation="vertical"
android:layout_marginBottom="100dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.button.MaterialButton
android:id="@+id/pause_resume"
android:visibility="gone"
android:visibility="visible"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="30dp"
android:layout_gravity="end"
android:paddingVertical="10dp"
android:text="@string/resume"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_recyclerview"
@ -27,6 +25,7 @@
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0.0"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
@ -38,4 +37,4 @@
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

@ -31,13 +31,25 @@
android:orientation="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/use_cookies"
android:layout_width="match_parent"
android:background="?attr/colorSurfaceContainer"
android:paddingHorizontal="20dp"
android:layout_height="70dp"
android:checked="false"
android:text="@string/use_cookies"/>
<com.google.android.material.chip.Chip
android:id="@+id/newCookie"
android:layout_margin="10dp"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/use_cookies"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:text="@string/new_cookie"
app:chipIcon="@drawable/ic_plus" />

@ -3,7 +3,7 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="200dp"
android:scrollbars="none"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"

@ -111,7 +111,7 @@
android:id="@+id/recyclerviewhistorys"
android:orientation="vertical"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:scrollbars="none"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"

@ -120,33 +120,6 @@
</com.google.android.material.search.SearchView>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/home_appbarlayout"
app:liftOnScroll="true"
android:background="@null"
android:elevation="0dp"
app:elevation="0dp"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/home_toolbar"
android:layout_width="match_parent"
app:title="@string/app_name"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_height="wrap_content" />
<com.google.android.material.search.SearchBar
android:id="@+id/search_bar"
app:menu="@menu/main_menu"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/search_hint" />
</com.google.android.material.appbar.AppBarLayout>
<RelativeLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
@ -200,6 +173,30 @@
</RelativeLayout>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/home_appbarlayout"
android:background="@null"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/home_toolbar"
android:layout_width="match_parent"
app:title="@string/app_name"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_height="wrap_content" />
<com.google.android.material.search.SearchBar
android:id="@+id/search_bar"
app:menu="@menu/main_menu"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/search_hint" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/home_fabs"
android:layout_width="match_parent"

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="100dp"
android:orientation="vertical"
android:layout_marginBottom="100dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
@ -24,6 +25,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:fastScrollEnabled="true"
android:scrollbars="none"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
@ -36,4 +38,4 @@
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

@ -80,7 +80,6 @@
android:layout_marginStart="20dp"
android:layout_marginEnd="10dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
@ -113,8 +112,7 @@
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginVertical="10dp"
android:layout_marginEnd="5dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toTopOf="parent" />
@ -124,7 +122,7 @@
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_margin="10dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

@ -5,8 +5,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/BaseTheme">
<item android:title="@string/command_templates"
android:icon="@drawable/ic_terminal"
app:showAsAction="ifRoom"
android:id="@+id/command_templates"/>
<item android:title="@string/shortcuts"
android:icon="@drawable/ic_shortcut"
app:showAsAction="ifRoom"
android:id="@+id/shortcuts"/>
</menu>

@ -272,10 +272,8 @@
<string name="custom_audio_quality">استخدام جودة صوت مخصصة</string>
<string name="piped_instance">مثيل Piped</string>
<string name="piped_instance_summary">إدخال خادم PIPED API، يمكن للتطبيق استخدامه لاستعلامات وتنسيقات يوتيوب</string>
<string name="subtitle_format">صيغة الترجمة
\n</string>
<string name="subtitle_format">صيغة الترجمة</string>
<string name="hide_thumbnails">إخفاء الصور المصغرة من الشاشة المحددة</string>
<string name="hide_terminal">إخفاء أيقونة سطر الأوامر من قائمة \"مشاركة\"</string>
<string name="saved">المحفوظة</string>
<string name="clear_saved">تنظيف المحفوظة</string>
<string name="save_for_later">حفظ، للتحميل فيما بعد؟</string>

@ -275,10 +275,8 @@
<string name="retries">Wiederholungen</string>
<string name="fragment_retries">Fragmentwiederholungen</string>
<string name="format_order">Formatreihenfolge</string>
<string name="subtitle_format">Untertitelformat
\n</string>
<string name="subtitle_format">Untertitelformat</string>
<string name="hide_thumbnails">Vorschaubilder verstecken</string>
<string name="hide_terminal">Terminal im Freigabemenü ausblenden</string>
<string name="saved">Gespeichert</string>
<string name="save_for_later">Download für später merken\?</string>
<string name="clear_saved">Gespeichertes leeren</string>

@ -281,10 +281,8 @@
<string name="use_extra_commands_summary">Do pobierania audio/wideo. Dodaj dodatkowe polecenia wraz z konfiguracją GUI</string>
<string name="current">Bieżące polecenie</string>
<string name="hide_thumbnails">Ukryj miniatury</string>
<string name="hide_terminal">Ukryj terminal w menu udostępniania</string>
<string name="saved">Zapisane</string>
<string name="clear_saved">Wyczyść zapisane</string>
<string name="save_for_later">Zachować pobieranie na później\?</string>
<string name="subtitle_format">Format napisów
\n</string>
<string name="subtitle_format">Format napisów</string>
</resources>

@ -279,7 +279,6 @@
<string name="force_keyframes_summary">Processo mais lento, mas cortes mais precisos</string>
<string name="subtitle_format">Formato da legenda</string>
<string name="hide_thumbnails">Ocultar miniaturas</string>
<string name="hide_terminal">Ocultar Terminal do menu de compartilhamento</string>
<string name="saved">Salvou</string>
<string name="clear_saved">Remoção Concluída</string>
<string name="save_for_later">Salvar download para mais tarde\?</string>

@ -281,10 +281,8 @@
<string name="saved">Guardado</string>
<string name="clear_saved">Limpar guardado</string>
<string name="use_extra_commands_summary">Para transferências de áudio/vídeo. Adicionar comandos extra juntamente com a configuração GUI</string>
<string name="subtitle_format">Formato da legenda
\n</string>
<string name="subtitle_format">Formato da legenda</string>
<string name="hide_thumbnails">Esconder miniaturas</string>
<string name="hide_terminal">Ocultar o Terminal do menu de partilha</string>
<string name="force_keyframes_summary">Processo mais lento, mas cortes mais precisos</string>
<string name="save_for_later">Guardar a transferência para mais tarde\?</string>
<string name="force_keyframes">Forçar quadros-chave nos cortes</string>

@ -282,7 +282,6 @@
<string name="current">Текущая команда</string>
<string name="subtitle_format">Формат субтитров</string>
<string name="hide_thumbnails">Скрыть миниатюры</string>
<string name="hide_terminal">Скрыть терминал из меню «Поделиться»</string>
<string name="saved">Сохранено</string>
<string name="clear_saved">Очистить сохраненное</string>
<string name="save_for_later">Сохранить загрузку на потом\?</string>

@ -98,10 +98,8 @@
<string name="save_for_later">บันทึกการดาวน์โหลดสำหรับภายหลัง\?</string>
<string name="clear_saved">ล้างข้อมูลที่บันทึกไว้</string>
<string name="saved">บันทึกแล้ว</string>
<string name="hide_terminal">ซ่อน Terminal จากเมนูแชร์</string>
<string name="hide_thumbnails">ซ่อนภาพขนาดย่อ</string>
<string name="subtitle_format">รูปแบบคำบรรยาย
\n</string>
<string name="subtitle_format">รูปแบบคำบรรยาย</string>
<string name="current">คำสั่งปัจจุบัน</string>
<string name="use_extra_commands">เพิ่มคำสั่งพิเศษ</string>
<string name="force_keyframes_summary">กระบวนการช้าลง แต่มีการตัดที่แม่นยำยิ่งขึ้น</string>

@ -286,10 +286,8 @@
<string name="use_extra_commands_summary">Ses / Video indirmeleri için. GUI yapılandırması ile birlikte ekstra komutlar ekleyin</string>
<string name="current">Geçerli Komut</string>
<string name="save_for_later">Daha Sonra İndirmek için Kaydedilsin mi\?</string>
<string name="subtitle_format">Altyazı Biçimi
\n</string>
<string name="subtitle_format">Altyazı Biçimi</string>
<string name="saved">Kaydedildi</string>
<string name="clear_saved">Kaydedilenleri Temizle</string>
<string name="hide_terminal">Terminal\'i paylaş menüsünden gizle</string>
<string name="hide_thumbnails">Küçük Resimleri Gizle</string>
</resources>

@ -281,11 +281,9 @@
<string name="current">Поточна команда</string>
<string name="use_extra_commands_summary">Додайте свої додаткові команди перед завантаженням аудіо/відео</string>
<string name="hide_thumbnails">Приховати мініатюри</string>
<string name="hide_terminal">Приховати термінал у меню спільного доступу</string>
<string name="saved">Збережено</string>
<string name="clear_saved">Очистити збережене</string>
<string name="subtitle_format">Формат субтитрів
\n</string>
<string name="subtitle_format">Формат субтитрів</string>
<string name="save_for_later">Зберегти завантаження на потім\?</string>
<string name="orange">Помаранчевий</string>
</resources>

@ -278,10 +278,8 @@
<string name="use_extra_commands">新增額外指令</string>
<string name="use_extra_commands_summary">為了音訊 / 影片下載,請在圖形使用者介面配置中新增額外的指令</string>
<string name="hide_thumbnails">隱藏縮略圖</string>
<string name="subtitle_format">字幕格式
\n</string>
<string name="subtitle_format">字幕格式</string>
<string name="force_keyframes_summary">較為緩慢的處理過程,但能得到更準確的剪輯結果</string>
<string name="hide_terminal">隱藏分享選單中的終端機</string>
<string name="saved">已儲存</string>
<string name="clear_saved">清除已儲存的內容</string>
<string name="save_for_later">儲存下載以供日後使用?</string>

@ -294,4 +294,8 @@
<string name="saved">Saved</string>
<string name="clear_saved">Clear Saved</string>
<string name="save_for_later">Save Download for Later?</string>
<string name="write_description">Save Description</string>
<string name="write_description_summary">Write description file</string>
<string name="extra_command">Extra Command</string>
<string name="use_cookies">Use Cookies</string>
</resources>

@ -53,6 +53,14 @@
app:useSimpleSummaryProvider="true"
app:title="@string/subtitle_format" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/baseline_description_24"
app:key="write_description"
android:summary="@string/write_description_summary"
app:title="@string/write_description" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"

@ -21,7 +21,7 @@ buildscript {
coroutineVer = "1.6.4"
retrofitVer = "2.9.0"
kodeinVer = "7.16.0"
navVer = "2.5.3"
navVer = "2.6.0"
media3_version = "1.1.0"
}
repositories {

Loading…
Cancel
Save