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
This commit is contained in:
deniscerri 2023-07-27 23:41:50 +02:00
parent bc3bcbf58b
commit c44929d5cb
No known key found for this signature in database
GPG key ID: 95C43D517D830350
60 changed files with 803 additions and 415 deletions

View file

@ -10,7 +10,7 @@ def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 6 def versionMinor = 6
def versionPatch = 3 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 = "" def versionExt = ""
if (versionBuild > 0){ if (versionBuild > 0){
@ -152,18 +152,13 @@ dependencies {
implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.preference:preference:1.2.0'
implementation "androidx.navigation:navigation-fragment-ktx:$navVer" implementation "androidx.navigation:navigation-fragment-ktx:$navVer"
implementation "androidx.navigation:navigation-ui-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.test.ext:junit-ktx:1.1.5'
implementation 'androidx.benchmark:benchmark-macro:1.1.1' implementation 'androidx.benchmark:benchmark-macro:1.1.1'
testImplementation "junit:junit:$junitVer" testImplementation "junit:junit:$junitVer"
androidTestImplementation "junit:junit:$junitVer" androidTestImplementation "junit:junit:$junitVer"
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" 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 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "com.devbrackets.android:exomedia:4.3.0" implementation "com.devbrackets.android:exomedia:4.3.0"

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 9, "version": 9,
"identityHash": "bcd20f64b54fbc6e470be0d15059a66e", "identityHash": "d29e09789b7c96b5551b80da253b7330",
"entities": [ "entities": [
{ {
"tableName": "results", "tableName": "results",
@ -180,7 +180,7 @@
}, },
{ {
"tableName": "downloads", "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": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -324,12 +324,6 @@
"columnName": "logID", "columnName": "logID",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": false "notNull": false
},
{
"fieldPath": "workID",
"columnName": "workID",
"affinity": "BLOB",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -343,7 +337,7 @@
}, },
{ {
"tableName": "commandTemplates", "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": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -362,6 +356,13 @@
"columnName": "content", "columnName": "content",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": true "notNull": true
},
{
"fieldPath": "useAsExtraCommand",
"columnName": "useAsExtraCommand",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
} }
], ],
"primaryKey": { "primaryKey": {
@ -511,7 +512,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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')"
] ]
} }
} }

View file

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis package com.deniscerri.ytdlnis
import android.app.Application import android.app.Application
import android.os.Looper
import android.widget.Toast import android.widget.Toast
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -66,7 +67,9 @@ class App : Application() {
) )
}catch (e: Exception){ }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() e.printStackTrace()
} }
} }

View file

