implemented cookies import & invert selected in contextual app bar & download preference without metered networks

This commit is contained in:
deniscerri 2023-04-12 22:17:33 +02:00
parent f6e5f771f6
commit 55b59d75b1
No known key found for this signature in database
GPG key ID: 95C43D517D830350
38 changed files with 521 additions and 305 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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&regionCode=$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&regionCode=" + 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&regionCode=$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&regionCode=" + 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

View file

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

View file

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

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

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="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>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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