implemented cookies import & invert selected in contextual app bar & download preference without metered networks
This commit is contained in:
parent
f6e5f771f6
commit
55b59d75b1
38 changed files with 521 additions and 305 deletions
|
|
@ -13,6 +13,8 @@
|
|||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
|
|
|
|||
|
|
@ -101,11 +101,7 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
cookieViewModel.getCookiesFromDB().getOrNull()?.let{
|
||||
File(cacheDir, "cookies.txt").apply { writeText(it) }
|
||||
}
|
||||
|
||||
cookieViewModel.updateCookiesFile()
|
||||
val intent = intent
|
||||
handleIntents(intent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity
|
|||
title.text = item?.url
|
||||
|
||||
val content = card.findViewById<TextView>(R.id.content)
|
||||
content.visibility = View.GONE
|
||||
content.text = item?.content
|
||||
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onItemClick(item!!)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
|
@ -172,6 +173,16 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
|||
checkedItems.clear()
|
||||
}
|
||||
|
||||
fun invertSelected(items: List<HistoryItem?>?){
|
||||
val invertedList = mutableListOf<Long>()
|
||||
items?.forEach {
|
||||
if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
|
||||
}
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(invertedList)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIFF_CALLBACK: DiffUtil.ItemCallback<HistoryItem> = object : DiffUtil.ItemCallback<HistoryItem>() {
|
||||
override fun areItemsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
|
||||
|
|
|
|||
|
|
@ -173,6 +173,16 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun invertSelected(items: List<ResultItem?>?){
|
||||
val invertedList = mutableListOf<String>()
|
||||
items?.forEach {
|
||||
if (!checkedVideos.contains(it!!.url)) invertedList.add(it.url)
|
||||
}
|
||||
checkedVideos.clear()
|
||||
checkedVideos.addAll(invertedList)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun clearCheckedItems(){
|
||||
checkedVideos.clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
|
|||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: CommandTemplate, newItem: CommandTemplate): Boolean {
|
||||
return oldItem.content == newItem.content
|
||||
return oldItem.title == newItem.title && oldItem.content == newItem.content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ interface CookieDao {
|
|||
@Query("SELECT * FROM cookies ORDER BY id DESC")
|
||||
fun getAllCookiesLiveData() : LiveData<List<CookieItem>>
|
||||
|
||||
@Query("SELECT * FROM cookies WHERE url=:url")
|
||||
fun checkIfExistsWithSameURL(url: String) : CookieItem
|
||||
@Query("SELECT EXISTS(SELECT * FROM cookies WHERE url=:url LIMIT 1)")
|
||||
fun checkIfExistsWithSameURL(url: String) : Boolean
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insert(item: CookieItem)
|
||||
suspend fun insert(item: CookieItem) : Long
|
||||
|
||||
@Query("DELETE FROM cookies")
|
||||
suspend fun deleteAll()
|
||||
|
|
|
|||
|
|
@ -12,12 +12,11 @@ class CookieRepository(private val cookieDao: CookieDao) {
|
|||
}
|
||||
|
||||
|
||||
suspend fun insert(item: CookieItem){
|
||||
try{
|
||||
cookieDao.checkIfExistsWithSameURL(item.url)
|
||||
}catch(e: Exception){
|
||||
cookieDao.insert(item)
|
||||
suspend fun insert(item: CookieItem) : Long{
|
||||
if (! cookieDao.checkIfExistsWithSameURL(item.url)){
|
||||
return cookieDao.insert(item)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
suspend fun delete(item: CookieItem){
|
||||
|
|
|
|||
|
|
@ -6,26 +6,27 @@ import android.content.ClipboardManager
|
|||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteDatabase.OPEN_READONLY
|
||||
import android.util.Log
|
||||
import android.webkit.CookieManager
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
||||
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||
private val repository: CookieRepository
|
||||
val items: LiveData<List<CookieItem>>
|
||||
private val jsonFormat = Json { prettyPrint = true }
|
||||
|
||||
private val cookieHeader = "# Netscape HTTP Cookie File\n" +
|
||||
"# WebView Generated by the YTDLnis app\n"
|
||||
|
||||
init {
|
||||
val dao = DBManager.getInstance(application).cookieDao
|
||||
|
|
@ -37,12 +38,13 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
return repository.getAll()
|
||||
}
|
||||
|
||||
fun insert(item: CookieItem) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.insert(item)
|
||||
suspend fun insert(item: CookieItem) : Long {
|
||||
return repository.insert(item)
|
||||
}
|
||||
|
||||
fun delete(item: CookieItem) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.delete(item)
|
||||
updateCookiesFile()
|
||||
}
|
||||
|
||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||
|
|
@ -106,20 +108,53 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
close()
|
||||
}
|
||||
close()
|
||||
val header = "# Netscape HTTP Cookie File\n" +
|
||||
"# WebView Generated by the YTDLnis app\n"
|
||||
cookieList.fold(StringBuilder(header)) { acc, cookie ->
|
||||
cookieList.fold(StringBuilder("")) { acc, cookie ->
|
||||
acc.append(cookie.toNetscapeFormat()).append("\n")
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCookiesFile() = viewModelScope.launch(Dispatchers.IO) {
|
||||
val cookies = repository.getAll()
|
||||
val cookieTXT = StringBuilder(cookieHeader)
|
||||
val cookieFile = File(application.cacheDir, "cookies.txt")
|
||||
if (cookies.isEmpty()) cookieFile.apply { writeText("") }
|
||||
cookies.forEach {
|
||||
it.content.lines().forEach {line ->
|
||||
if (! cookieTXT.contains(line)) cookieTXT.append(it.content)
|
||||
}
|
||||
}
|
||||
cookieFile.apply { writeText(cookieTXT.toString()) }
|
||||
}
|
||||
|
||||
suspend fun importFromClipboard() {
|
||||
try{
|
||||
val clipboard: ClipboardManager =
|
||||
application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
var clip = clipboard.primaryClip!!.getItemAt(0).text
|
||||
Log.e("Aaa", clip.toString())
|
||||
if (clip.startsWith(cookieHeader)){
|
||||
clip = clip.removePrefix(cookieHeader)
|
||||
val cookie = CookieItem(
|
||||
0,
|
||||
"Cookie Import [${System.currentTimeMillis()}]",
|
||||
clip.toString()
|
||||
)
|
||||
insert(cookie)
|
||||
updateCookiesFile()
|
||||
}
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun exportToClipboard() = viewModelScope.launch {
|
||||
try{
|
||||
val clipboard: ClipboardManager =
|
||||
application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
getCookiesFromDB().getOrNull()?.let {
|
||||
val cookieFile = File(application.cacheDir, "cookies.txt")
|
||||
if (! cookieFile.exists()) updateCookiesFile()
|
||||
cookieFile.readText().let {
|
||||
clipboard.setText(it)
|
||||
}
|
||||
}catch (e: Exception){
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
|||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.text.format.DateFormat
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
|
|
@ -10,10 +13,7 @@ import androidx.lifecycle.AndroidViewModel
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.*
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
|
|
@ -287,6 +287,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
|
||||
suspend fun queueDownloads(items: List<DownloadItem>) {
|
||||
val context = getApplication<App>().applicationContext
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
items.forEach { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
val ids = repository.insertAll(items)
|
||||
for (i in ids.indices){
|
||||
|
|
@ -301,6 +302,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.setInputData(Data.Builder().putLong("id", it.id).build())
|
||||
.addTag("download")
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(if (allowMeteredNetworks) NetworkType.CONNECTED else NetworkType.UNMETERED)
|
||||
.build()
|
||||
)
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
|
|
@ -318,6 +324,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
repository.delete(it)
|
||||
}
|
||||
}
|
||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
||||
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -78,10 +78,7 @@ class ShareActivity : AppCompatActivity() {
|
|||
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
||||
sharedPreferences = getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
||||
|
||||
cookieViewModel.getCookiesFromDB().getOrNull()?.let{
|
||||
File(cacheDir, "cookies.txt").apply { writeText(it) }
|
||||
}
|
||||
|
||||
cookieViewModel.updateCookiesFile()
|
||||
val intent = intent
|
||||
handleIntents(intent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
|
||||
class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickListener {
|
||||
|
|
@ -595,6 +596,17 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
|
|||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
homeAdapter?.invertSelected(resultsList)
|
||||
val invertedList = arrayListOf<ResultItem>()
|
||||
resultsList?.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it!!)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloads
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
|
|
@ -32,6 +33,7 @@ import com.deniscerri.ytdlnis.MainActivity
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.HistoryAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySort
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
|
|
@ -213,6 +215,10 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
deleteDialog.show()
|
||||
}
|
||||
}
|
||||
R.id.download_queue -> {
|
||||
val intent = Intent(context, DownloadQueueActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
R.id.remove_deleted_history -> {
|
||||
if(allhistoryList!!.isEmpty()){
|
||||
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -565,6 +571,17 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
actionMode?.finish()
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
historyAdapter?.invertSelected(historyList)
|
||||
val invertedList = arrayListOf<HistoryItem>()
|
||||
historyList?.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it!!)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@ import android.content.Context
|
|||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
|
|
@ -22,6 +25,8 @@ import com.deniscerri.ytdlnis.util.UiUtil
|
|||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.net.CookieManager
|
||||
|
||||
|
|
@ -70,13 +75,18 @@ class CookiesActivity : AppCompatActivity(), CookieAdapter.OnItemClickListener {
|
|||
deleteDialog.setMessage(getString(R.string.confirm_delete_cookies_desc))
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
android.webkit.CookieManager.getInstance().removeAllCookies(null)
|
||||
cookiesViewModel.deleteAll()
|
||||
kotlin.runCatching {
|
||||
File(cacheDir, "cookies.txt").apply { writeText("") }
|
||||
}
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
R.id.import_clipboard -> {
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
cookiesViewModel.importFromClipboard()
|
||||
}
|
||||
}
|
||||
R.id.export_clipboard -> {
|
||||
cookiesViewModel.exportToClipboard()
|
||||
}
|
||||
|
|
@ -97,7 +107,9 @@ class CookiesActivity : AppCompatActivity(), CookieAdapter.OnItemClickListener {
|
|||
builder.setTitle(getString(R.string.cookies))
|
||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
editText.setText(url)
|
||||
val text = if (url.isNullOrBlank()) "https://" else url
|
||||
editText.setText(text)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.get_cookies)
|
||||
|
|
@ -112,9 +124,8 @@ class CookiesActivity : AppCompatActivity(), CookieAdapter.OnItemClickListener {
|
|||
getString(R.string.cancel)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
val dialog = builder.create()
|
||||
editText.setOnKeyListener { view, i, keyEvent ->
|
||||
editText.doOnTextChanged { text, start, before, count ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
false
|
||||
}
|
||||
dialog.show()
|
||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.more
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebView
|
||||
|
|
@ -7,12 +8,16 @@ import android.webkit.WebViewClient
|
|||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
|
@ -25,6 +30,7 @@ class WebViewActivity : AppCompatActivity() {
|
|||
private lateinit var url: String
|
||||
private lateinit var cookies: String
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.webview_activity)
|
||||
|
|
@ -35,6 +41,7 @@ class WebViewActivity : AppCompatActivity() {
|
|||
toolbar = appbar.findViewById(R.id.webviewToolbar)
|
||||
generateBtn = toolbar.findViewById(R.id.generate)
|
||||
cookieManager = CookieManager.getInstance()
|
||||
cookieManager.removeAllCookies(null)
|
||||
|
||||
webView = findViewById(R.id.webview)
|
||||
webView.settings.javaScriptEnabled = true
|
||||
|
|
@ -62,20 +69,24 @@ class WebViewActivity : AppCompatActivity() {
|
|||
|
||||
generateBtn.setOnClickListener {
|
||||
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
||||
File(cacheDir , "/cookies.txt").apply { writeText(it) }
|
||||
kotlin.runCatching {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
cookiesViewModel.insert(
|
||||
CookieItem(
|
||||
0,
|
||||
url,
|
||||
it
|
||||
)
|
||||
)
|
||||
}
|
||||
cookiesViewModel.updateCookiesFile()
|
||||
}
|
||||
}.onFailure {
|
||||
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
kotlin.runCatching {
|
||||
cookiesViewModel.insert(
|
||||
CookieItem(
|
||||
0,
|
||||
url,
|
||||
""
|
||||
)
|
||||
)
|
||||
}.onFailure {
|
||||
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
cookieManager.flush()
|
||||
cookieManager.removeAllCookies(null)
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
private var incognito: SwitchPreferenceCompat? = null
|
||||
private var preferredDownloadType : ListPreference? = null
|
||||
private var downloadCard: SwitchPreferenceCompat? = null
|
||||
private var meteredNetwork: SwitchPreferenceCompat? = null
|
||||
private var apiKey: EditTextPreference? = null
|
||||
private var concurrentFragments: SeekBarPreference? = null
|
||||
private var concurrentDownloads: SeekBarPreference? = null
|
||||
|
|
@ -106,6 +107,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
incognito = findPreference("incognito")
|
||||
preferredDownloadType = findPreference("preferred_download_type")
|
||||
downloadCard = findPreference("download_card")
|
||||
meteredNetwork = findPreference("metered_networks")
|
||||
apiKey = findPreference("api_key")
|
||||
concurrentFragments = findPreference("concurrent_fragments")
|
||||
concurrentDownloads = findPreference("concurrent_downloads")
|
||||
|
|
@ -168,6 +170,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
editor.putBoolean("incognito", incognito!!.isChecked)
|
||||
editor.putString("preferred_download_type", preferredDownloadType!!.value)
|
||||
editor.putBoolean("download_card", downloadCard!!.isChecked)
|
||||
editor.putBoolean("metered_networks", meteredNetwork!!.isChecked)
|
||||
editor.putString("api_key", apiKey!!.text)
|
||||
editor.putInt("concurrent_fragments", concurrentFragments!!.value)
|
||||
editor.putInt("concurrent_downloads", concurrentDownloads!!.value)
|
||||
|
|
@ -282,8 +285,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
editor.apply()
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
downloadCard!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val enable = newValue as Boolean
|
||||
|
|
@ -291,6 +292,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
editor.apply()
|
||||
true
|
||||
}
|
||||
meteredNetwork!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val enable = newValue as Boolean
|
||||
editor.putBoolean("metered_networks", enable)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
apiKey!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
editor.putString("api_key", newValue.toString())
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.deniscerri.ytdlnis.util
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
|
|
@ -138,63 +140,70 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
||||
init()
|
||||
items = ArrayList()
|
||||
if (key!!.isEmpty()) {
|
||||
return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
|
||||
try{
|
||||
init()
|
||||
items = ArrayList()
|
||||
if (key!!.isEmpty()) {
|
||||
return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
|
||||
"",
|
||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
||||
)
|
||||
}
|
||||
val url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken=$nextPageToken&maxResults=50®ionCode=$countryCODE&playlistId=$id&key=$key"
|
||||
//short data
|
||||
val res = genericRequest(url)
|
||||
if (!res.has("items")) return PlaylistTuple(
|
||||
"",
|
||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
||||
)
|
||||
val dataArray = res.getJSONArray("items")
|
||||
|
||||
//extra data
|
||||
var url2 = "https://www.googleapis.com/youtube/v3/videos?id="
|
||||
//getting all ids, for the extra data request
|
||||
for (i in 0 until dataArray.length()) {
|
||||
val element = dataArray.getJSONObject(i)
|
||||
val snippet = element.getJSONObject("snippet")
|
||||
val videoID = snippet.getJSONObject("resourceId").getString("videoId")
|
||||
url2 = "$url2$videoID,"
|
||||
snippet.put("videoID", videoID)
|
||||
}
|
||||
url2 = url2.substring(
|
||||
0,
|
||||
url2.length - 1
|
||||
) + "&part=contentDetails®ionCode=" + countryCODE + "&key=" + key
|
||||
val extra = genericRequest(url2)
|
||||
val extraArray = extra.getJSONArray("items")
|
||||
var j = 0
|
||||
var i = 0
|
||||
while (i < extraArray.length()) {
|
||||
val element = dataArray.getJSONObject(i)
|
||||
val snippet = element.getJSONObject("snippet")
|
||||
var duration =
|
||||
extra.getJSONArray("items").getJSONObject(j).getJSONObject("contentDetails")
|
||||
.getString("duration")
|
||||
duration = formatDuration(duration)
|
||||
snippet.put("duration", duration)
|
||||
fixThumbnail(snippet)
|
||||
val v = createVideofromJSON(snippet)
|
||||
if (v == null || v.thumb.isEmpty()) {
|
||||
i++
|
||||
continue
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
v.playlistTitle = "YTDLnis"
|
||||
items.add(v)
|
||||
i++
|
||||
}
|
||||
val next = res.optString("nextPageToken")
|
||||
return PlaylistTuple(next, items)
|
||||
}catch (e: Exception){
|
||||
return PlaylistTuple(
|
||||
"",
|
||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
||||
)
|
||||
}
|
||||
val url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken=$nextPageToken&maxResults=50®ionCode=$countryCODE&playlistId=$id&key=$key"
|
||||
//short data
|
||||
val res = genericRequest(url)
|
||||
if (!res.has("items")) return PlaylistTuple(
|
||||
"",
|
||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
||||
)
|
||||
val dataArray = res.getJSONArray("items")
|
||||
|
||||
//extra data
|
||||
var url2 = "https://www.googleapis.com/youtube/v3/videos?id="
|
||||
//getting all ids, for the extra data request
|
||||
for (i in 0 until dataArray.length()) {
|
||||
val element = dataArray.getJSONObject(i)
|
||||
val snippet = element.getJSONObject("snippet")
|
||||
val videoID = snippet.getJSONObject("resourceId").getString("videoId")
|
||||
url2 = "$url2$videoID,"
|
||||
snippet.put("videoID", videoID)
|
||||
}
|
||||
url2 = url2.substring(
|
||||
0,
|
||||
url2.length - 1
|
||||
) + "&part=contentDetails®ionCode=" + countryCODE + "&key=" + key
|
||||
val extra = genericRequest(url2)
|
||||
val extraArray = extra.getJSONArray("items")
|
||||
var j = 0
|
||||
var i = 0
|
||||
while (i < extraArray.length()) {
|
||||
val element = dataArray.getJSONObject(i)
|
||||
val snippet = element.getJSONObject("snippet")
|
||||
var duration =
|
||||
extra.getJSONArray("items").getJSONObject(j).getJSONObject("contentDetails")
|
||||
.getString("duration")
|
||||
duration = formatDuration(duration)
|
||||
snippet.put("duration", duration)
|
||||
fixThumbnail(snippet)
|
||||
val v = createVideofromJSON(snippet)
|
||||
if (v == null || v.thumb.isEmpty()) {
|
||||
i++
|
||||
continue
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
v.playlistTitle = "YTDLnis"
|
||||
items.add(v)
|
||||
i++
|
||||
}
|
||||
val next = res.optString("nextPageToken")
|
||||
return PlaylistTuple(next, items)
|
||||
}
|
||||
|
||||
private fun getPlaylistFromInvidous(id: String): PlaylistTuple {
|
||||
|
|
@ -221,30 +230,34 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getVideo(id: String): ResultItem? {
|
||||
init()
|
||||
if (key!!.isEmpty()) {
|
||||
return if (useInvidous) {
|
||||
val res = genericRequest(invidousURL + "videos/" + id)
|
||||
if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideofromInvidiousJSON(
|
||||
res
|
||||
)
|
||||
} else {
|
||||
getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
|
||||
try {
|
||||
init()
|
||||
if (key!!.isEmpty()) {
|
||||
return if (useInvidous) {
|
||||
val res = genericRequest(invidousURL + "videos/" + id)
|
||||
if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideofromInvidiousJSON(
|
||||
res
|
||||
)
|
||||
} else {
|
||||
getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//short data
|
||||
var res =
|
||||
genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=$id&key=$key")
|
||||
if (!res.has("items")) return getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
|
||||
var duration = res.getJSONArray("items").getJSONObject(0).getJSONObject("contentDetails")
|
||||
.getString("duration")
|
||||
duration = formatDuration(duration)
|
||||
res = res.getJSONArray("items").getJSONObject(0).getJSONObject("snippet")
|
||||
res.put("videoID", id)
|
||||
res.put("duration", duration)
|
||||
fixThumbnail(res)
|
||||
return createVideofromJSON(res)
|
||||
//short data
|
||||
var res =
|
||||
genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=$id&key=$key")
|
||||
if (!res.has("items")) return getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
|
||||
var duration = res.getJSONArray("items").getJSONObject(0).getJSONObject("contentDetails")
|
||||
.getString("duration")
|
||||
duration = formatDuration(duration)
|
||||
res = res.getJSONArray("items").getJSONObject(0).getJSONObject("snippet")
|
||||
res.put("videoID", id)
|
||||
res.put("duration", duration)
|
||||
fixThumbnail(res)
|
||||
return createVideofromJSON(res)
|
||||
}catch (e: Exception){
|
||||
return getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
|
||||
}
|
||||
}
|
||||
|
||||
private fun createVideofromJSON(obj: JSONObject): ResultItem? {
|
||||
|
|
@ -510,7 +523,8 @@ class InfoUtil(private val context: Context) {
|
|||
arrayOf(youtubeDLResponse.out)
|
||||
}
|
||||
for (result in results) {
|
||||
val jsonObject = JSONObject(result!!)
|
||||
if (result.isNullOrBlank()) continue
|
||||
val jsonObject = JSONObject(result)
|
||||
val title = if (jsonObject.has("title")) {
|
||||
if (jsonObject.getString("title") == "[Private video]") continue
|
||||
jsonObject.getString("title")
|
||||
|
|
@ -531,7 +545,9 @@ class InfoUtil(private val context: Context) {
|
|||
thumb = jsonObject.getString("thumbnail")
|
||||
} else if (jsonObject.has("thumbnails")) {
|
||||
val thumbs = jsonObject.getJSONArray("thumbnails")
|
||||
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
||||
if (thumbs.length() > 0){
|
||||
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
||||
}
|
||||
}
|
||||
val website = if (jsonObject.has("ie_key")) jsonObject.getString("ie_key") else jsonObject.getString("extractor")
|
||||
var playlistTitle: String? = ""
|
||||
|
|
@ -560,6 +576,9 @@ class InfoUtil(private val context: Context) {
|
|||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Looper.prepare().run {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
e.printStackTrace()
|
||||
}
|
||||
return items
|
||||
|
|
|
|||
|
|
@ -145,25 +145,27 @@ class DownloadWorker(
|
|||
|
||||
if (audioQualityId.isNotBlank()){
|
||||
request.addOption("-f", audioQualityId)
|
||||
}
|
||||
|
||||
val ext = downloadItem.format.container
|
||||
if(ext != context.getString(R.string.defaultValue) && ext != "webm"){
|
||||
val codec = when(downloadItem.format.container){
|
||||
"m4a" -> "aac"
|
||||
"aac" -> "aac"
|
||||
"mp3" -> "libmp3lame"
|
||||
"flac" -> "flac"
|
||||
"opus" -> "libopus"
|
||||
else -> ""
|
||||
}
|
||||
Log.e("aa", codec)
|
||||
if (codec.isEmpty()){
|
||||
request.addOption("-x")
|
||||
}else{
|
||||
request.addOption("--remux-video", ext)
|
||||
request.addOption("--ppa", "VideoRemuxer:-vn -c:a $codec")
|
||||
val ext = downloadItem.format.container
|
||||
if(ext != context.getString(R.string.defaultValue) && ext != "webm"){
|
||||
val codec = when(downloadItem.format.container){
|
||||
"aac" -> "aac"
|
||||
"mp3" -> "libmp3lame"
|
||||
"flac" -> "flac"
|
||||
"opus" -> "libopus"
|
||||
else -> ""
|
||||
}
|
||||
Log.e("aa", codec)
|
||||
if (codec.isEmpty()){
|
||||
request.addOption("-x")
|
||||
}else{
|
||||
request.addOption("--remux-video", ext)
|
||||
request.addOption("--ppa", "VideoRemuxer:-vn -c:a $codec")
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
request.addOption("-x")
|
||||
}
|
||||
|
||||
request.addOption("--embed-metadata")
|
||||
|
|
|
|||
|
|
@ -103,6 +103,15 @@ class TerminalDownloadWorker(
|
|||
}, 1000)
|
||||
}
|
||||
|
||||
val chunks = it.out.chunked(5000)
|
||||
val dataBuilder = Data.Builder();
|
||||
dataBuilder.putString("output", chunks[0])
|
||||
|
||||
|
||||
return Result.success(
|
||||
dataBuilder.build()
|
||||
)
|
||||
|
||||
}.onFailure {
|
||||
if (it is YoutubeDL.CanceledException) {
|
||||
return Result.failure()
|
||||
|
|
|
|||
5
app/src/main/res/drawable/baseline_downloading_24.xml
Normal file
5
app/src/main/res/drawable/baseline_downloading_24.xml
Normal 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:textColorPrimary" android:pathData="M18.32,4.26C16.84,3.05 15.01,2.25 13,2.05v2.02c1.46,0.18 2.79,0.76 3.9,1.62L18.32,4.26zM19.93,11h2.02c-0.2,-2.01 -1,-3.84 -2.21,-5.32L18.31,7.1C19.17,8.21 19.75,9.54 19.93,11zM18.31,16.9l1.43,1.43c1.21,-1.48 2.01,-3.32 2.21,-5.32h-2.02C19.75,14.46 19.17,15.79 18.31,16.9zM13,19.93v2.02c2.01,-0.2 3.84,-1 5.32,-2.21l-1.43,-1.43C15.79,19.17 14.46,19.75 13,19.93zM13,12V7h-2v5H7l5,5l5,-5H13zM11,19.93v2.02c-5.05,-0.5 -9,-4.76 -9,-9.95s3.95,-9.45 9,-9.95v2.02C7.05,4.56 4,7.92 4,12S7.05,19.44 11,19.93z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/metered_networks.xml
Normal file
5
app/src/main/res/drawable/metered_networks.xml
Normal 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="M18.99,11.5c0.34,0 0.67,0.03 1,0.07L20,0 0,20h11.56c-0.04,-0.33 -0.07,-0.66 -0.07,-1 0,-4.14 3.36,-7.5 7.5,-7.5zM22.7,19.49c0.02,-0.16 0.04,-0.32 0.04,-0.49 0,-0.17 -0.01,-0.33 -0.04,-0.49l1.06,-0.83c0.09,-0.08 0.12,-0.21 0.06,-0.32l-1,-1.73c-0.06,-0.11 -0.19,-0.15 -0.31,-0.11l-1.24,0.5c-0.26,-0.2 -0.54,-0.37 -0.85,-0.49l-0.19,-1.32c-0.01,-0.12 -0.12,-0.21 -0.24,-0.21h-2c-0.12,0 -0.23,0.09 -0.25,0.21l-0.19,1.32c-0.3,0.13 -0.59,0.29 -0.85,0.49l-1.24,-0.5c-0.11,-0.04 -0.24,0 -0.31,0.11l-1,1.73c-0.06,0.11 -0.04,0.24 0.06,0.32l1.06,0.83c-0.02,0.16 -0.03,0.32 -0.03,0.49 0,0.17 0.01,0.33 0.03,0.49l-1.06,0.83c-0.09,0.08 -0.12,0.21 -0.06,0.32l1,1.73c0.06,0.11 0.19,0.15 0.31,0.11l1.24,-0.5c0.26,0.2 0.54,0.37 0.85,0.49l0.19,1.32c0.02,0.12 0.12,0.21 0.25,0.21h2c0.12,0 0.23,-0.09 0.25,-0.21l0.19,-1.32c0.3,-0.13 0.59,-0.29 0.84,-0.49l1.25,0.5c0.11,0.04 0.24,0 0.31,-0.11l1,-1.73c0.06,-0.11 0.03,-0.24 -0.06,-0.32l-1.07,-0.83zM18.99,20.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
|
||||
</vector>
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
android:checkable="true"
|
||||
app:strokeWidth="0dp"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_margin="20dp">
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/image_view"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_margin="10dp"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
|
||||
|
||||
|
|
@ -37,6 +36,7 @@
|
|||
android:id="@+id/chips_command"
|
||||
android:layout_width="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_margin="10dp"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/newCookie"
|
||||
android:layout_margin="10dp"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -30,47 +30,58 @@
|
|||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_margin="10dp"
|
||||
android:id="@+id/custom_command_scrollview"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
android:id="@+id/custom_command_scrollview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/custom_command_output"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="bottom"
|
||||
android:textIsSelectable="true" />
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/command_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="start"
|
||||
android:inputType="textMultiLine"
|
||||
android:paddingBottom="100dp"
|
||||
android:maxLines="100"
|
||||
android:text="yt-dlp "
|
||||
android:textSize="15sp" />
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/custom_command_output"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="bottom"
|
||||
android:scrollbars="horizontal|vertical"
|
||||
android:scrollHorizontally="true"
|
||||
android:textIsSelectable="true" />
|
||||
|
||||
</ScrollView>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/command_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="start"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="100"
|
||||
android:text="yt-dlp "
|
||||
android:textSize="15sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
|
|
|
|||
|
|
@ -135,6 +135,8 @@
|
|||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/shortcutsChipGroup"
|
||||
android:layout_width="wrap_content"
|
||||
app:chipSpacingVertical="-10dp"
|
||||
app:chipSpacing="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="false">
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:hint="@string/title"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
|
@ -87,8 +88,8 @@
|
|||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/outputPath"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:paddingTop="10dp"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -113,7 +114,7 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/adjust_audio"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="10dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:hint="@string/title"
|
||||
android:padding="10dp">
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingHorizontal="10dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
android:paddingHorizontal="10dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/author_textinput"
|
||||
|
|
@ -111,7 +112,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
android:paddingHorizontal="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -6,132 +6,138 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="MissingConstraints">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/ic_app_icon"
|
||||
android:scaleType="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/terminal"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/terminal"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:paddingVertical="15dp"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
app:drawableStartCompat="@drawable/ic_terminal" />
|
||||
android:gravity="top"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="MissingConstraints">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/logs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/logs"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:paddingVertical="15dp"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
app:drawableStartCompat="@drawable/ic_baseline_file_open_24" />
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/ic_app_icon"
|
||||
android:scaleType="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/terminal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/terminal"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:paddingVertical="15dp"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
app:drawableStartCompat="@drawable/ic_terminal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/logs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/logs"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:paddingVertical="15dp"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
app:drawableStartCompat="@drawable/ic_baseline_file_open_24" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/command_templates"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/command_templates"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_baseline_keyboard_arrow_right_24" />
|
||||
<TextView
|
||||
android:id="@+id/command_templates"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/command_templates"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_baseline_keyboard_arrow_right_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_queue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/download_queue"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_concurrent_downloads" />
|
||||
<TextView
|
||||
android:id="@+id/download_queue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/download_queue"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_concurrent_downloads" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cookies"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/cookies"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/baseline_cookie_24" />
|
||||
<TextView
|
||||
android:id="@+id/cookies"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/cookies"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/baseline_cookie_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/terminate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/kill_app"
|
||||
android:textStyle="bold"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_power" />
|
||||
<TextView
|
||||
android:id="@+id/terminate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/kill_app"
|
||||
android:textStyle="bold"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_power" />
|
||||
|
||||
|
||||
<View
|
||||
android:layout_margin="0dp"
|
||||
style="@style/Divider.Horizontal" />
|
||||
<View
|
||||
android:layout_margin="0dp"
|
||||
style="@style/Divider.Horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/settings"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_settings" />
|
||||
<TextView
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/settings"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/ic_settings" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
|||
|
|
@ -10,15 +10,13 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:padding="10dp"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/sort_by"
|
||||
android:paddingBottom="20dp"
|
||||
android:textSize="12sp"
|
||||
android:layout_margin="20dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
|
|
@ -27,6 +25,7 @@
|
|||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:drawablePadding="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -41,6 +40,7 @@
|
|||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:drawablePadding="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:drawablePadding="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -70,6 +71,7 @@
|
|||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="30dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
|
|
@ -77,7 +79,6 @@
|
|||
app:drawableStartCompat="@drawable/ic_arrow_up" />
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@
|
|||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/shortcutsChipGroup"
|
||||
android:layout_width="wrap_content"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:chipSpacingVertical="-10dp"
|
||||
app:chipSpacing="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="false">
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="10dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@
|
|||
android:title="@string/delete_all_cookies"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/import_clipboard"
|
||||
android:title="@string/import_from_clipboard"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/export_clipboard"
|
||||
android:title="@string/export_from_clipboard"
|
||||
|
|
|
|||
|
|
@ -7,9 +7,15 @@
|
|||
android:id="@+id/search_history"
|
||||
android:title="@string/search"
|
||||
android:icon="@drawable/ic_search"
|
||||
app:showAsAction="collapseActionView|always"
|
||||
app:showAsAction="collapseActionView|ifRoom"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/download_queue"
|
||||
android:title="@string/download_queue"
|
||||
android:icon="@drawable/baseline_downloading_24"
|
||||
app:showAsAction="collapseActionView|ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/remove_history"
|
||||
android:icon="@drawable/ic_delete_all"
|
||||
|
|
|
|||
|
|
@ -15,5 +15,10 @@
|
|||
app:showAsAction="ifRoom"
|
||||
android:title="@string/share" />
|
||||
|
||||
<item
|
||||
android:id="@+id/invert_selected"
|
||||
android:title="@string/invert_selected"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,5 +20,10 @@
|
|||
android:title="@string/select_all"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/invert_selected"
|
||||
android:title="@string/invert_selected"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
|
||||
|
|
|
|||
|
|
@ -204,4 +204,8 @@
|
|||
<string name="get_cookies">Get Cookies</string>
|
||||
<string name="delete_all_cookies">Delete All Cookies</string>
|
||||
<string name="confirm_delete_cookies_desc">Delete all the cookies, permanently</string>
|
||||
<string name="download_over_metered_networks">Download over Metered Networks</string>
|
||||
<string name="download_over_metered_networks_summary">Let the download worker work on any network type. If disabled, it will wait for an unmetered network to start the downloads</string>
|
||||
<string name="metered_network_download_start_info">Download worker will start after you are connected to an unmetered network</string>
|
||||
<string name="invert_selected">Invert Selected</string>
|
||||
</resources>
|
||||
|
|
@ -65,6 +65,14 @@
|
|||
app:summary="@string/download_card_summary"
|
||||
app:title="@string/show_download_card" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:icon="@drawable/metered_networks"
|
||||
android:key="metered_networks"
|
||||
app:summary="@string/download_over_metered_networks_summary"
|
||||
app:title="@string/download_over_metered_networks" />
|
||||
|
||||
<ListPreference
|
||||
android:dependency="download_card"
|
||||
android:defaultValue="video"
|
||||
|
|
|
|||
Loading…
Reference in a new issue