@ -41,6 +41,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.navOptions import androidx.navigation.navOptions
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel 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.navigation.NavigationView
import com.google.android.material.navigationrail.NavigationRailView import com.google.android.material.navigationrail.NavigationRailView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.io.Reader import java.io.Reader
@ -116,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)
@ -172,11 +176,24 @@ class MainActivity : BaseActivity() {
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
terminateDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> terminateDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
if (doNotShowAgain){ runBlocking {
sharedPreferences.edit().putBoolean("ask_terminate_app", false).apply() 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() terminateDialog.show()
}else{ }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) { private fun View.visibilityChanged(action: (View) -> Unit) {
this.viewTreeObserver.addOnGlobalLayoutListener { this.viewTreeObserver.addOnGlobalLayoutListener {

View file

@ -148,16 +148,10 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
pauseButton.setOnClickListener { pauseButton.setOnClickListener {
if (pauseButton.tag == ActiveDownloadAction.Pause){ if (pauseButton.tag == ActiveDownloadAction.Pause){
onItemClickListener.onPauseClick(item.id, 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{ }else{
onItemClickListener.onPauseClick(item.id, ActiveDownloadAction.Resume) 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
} }
} }

View file

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

View file

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

View file

@ -12,6 +12,8 @@ interface CommandTemplateDao {
@Query("SELECT * FROM commandTemplates ORDER BY id DESC") @Query("SELECT * FROM commandTemplates ORDER BY id DESC")
fun getAllTemplatesFlow() : Flow<List<CommandTemplate>> 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") @Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>> fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)
}

View file

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

View file

@ -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
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.databinding.ExtraCommandsBottomSheetBinding
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.InfoUtil 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)){ if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE extraCommands.visibility = View.VISIBLE
extraCommands.setOnClickListener { extraCommands.setOnClickListener {
val bottomSheet = BottomSheetDialog(requireContext()) val callback = object : ExtraCommandsListener {
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) override fun onChangeExtraCommand(c: String) {
bottomSheet.setContentView(R.layout.extra_commands_bottom_sheet) downloadItem.extraCommands = c
}
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()
} }
bottomSheet.show() val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
val displayMetrics = DisplayMetrics() bottomSheetDialog.show(parentFragmentManager, "extraCommands")
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
} }
}else{ }else{
extraCommands.visibility = View.GONE extraCommands.visibility = View.GONE

View file

@ -172,7 +172,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
val editSelected : Chip = view.findViewById(R.id.editSelected) val editSelected : Chip = view.findViewById(R.id.editSelected)
editSelected.setOnClickListener { editSelected.setOnClickListener {
var current = templates.find { it.title == autoCompleteTextView.text.toString() } 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) { UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
templates.add(it) templates.add(it)
chosenCommandView.editText!!.setText(it.content) chosenCommandView.editText!!.setText(it.content)

View file

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

View file

@ -408,37 +408,14 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
if (sharedPreferences.getBoolean("use_extra_commands", false)){ if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE extraCommands.visibility = View.VISIBLE
extraCommands.setOnClickListener { extraCommands.setOnClickListener {
val bottomSheet = BottomSheetDialog(requireContext()) val callback = object : ExtraCommandsListener {
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) override fun onChangeExtraCommand(c: String) {
bottomSheet.setContentView(R.layout.extra_commands_bottom_sheet) downloadItem.extraCommands = c
}
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()
} }
bottomSheet.show() val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
val displayMetrics = DisplayMetrics() bottomSheetDialog.show(parentFragmentManager, "extraCommands")
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
} }
}else{ }else{
extraCommands.visibility = View.GONE extraCommands.visibility = View.GONE

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,12 +10,24 @@ import android.view.ViewGroup
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R 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.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder 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 import kotlin.system.exitProcess
class MoreFragment : Fragment() { class MoreFragment : Fragment() {
@ -29,12 +41,14 @@ class MoreFragment : Fragment() {
private lateinit var terminateApp: TextView private lateinit var terminateApp: TextView
private lateinit var settings: TextView private lateinit var settings: TextView
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private lateinit var downloadViewModel: DownloadViewModel
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
mainActivity = activity as MainActivity mainActivity = activity as MainActivity
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
return inflater.inflate(R.layout.fragment_more, container, false) 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.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
terminateDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> terminateDialog.setPositiveButton(getString(R.string.ok)) { diag: DialogInterface?, _: Int ->
if (doNotShowAgain){ runBlocking {
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false) val job : Job = lifecycleScope.launch(Dispatchers.IO) {
mainSharedPreferencesEditor.commit() 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() terminateDialog.show()
}else{ }else{

View file

@ -129,8 +129,12 @@ class TerminalActivity : BaseActivity() {
} }
} }
R.id.shortcuts -> { R.id.shortcuts -> {
if (shortcutCount > 0){ lifecycleScope.launch {
showShortcuts() if (shortcutCount > 0){
UiUtil.showShortcuts(this@TerminalActivity, commandTemplateViewModel){sh ->
input!!.text.insert(input!!.selectionStart, "$sh ")
}
}
} }
} }
R.id.folder -> { R.id.folder -> {
@ -295,35 +299,6 @@ class TerminalActivity : BaseActivity() {
topAppBar!!.menu.forEach { it.isEnabled = false } 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( private var commandPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->

View file

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

View file

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

View file

@ -13,6 +13,7 @@ import android.text.TextWatcher
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.Gravity import android.view.Gravity
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
@ -31,6 +32,8 @@ import androidx.core.content.FileProvider
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
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.deniscerri.ytdlnis.database.models.Format 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.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.Calendar import java.util.Calendar
@ -100,6 +105,7 @@ object UiUtil {
val ok : Button = bottomSheet.findViewById(R.id.template_create)!! val ok : Button = bottomSheet.findViewById(R.id.template_create)!!
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!! val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
val content : TextInputLayout = bottomSheet.findViewById(R.id.content)!! 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 shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!! val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
@ -148,6 +154,8 @@ object UiUtil {
} }
} }
extraCommandsSwitch.isChecked = item!!.useAsExtraCommand
commandTemplateViewModel.shortcuts.observe(lifeCycle){ commandTemplateViewModel.shortcuts.observe(lifeCycle){
shortcutsChipGroup.removeAllViews() shortcutsChipGroup.removeAllViews()
it.forEach {shortcut -> it.forEach {shortcut ->
@ -166,7 +174,7 @@ object UiUtil {
ok.setOnClickListener { ok.setOnClickListener {
if (item == null){ 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) commandTemplateViewModel.insert(t)
newTemplate(t) newTemplate(t)
}else{ }else{
@ -507,4 +515,48 @@ object UiUtil {
ViewGroup.LayoutParams.MATCH_PARENT 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
}
}
} }

View file

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

View file

@ -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>

View file

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

View file

@ -1,6 +1,7 @@
<?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" <androidx.constraintlayout.widget.ConstraintLayout 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"
android:id="@+id/command_template_item_constraint" android:id="@+id/command_template_item_constraint"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -31,24 +32,26 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" /> app:layout_constraintTop_toBottomOf="@+id/title" />
<!-- <View--> <TextView
<!-- android:id="@+id/divider"--> android:id="@+id/useInExtraCommands"
<!-- style="@style/Divider.Vertical"--> style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
<!-- android:layout_height="0dp"--> android:layout_width="wrap_content"
<!-- app:layout_constraintBottom_toBottomOf="parent"--> android:layout_height="wrap_content"
<!-- app:layout_constraintEnd_toStartOf="@+id/check"--> android:background="@drawable/rounded_corner"
<!-- app:layout_constraintStart_toEndOf="@+id/title"--> android:backgroundTint="?attr/colorSecondary"
<!-- app:layout_constraintTop_toTopOf="parent" />--> android:clickable="false"
android:visibility="gone"
<!-- <RadioButton--> android:ellipsize="end"
<!-- android:id="@+id/check"--> android:text="@string/extra_command"
<!-- android:layout_width="wrap_content"--> android:gravity="center"
<!-- android:layout_height="wrap_content"--> android:maxLength="17"
<!-- android:gravity="center"--> android:maxLines="1"
<!-- android:minWidth="0dp"--> android:minWidth="30dp"
<!-- app:layout_constraintBottom_toBottomOf="parent"--> android:paddingHorizontal="5dp"
<!-- app:layout_constraintEnd_toEndOf="parent"--> android:textSize="12sp"
<!-- app:layout_constraintTop_toTopOf="parent"--> android:textStyle="bold"
<!-- app:layout_constraintVertical_bias="0.558" />--> app:cornerRadius="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/content" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -102,6 +102,18 @@
</com.google.android.material.textfield.TextInputLayout> </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 <View
style="@style/Divider.Horizontal" style="@style/Divider.Horizontal"
android:layout_marginVertical="10dp" /> android:layout_marginVertical="10dp" />

View file

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

View file

@ -1,90 +1,147 @@
<?xml version="1.0" encoding="utf-8"?> <?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_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_height="wrap_content"
android:layout_margin="20dp" android:orientation="vertical"
android:orientation="vertical"> app:layout_constraintTop_toTopOf="parent">
<TextView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_marginVertical="10dp" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/title" android:layout_marginHorizontal="20dp"
android:layout_height="wrap_content"> 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" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:scrollbars="none">
<LinearLayout <LinearLayout
android:id="@+id/linear"
android:layout_width="match_parent" android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<TextView <com.google.android.material.card.MaterialCardView
android:id="@+id/currentTitle" android:id="@+id/current"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/current" android:layout_marginHorizontal="20dp"
android:textSize="12sp" android:layout_marginVertical="5dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title">
app:layout_constraintTop_toTopOf="parent" />
<TextView <LinearLayout
android:id="@+id/currentText" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:orientation="vertical"
android:fontFamily="monospace" android:padding="10dp">
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent" <TextView
app:layout_constraintTop_toTopOf="parent" /> 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> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </ScrollView>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout" android:id="@+id/textInputLayout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox" style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_marginVertical="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="5dp"
app:layout_constraintTop_toBottomOf="@+id/current"> app:layout_constraintTop_toBottomOf="@+id/current">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/command" android:id="@+id/command"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:fontFamily="monospace" android:fontFamily="monospace"
android:inputType="textMultiLine"
android:maxLines="2000" /> android:maxLines="2000" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/okButton" </LinearLayout>
android:layout_marginVertical="10dp"
style="@style/Widget.Material3.Button.ElevatedButton" <Button
android:layout_width="wrap_content" android:id="@+id/shortcuts"
android:layout_height="wrap_content" style="@style/Widget.Material3.Button.IconButton.Filled"
android:text="@string/ok" android:layout_width="wrap_content"
app:icon="@drawable/ic_check" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toEndOf="parent" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" /> app:icon="@drawable/ic_shortcut"
</androidx.constraintlayout.widget.ConstraintLayout> 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>

View file

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

View file

@ -31,13 +31,25 @@
android:orientation="vertical" android:orientation="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> 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 <com.google.android.material.chip.Chip
android:id="@+id/newCookie" android:id="@+id/newCookie"
android:layout_margin="10dp"
style="@style/Widget.Material3.Chip.Assist" style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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" android:text="@string/new_cookie"
app:chipIcon="@drawable/ic_plus" /> app:chipIcon="@drawable/ic_plus" />

View file

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

View file

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

View file

@ -120,33 +120,6 @@
</com.google.android.material.search.SearchView> </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 <RelativeLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -200,6 +173,30 @@
</RelativeLayout> </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 <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/home_fabs" android:id="@+id/home_fabs"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?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_width="match_parent"
android:layout_height="wrap_content" 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: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">
@ -24,6 +25,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fastScrollEnabled="true" app:fastScrollEnabled="true"
android:scrollbars="none"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable" app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable" app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
@ -36,4 +38,4 @@
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>

View file

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

View file

@ -5,8 +5,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
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">

View file

@ -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>

View file

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

View file

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

View file

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

View file

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

View file

@ -281,10 +281,8 @@
<string name="saved">Guardado</string> <string name="saved">Guardado</string>
<string name="clear_saved">Limpar 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="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 <string name="subtitle_format">Formato da legenda</string>
\n</string>
<string name="hide_thumbnails">Esconder miniaturas</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="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="save_for_later">Guardar a transferência para mais tarde\?</string>
<string name="force_keyframes">Forçar quadros-chave nos cortes</string> <string name="force_keyframes">Forçar quadros-chave nos cortes</string>

View file

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

View file

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

View file

@ -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="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="current">Geçerli Komut</string>
<string name="save_for_later">Daha Sonra İndirmek için Kaydedilsin mi\?</string> <string name="save_for_later">Daha Sonra İndirmek için Kaydedilsin mi\?</string>
<string name="subtitle_format">Altyazı Biçimi <string name="subtitle_format">Altyazı Biçimi</string>
\n</string>
<string name="saved">Kaydedildi</string> <string name="saved">Kaydedildi</string>
<string name="clear_saved">Kaydedilenleri Temizle</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> <string name="hide_thumbnails">Küçük Resimleri Gizle</string>
</resources> </resources>

View file

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

View file

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

View file

@ -294,4 +294,8 @@
<string name="saved">Saved</string> <string name="saved">Saved</string>
<string name="clear_saved">Clear Saved</string> <string name="clear_saved">Clear Saved</string>
<string name="save_for_later">Save Download for Later?</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> </resources>

View file

@ -53,6 +53,14 @@
app:useSimpleSummaryProvider="true" app:useSimpleSummaryProvider="true"
app:title="@string/subtitle_format" /> 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 <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true" app:defaultValue="true"

View file

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