- added subtitle language codes suggestions in the settings page
- made the extractor chips in history page Sentence case
- added a changelog screen where you can see recent releases and you can download the apks from it too
- prevented app from crashing when trying to backup from a corrupted backup
- added uploader_id as fallback for author data fetching in yt-dlp in case others are empty
- fixed null pointer exception when running the update multiple items formats worker
- added the seconds where the cut starts on downloads with cuts in them
- made autonumbers be normal numbers instead of being 5 digits
- fixed filename templates in cut files and added the index in the beginning and fixed bugs if the users left the template as empty
- added 240p as a generic format
This commit is contained in:
zaednasr 2023-12-17 21:02:04 +01:00
parent f0c88dfc73
commit fba26b50d0
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
26 changed files with 624 additions and 342 deletions

View file

@ -9,8 +9,8 @@ plugins {
def properties = new Properties()
def versionMajor = 1
def versionMinor = 6
def versionPatch = 9
def versionMinor = 7
def versionPatch = 0
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def versionExt = ""

View file

@ -339,10 +339,6 @@ class MainActivity : BaseActivity() {
window.statusBarColor = getColor(android.R.color.transparent)
incognitoHeader.visibility = View.GONE
}
//check logs option to show in tablet navbar
if (navigationView is NavigationView){
(navigationView as NavigationView).menu.findItem(R.id.downloadLogListFragment).isVisible = preferences.getBoolean("log_downloads", false)
}
}
override fun onNewIntent(intent: Intent) {

View file

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis.database.models
import com.google.gson.annotations.SerializedName
import java.util.Date
data class GithubRelease(
@SerializedName(value = "html_url")
@ -9,6 +10,8 @@ data class GithubRelease(
var tag_name: String,
@SerializedName(value = "body")
var body: String,
@SerializedName(value = "published_at")
var published_at: Date,
@SerializedName(value = "assets")
var assets: List<GithubReleaseAsset>
)

View file

@ -64,7 +64,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
val infoUtil = InfoUtil(context)
val items : ArrayList<ResultItem?> = arrayListOf()
do {
val tmp = infoUtil.getPlaylist(query, nextPageToken)
val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0]?.playlistTitle ?: "" else "")
items.addAll(tmp.videos)
val tmpToken = tmp.nextPageToken
if (tmpToken.isEmpty()) break

View file

@ -354,25 +354,35 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
fun switchDownloadType(list: List<DownloadItem>, type: Type) : List<DownloadItem>{
val updatedDownloadPath : String = when(type){
Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())!!
Type.command -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())!!
else -> ""
}
list.forEach {
val format = getFormat(it.allFormats, type)
it.format = format
val currentDownloadPath = when(it.type){
Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())
Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())
Type.command -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
else -> ""
}
if (it.downloadPath == currentDownloadPath) it.downloadPath = updatedDownloadPath
var updatedDownloadPath: String = ""
var container: String = ""
when(type){
Type.audio -> {
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
container = sharedPreferences.getString("audio_format", "")!!
}
Type.video -> {
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
container = sharedPreferences.getString("audio_format", "")!!
}
Type.command -> {
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
container = ""
}
else -> {
updatedDownloadPath = ""
}
}
it.downloadPath = updatedDownloadPath
it.type = type
it.container = container
}
return list
}
@ -443,7 +453,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
requirements.add {it: Format -> audioFormatIDPreference.contains(it.format_id)}
sharedPreferences.getString("audio_language", "")?.apply {
if (this.isNotEmpty()){
if (this.isNotBlank()){
requirements.add { it: Format -> it.lang == this }
}
}

View file

@ -275,10 +275,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
val items = mutableListOf<DownloadItem>()
ids?.forEach {
items.add(downloadViewModel.getItemByID(it))
downloadViewModel.deleteDownload(it)
}
withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
Pair("downloads", items)
))
}
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
Pair("downloads", items)
))
}
}
@ -449,96 +452,100 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
@SuppressLint("InflateParams")
private fun updateSearchViewItems(searchQuery: Editable?, linkYouCopied: View?) = lifecycleScope.launch(Dispatchers.Main) {
searchSuggestionsLinearLayout!!.visibility = GONE
searchHistoryLinearLayout!!.visibility = GONE
searchSuggestionsLinearLayout!!.post {
searchSuggestionsLinearLayout!!.removeAllViews()
}
searchHistoryLinearLayout!!.post {
searchHistoryLinearLayout!!.removeAllViews()
}
linkYouCopied!!.visibility = GONE
if (searchView!!.editText.text.isEmpty()){
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
}else{
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
}
val history = withContext(Dispatchers.IO){
resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) }
}
val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){
infoUtil!!.getSearchSuggestions(searchQuery.toString())
}else{
emptyList()
}
history.forEach { s ->
val v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null)
val textView = v.findViewById<TextView>(R.id.suggestion_text)
textView.text = s
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
Handler(Looper.getMainLooper()).post {
searchHistoryLinearLayout!!.addView(
v
)
lifecycleScope.launch {
searchSuggestionsLinearLayout!!.visibility = GONE
searchHistoryLinearLayout!!.visibility = GONE
searchSuggestionsLinearLayout!!.post {
searchSuggestionsLinearLayout!!.removeAllViews()
}
textView.setOnClickListener {
searchView!!.setText(s)
initSearch(searchView!!)
searchHistoryLinearLayout!!.post {
searchHistoryLinearLayout!!.removeAllViews()
}
textView.setOnLongClickListener {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + s + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
searchHistoryLinearLayout!!.removeView(v)
resultViewModel.removeSearchQueryFromHistory(s)
linkYouCopied!!.visibility = GONE
if (searchView!!.editText.text.isEmpty()){
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
}else{
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
}
val history = withContext(Dispatchers.IO){
resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) }
}
val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){
withContext(Dispatchers.IO){
infoUtil!!.getSearchSuggestions(searchQuery.toString())
}
deleteDialog.show()
true
}else{
emptyList()
}
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
searchView!!.editText.setText(s)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
}
searchHistoryLinearLayout!!.isVisible = history.isNotEmpty()
if (linkYouCopied.findViewById<TextView>(R.id.suggestion_text).text.isNotEmpty()){
linkYouCopied.visibility = VISIBLE
}
history.forEach { s ->
val v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null)
val textView = v.findViewById<TextView>(R.id.suggestion_text)
textView.text = s
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
Handler(Looper.getMainLooper()).post {
searchHistoryLinearLayout!!.addView(
v
)
}
textView.setOnClickListener {
searchView!!.setText(s)
initSearch(searchView!!)
}
textView.setOnLongClickListener {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + s + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
searchHistoryLinearLayout!!.removeView(v)
resultViewModel.removeSearchQueryFromHistory(s)
}
deleteDialog.show()
true
}
suggestions.forEach { s ->
val v = LayoutInflater.from(fragmentContext)
.inflate(R.layout.search_suggestion_item, null)
val textView = v.findViewById<TextView>(R.id.suggestion_text)
textView.text = s
Handler(Looper.getMainLooper()).post {
searchSuggestionsLinearLayout!!.addView(
v
)
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
searchView!!.editText.setText(s)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
}
textView.setOnClickListener {
searchView!!.setText(s)
initSearch(searchView!!)
searchHistoryLinearLayout!!.isVisible = history.isNotEmpty()
if (linkYouCopied.findViewById<TextView>(R.id.suggestion_text).text.isNotEmpty()){
linkYouCopied.visibility = VISIBLE
}
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
searchView!!.editText.setText(s)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
}
searchSuggestionsLinearLayout!!.isVisible = suggestions.isNotEmpty()
if (Patterns.WEB_URL.matcher(searchView!!.editText.text).matches()){
providersChipGroup?.visibility = GONE
chipGroupDivider?.visibility = GONE
}else{
providersChipGroup?.visibility = VISIBLE
chipGroupDivider?.visibility = VISIBLE
suggestions.forEach { s ->
val v = LayoutInflater.from(fragmentContext)
.inflate(R.layout.search_suggestion_item, null)
val textView = v.findViewById<TextView>(R.id.suggestion_text)
textView.text = s
Handler(Looper.getMainLooper()).post {
searchSuggestionsLinearLayout!!.addView(
v
)
}
textView.setOnClickListener {
searchView!!.setText(s)
initSearch(searchView!!)
}
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
searchView!!.editText.setText(s)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
}
searchSuggestionsLinearLayout!!.isVisible = suggestions.isNotEmpty()
if (Patterns.WEB_URL.matcher(searchView!!.editText.text).matches()){
providersChipGroup?.visibility = GONE
chipGroupDivider?.visibility = GONE
}else{
providersChipGroup?.visibility = VISIBLE
chipGroupDivider?.visibility = VISIBLE
}
}
}

View file

@ -128,7 +128,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
} else if (item.type == DownloadViewModel.Type.video) {
if (filePresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video)
}else{
btn.setImageResource(R.drawable.ic_terminal)
if (filePresent) btn.setImageResource(R.drawable.ic_terminal) else btn.setImageResource(R.drawable.baseline_code_off_24)
}
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = filePresent

View file

@ -253,9 +253,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val ids = mutableListOf<Long>()
runBlocking {
items.map { it.status = DownloadRepository.Status.Saved.toString() }
items.forEach {
ids.add(downloadViewModel.repository.insert(it))
}
ids.addAll(downloadViewModel.repository.insertAll(items))
}
val id = System.currentTimeMillis().toInt()
val workRequest = OneTimeWorkRequestBuilder<UpdatePlaylistFormatsWorker>()

View file

@ -16,6 +16,7 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.facebook.shimmer.ShimmerFrameLayout
@ -147,19 +148,30 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
updateFormatsJob = launch(Dispatchers.IO) {
//simple download
if (items.size == 1) {
val res = infoUtil.getFormats(items.first()!!.url)
res.filter { it.format_note != "storyboard" }
chosenFormats = if (items.first()?.type == Type.audio) {
res.filter { it.format_note.contains("audio", ignoreCase = true) }
} else {
res
}
if (chosenFormats.isEmpty()) throw Exception()
kotlin.runCatching {
val res = infoUtil.getFormats(items.first()!!.url)
res.filter { it.format_note != "storyboard" }
chosenFormats = if (items.first()?.type == Type.audio) {
res.filter { it.format_note.contains("audio", ignoreCase = true) }
} else {
res
}
if (chosenFormats.isEmpty()) throw Exception()
formats = listOf(res)
withContext(Dispatchers.Main){
listener.onFormatsUpdated(formats)
formats = listOf(res)
withContext(Dispatchers.Main){
listener.onFormatsUpdated(formats)
}
}.onFailure { err ->
withContext(Dispatchers.Main){
UiUtil.handleResultResponse(requireActivity(), ResultViewModel.ResultsUiState(
false,
Pair(R.string.no_results, err.message.toString()),
mutableListOf(Pair(R.string.copy_log, ResultViewModel.ResultAction.COPY_LOG))
), closed = {})
}
}
//list format filtering
}else{
var progress = "0/${items.size}"

View file

@ -26,6 +26,8 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.forEach
@ -410,7 +412,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
val w = websites[i]
if (w == "null" || w.isEmpty()) continue
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
tmp.text = w
tmp.text = w.replaceFirstChar { if (it.isLowerCase()) it.titlecase(java.util.Locale.getDefault()) else it.toString() }
tmp.id = i
tmp.setOnClickListener {
Log.e(TAG, tmp.isChecked.toString())

View file

@ -1,19 +1,15 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.os.LocaleListCompat
import androidx.preference.ListPreference
import androidx.preference.Preference
@ -84,6 +80,8 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
}
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this.startActivity(intent)
requireActivity().finishAffinity()
true
@ -93,6 +91,8 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this.startActivity(intent)
requireActivity().finishAffinity()
true
@ -101,6 +101,8 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this.startActivity(intent)
requireActivity().finishAffinity()

View file

@ -9,6 +9,8 @@ import android.os.Bundle
import android.os.Environment
import android.util.LayoutDirection
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
@ -36,10 +38,12 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
@ -66,7 +70,6 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
private lateinit var cookieViewModel: CookieViewModel
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
private var version: Preference? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
@ -149,21 +152,40 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
lifecycleScope.launch(Dispatchers.IO) {
if (checkedItems.all { !it }){
withContext(Dispatchers.Main){
Snackbar.make(requireView(), R.string.select_backup_categories, Snackbar.LENGTH_SHORT).show()
}
return@launch
}
val json = JsonObject()
json.addProperty("app", "YTDLnis_backup")
for (i in 0 until checkedItems.size) {
if (checkedItems[i]) {
when(values[i]){
"settings" -> json.add("settings", backupSettings(preferences))
"downloads" -> json.add("downloads", backupHistory())
"queued" -> json.add("queued", backupQueuedDownloads() )
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
"errored" -> json.add("errored", backupErroredDownloads() )
"saved" -> json.add("saved", backupSavedDownloads() )
"cookies" -> json.add("cookies", backupCookies() )
"templates" -> json.add("templates", backupCommandTemplates() )
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
"searchHistory" -> json.add("search_history", backupSearchHistory() )
runCatching {
when(values[i]){
"settings" -> json.add("settings", backupSettings(preferences))
"downloads" -> json.add("downloads", backupHistory())
"queued" -> json.add("queued", backupQueuedDownloads() )
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
"errored" -> json.add("errored", backupErroredDownloads() )
"saved" -> json.add("saved", backupSavedDownloads() )
"cookies" -> json.add("cookies", backupCookies() )
"templates" -> json.add("templates", backupCommandTemplates() )
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
"searchHistory" -> json.add("search_history", backupSearchHistory() )
}
}.onFailure {err ->
withContext(Dispatchers.Main){
val snack = Snackbar.make(requireView(), err.message ?: requireContext().getString(R.string.errored), Snackbar.LENGTH_LONG)
val snackbarView: View = snack.view
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
snackTextView.maxLines = 9999999
snack.setAction(android.R.string.copy){
UiUtil.copyToClipboard(err.message ?: requireContext().getString(R.string.errored), requireActivity())
}
snack.show()
}
}
}
}
@ -176,8 +198,12 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
Calendar.DAY_OF_MONTH)} [${currentTime.get(Calendar.MILLISECOND)}.json")
saveFile.delete()
saveFile.createNewFile()
saveFile.writeText(Gson().toJson(json))
Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG).show()
saveFile.writeText(GsonBuilder().setPrettyPrinting().create().toJson(json))
val s = Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG)
s.setAction(R.string.Open_File){
UiUtil.openFileIntent(requireContext(), saveFile.absolutePath)
}
s.show()
}
}
@ -201,25 +227,6 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
true
}
version = findPreference("version")
val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir
version!!.summary = "${BuildConfig.VERSION_NAME} [${nativeLibraryDir?.split("/lib/")?.get(1)}]"
version!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
lifecycleScope.launch{
withContext(Dispatchers.IO){
updateUtil!!.updateApp{ msg ->
lifecycleScope.launch(Dispatchers.Main){
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
}
}
}
}
true
}
}
private fun backupSettings(preferences: SharedPreferences) : JsonArray {

View file

@ -3,22 +3,38 @@ package com.deniscerri.ytdlnis.ui.more.settings
import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.util.UiUtil
class ProcessingSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.processing
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.processing_preferences, rootKey)
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = prefs.edit()
val preferredFormatID : EditTextPreference? = findPreference("format_id")
val preferredFormatIDAudio : EditTextPreference? = findPreference("format_id_audio")
val subtitleLanguages : Preference? = findPreference("subs_lang")
preferredFormatID?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.video)}]"
preferredFormatID?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
preferredFormatIDAudio?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.audio)}]"
preferredFormatIDAudio?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
subtitleLanguages?.summary = prefs.getString("subs_lang", "en.*,.*-orig")!!
subtitleLanguages?.setOnPreferenceClickListener {
UiUtil.showSubtitleLanguagesDialog(requireActivity(), prefs.getString("subs_lang", "en.*,.*-orig")!!){
editor.putString("subs_lang", it)
editor.apply()
subtitleLanguages.summary = it
}
true
}
}
}

View file

@ -1,15 +1,20 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class UpdateSettingsFragment : BaseSettingsFragment() {
@ -17,6 +22,8 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
private var updateYTDL: Preference? = null
private var ytdlVersion: Preference? = null
private var updateUtil: UpdateUtil? = null
private var version: Preference? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -39,33 +46,44 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
Snackbar.make(requireView(),
requireContext().getString(R.string.ytdl_updating_started),
Snackbar.LENGTH_LONG).show()
when (updateUtil!!.updateYoutubeDL()) {
YoutubeDL.UpdateStatus.DONE -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.ytld_update_success),
Snackbar.LENGTH_LONG).show()
runCatching {
when (updateUtil!!.updateYoutubeDL()) {
YoutubeDL.UpdateStatus.DONE -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.ytld_update_success),
Snackbar.LENGTH_LONG).show()
YoutubeDL.getInstance().version(context)?.let {
editor.putString("ytdl-version", it)
editor.apply()
ytdlVersion!!.summary = it
YoutubeDL.getInstance().version(context)?.let {
editor.putString("ytdl-version", it)
editor.apply()
ytdlVersion!!.summary = it
}
}
YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(),
requireContext().getString(R.string.you_are_in_latest_version),
Snackbar.LENGTH_LONG).show()
else -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.errored),
Snackbar.LENGTH_LONG).show()
}
}
YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(),
requireContext().getString(R.string.you_are_in_latest_version),
Snackbar.LENGTH_LONG).show()
else -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.errored),
Snackbar.LENGTH_LONG).show()
}.onFailure {
val msg = it.message ?: requireContext().getString(R.string.errored)
val snackBar = Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG)
snackBar.setAction(android.R.string.copy){
UiUtil.copyToClipboard(msg, requireActivity())
}
val snackbarView: View = snackBar.view
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
snackTextView.maxLines = 9999999
snackBar.show()
}
}
true
}
val pipedInstance = findPreference<Preference>("piped_instance")
pipedInstance?.setOnPreferenceClickListener {
findPreference<Preference>("piped_instance")?.setOnPreferenceClickListener {
UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){
editor.putString("piped_instance", it)
editor.apply()
@ -73,6 +91,32 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
true
}
findPreference<Preference>("changelog")?.setOnPreferenceClickListener {
lifecycleScope.launch(Dispatchers.IO) {
updateUtil?.showChangeLog(requireActivity())
}
true
}
version = findPreference("version")
val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir
version!!.summary = "${BuildConfig.VERSION_NAME} [${nativeLibraryDir?.split("/lib/")?.get(1)}]"
version!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
lifecycleScope.launch{
withContext(Dispatchers.IO){
updateUtil!!.updateApp{ msg ->
lifecycleScope.launch(Dispatchers.Main){
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
}
}
}
}
true
}
}

View file

@ -13,6 +13,7 @@ import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import android.widget.EditText
import android.widget.ScrollView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.neo.highlight.core.Highlight

View file

@ -100,7 +100,7 @@ class InfoUtil(private val context: Context) {
}
@Throws(JSONException::class)
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple {
try{
val items = arrayListOf<ResultItem?>()
// -------------- PIPED API FUNCTION -------------------
@ -117,7 +117,7 @@ class InfoUtil(private val context: Context) {
kotlin.runCatching {
val obj = dataArray.getJSONObject(i)
val itm = createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))
itm?.playlistTitle = res.getString("name")
itm?.playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
itm?.playlistURL = "https://www.youtube.com/playlist?list=$id"
itm?.playlistIndex = (i+1)
items.add(itm)
@ -425,65 +425,61 @@ class InfoUtil(private val context: Context) {
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
val m = p.matcher(url)
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
return try {
if(m.find() && formatSource == "piped"){
if (m.find() && formatSource == "piped"){
return try {
val id = getIDFromYoutubeURL(url)
val res = genericRequest("$pipedURL/streams/$id")
if (res.length() == 0) getFromYTDL(url)[0]!!
val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id")
item!!.formats
}else{
getFormatsFromYTDL(url)
}catch(e: Exception) {
if (e is CancellationException) throw e
return getFormatsFromYTDL(url)
}
}catch (e: Exception){
if (e is CancellationException) throw e
getFormatsFromYTDL(url)
}else{
return getFormatsFromYTDL(url)
}
}
private fun getFormatsFromYTDL(url: String) : List<Format> {
try {
val request = YoutubeDLRequest(url)
request.addOption("--print", "%(formats)s")
request.addOption("--print", "%(duration)s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--compat-options", "manifest-filesize-approx")
request.addOption("--socket-timeout", "5")
val request = YoutubeDLRequest(url)
request.addOption("--print", "%(formats)s")
request.addOption("--print", "%(duration)s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--compat-options", "manifest-filesize-approx")
request.addOption("--socket-timeout", "5")
if (sharedPreferences.getBoolean("use_cookies", false)){
FileUtil.getCookieFile(context){
request.addOption("--cookies", it)
}
val useHeader = sharedPreferences.getBoolean("use_header", false)
val header = sharedPreferences.getString("useragent_header", "")
if (useHeader && !header.isNullOrBlank()){
request.addOption("--add-header","User-Agent:${header}")
}
if (sharedPreferences.getBoolean("use_cookies", false)){
FileUtil.getCookieFile(context){
request.addOption("--cookies", it)
}
val proxy = sharedPreferences.getString("proxy", "")
if (proxy!!.isNotBlank()) {
request.addOption("--proxy", proxy)
val useHeader = sharedPreferences.getBoolean("use_header", false)
val header = sharedPreferences.getString("useragent_header", "")
if (useHeader && !header.isNullOrBlank()){
request.addOption("--add-header","User-Agent:${header}")
}
val res = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
res.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(res.out)
}
val json = results[0]
val jsonArray = JSONArray(json)
return parseYTDLFormats(jsonArray)
}catch (e: Exception){
return listOf()
}
val proxy = sharedPreferences.getString("proxy", "")
if (proxy!!.isNotBlank()) {
request.addOption("--proxy", proxy)
}
val res = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
res.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(res.out)
}
val json = results[0]
val jsonArray = JSONArray(json)
return parseYTDLFormats(jsonArray)
}
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
@ -548,8 +544,9 @@ class InfoUtil(private val context: Context) {
urls.forEach {
val id = getIDFromYoutubeURL(it)
val res = genericRequest("$pipedURL/streams/$id")
val vid = createVideoFromPipedJSON(res, it)
progress(vid!!.formats)
createVideoFromPipedJSON(res, it)?.apply {
progress(this.formats)
}
}
}
@ -629,6 +626,9 @@ class InfoUtil(private val context: Context) {
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("uploader_id")) jsonObject.getString("uploader_id") else ""
}
}
}
var duration = ""
@ -791,6 +791,9 @@ class InfoUtil(private val context: Context) {
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("uploader_id")) jsonObject.getString("uploader_id") else ""
}
}
}
@ -837,7 +840,6 @@ class InfoUtil(private val context: Context) {
fun getSearchSuggestions(query: String): ArrayList<String> {
val url = "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&client=firefox&q=$query"
// invidousURL + "search/suggestions?q=" + query
val res = genericArrayRequest(url)
Log.e("aa", res.toString())
if (res.length() == 0) return ArrayList()
@ -1086,8 +1088,10 @@ class InfoUtil(private val context: Context) {
if(downloadItem.title.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata", "title", ".+", downloadItem.title))
}
request.addOption("--parse-metadata", "%(channel,uploader,creator,artist|null)s:%(uploader)s")
request.addOption("--parse-metadata", "%(uploader)s:%(album_artist)s")
if (downloadItem.author.isNotBlank()){
request.addOption("--parse-metadata", "%(artist,uploader,creator,channel|null)s:%(uploader)s")
request.addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author))
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
}
@ -1104,9 +1108,13 @@ class InfoUtil(private val context: Context) {
request.addOption("--force-keyframes-at-cuts")
}
}
filenameTemplate += " %(section_title|)s "
if (filenameTemplate.isBlank()){
filenameTemplate = "%(section_title& {}|)s %(title)s"
}else{
filenameTemplate += " %(section_title& {}|)s "
}
if (downloadItem.downloadSections.split(";").size > 1){
filenameTemplate += "%(autonumber)s"
filenameTemplate = "%(autonumber)d. %(section_start& {}|)s $filenameTemplate"
}
request.addOption("--output-na-placeholder", " ")
}
@ -1145,7 +1153,7 @@ class InfoUtil(private val context: Context) {
val preferredAudioCodec = sharedPreferences.getString("audio_codec", "")!!
val aCodecPrefIndex = context.getStringArray(R.array.audio_codec_values).indexOf(preferredAudioCodec)
val aCodecPref = context.getStringArray(R.array.audio_codec_values_ytdlp)[aCodecPrefIndex]
val aCodecPref = runCatching { context.getStringArray(R.array.audio_codec_values_ytdlp)[aCodecPrefIndex] }.getOrElse { "" }
when(type){
DownloadViewModel.Type.audio -> {
@ -1162,14 +1170,14 @@ class InfoUtil(private val context: Context) {
val ext = downloadItem.container
if (audioQualityId.isNotBlank()) {
if (audioQualityId.contains("-") && !downloadItem.format.lang.isNullOrBlank() && downloadItem.format.lang != "None"){
audioQualityId = "ba[format_id~='^(${audioQualityId.split("-")[0]})'][language=${downloadItem.format.lang}]/ba/b"
audioQualityId = "ba[format_id~='^(${audioQualityId.split("-")[0]})'][language^=${downloadItem.format.lang}]/ba/b"
}
request.addOption("-f", audioQualityId)
}else{
//enters here if generic or quick downloaded with ba format
val preferredLanguage = sharedPreferences.getString("audio_language","")!!
if (preferredLanguage.isNotBlank()){
request.addOption("-f", "ba[language=$preferredLanguage]/ba/b")
request.addOption("-f", "ba[language^=$preferredLanguage]/ba/b")
}
}
request.addOption("-x")
@ -1272,15 +1280,17 @@ class InfoUtil(private val context: Context) {
val defaultFormats = context.resources.getStringArray(R.array.video_formats_values)
val usingGenericFormat = defaultFormats.contains(videoF) || downloadItem.allFormats.isEmpty() || downloadItem.allFormats == getGenericVideoFormats(context.resources)
if (!usingGenericFormat){
if (audioF.isNotBlank()) audioF = "+$audioF"
f.append("$videoF$audioF/")
if (preferredAudioCodec.isNotBlank()){
f.append("$videoF+$aCodecPref/")
}
f.append("$videoF/bv/b")
// with audio removed
if (audioF.isBlank()){
f.append("$videoF/bv/b")
}else{
//with audio
audioF = "+$audioF"
f.append("$videoF$audioF/$videoF+ba/$videoF/b")
if (audioF.count("+") > 1){
request.addOption("--audio-multistreams")
if (audioF.count("+") > 1){
request.addOption("--audio-multistreams")
}
}
}else{
if (videoF == context.resources.getString(R.string.best_quality) || videoF == "best") {
@ -1306,8 +1316,7 @@ class InfoUtil(private val context: Context) {
.filter { it.isNotBlank() }
.ifEmpty {
val list = mutableListOf<String>()
if (preferredAudioCodec.isNotBlank()) list.add("ba[acodec~='^($preferredAudioCodec)']")
if (preferredAudioLanguage.isNotEmpty()) list.add("ba[language=$preferredAudioLanguage]")
if (preferredAudioLanguage.isNotEmpty()) list.add("ba[language^=$preferredAudioLanguage]")
list.add(audioF)
list
}.apply {
@ -1333,7 +1342,7 @@ class InfoUtil(private val context: Context) {
if (!downloadItem.videoPreferences.removeAudio){
//build format with audio with preferred language
if (preferredAudioLanguage.isNotBlank()){
val al = "$v+ba[language=$preferredAudioLanguage]/"
val al = "$v+ba[language^=$preferredAudioLanguage]/"
if (!f.contains(al)) f.append(al)
}
//build format with best audio
@ -1348,12 +1357,14 @@ class InfoUtil(private val context: Context) {
f.append("b")
}
val formatSorting = StringBuilder("+hasaud")
if (vCodecPref.isNotBlank()) formatSorting.append(",vcodec:$vCodecPref")
if (aCodecPref.isNotBlank()) formatSorting.append(",acodec:$aCodecPref")
if (cont.isNotBlank()) formatSorting.append(",ext:$cont")
request.addOption("-S", formatSorting.toString())
StringBuilder().apply {
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
if (cont.isNotBlank()) append(",ext:$cont")
if (this.isNotBlank()){
request.addOption("-S", "+hasaud$this")
}
}
}

View file

@ -896,7 +896,7 @@ object UiUtil {
)
}
private fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){
fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.subtitle_languages))
val view = context.layoutInflater.inflate(R.layout.subtitle_dialog, null)
@ -966,7 +966,7 @@ object UiUtil {
}
private fun copyToClipboard(text: String, activity: Activity){
fun copyToClipboard(text: String, activity: Activity){
val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(text, text)
clipboard.setPrimaryClip(clip)
@ -1454,6 +1454,21 @@ object UiUtil {
errDialog.show()
}
fun showErrorDialog(context: Context, it: String){
val title = context.getString(R.string.errored)
val errDialog = MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(it)
.setPositiveButton(android.R.string.copy) { d:DialogInterface?, _:Int ->
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(it)
d?.dismiss()
}
errDialog.show()
}
@SuppressLint("SetTextI18n")
fun handleDownloadsResponse(context: Activity, lifecycleScope: CoroutineScope, supportFragmentManager: FragmentManager, it: DownloadViewModel.DownloadsUiState, downloadViewModel: DownloadViewModel, historyViewModel: HistoryViewModel){
val downloadAnywayAction = it.actions?.first { it.second == DownloadViewModel.DownloadsAction.DOWNLOAD_ANYWAY}
@ -1732,7 +1747,7 @@ object UiUtil {
CoroutineScope(Dispatchers.IO).launch {
val chipGroup = view.findViewById<ChipGroup>(R.id.filename_suggested_chipgroup)
val chips = mutableListOf<Chip>()
val instances = InfoUtil(context).getPipedInstances()
val instances = InfoUtil(context).getPipedInstances().ifEmpty { return@launch }
instances.forEach { s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
tmp.text = s

View file

@ -1,6 +1,7 @@
package com.deniscerri.ytdlnis.util
import android.annotation.SuppressLint
import android.app.Activity
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
@ -13,8 +14,11 @@ import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.text.format.DateFormat
import android.text.method.LinkMovementMethod
import android.util.Log
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
@ -22,6 +26,10 @@ import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.GithubRelease
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
@ -36,6 +44,8 @@ import java.io.File
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.text.SimpleDateFormat
import java.util.Locale
class UpdateUtil(var context: Context) {
@ -152,6 +162,75 @@ class UpdateUtil(var context: Context) {
}
}
fun showChangeLog(activity: Activity){
runCatching {
val scrollView = ScrollView(activity)
val linearLayout = LinearLayout(activity)
linearLayout.orientation = LinearLayout.VERTICAL
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParams.setMargins(20, 20, 20, 0)
scrollView.layoutParams = layoutParams
scrollView.addView(linearLayout)
getGithubReleases().forEach {
(activity.layoutInflater.inflate(R.layout.changelog_item, null) as MaterialCardView).apply {
this.layoutParams = layoutParams
findViewById<TextView>(R.id.version).text = it.tag_name
findViewById<TextView>(R.id.date).text = SimpleDateFormat(
DateFormat.getBestDateTimePattern(
Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time)
val mdText = findViewById<TextView>(R.id.content)
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(context, browserIntent, Bundle())
}
}
}).build()
mw.setMarkdown(mdText, it.body)
val assetGroup = findViewById<ChipGroup>(R.id.assets)
it.assets.forEachIndexed { idx, c ->
val tmp = activity.layoutInflater.inflate(R.layout.suggestion_chip, assetGroup, false) as Chip
tmp.text = c.name
tmp.id = idx
tmp.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url))
startActivity(context, browserIntent, Bundle())
}
assetGroup!!.addView(tmp)
}
linearLayout.addView(this)
}
}
val changeLogDialog = MaterialAlertDialogBuilder(context)
.setTitle(activity.getString(R.string.changelog))
.setView(scrollView)
.setIcon(R.drawable.ic_chapters)
.setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
Handler(Looper.getMainLooper()).post {
changeLogDialog.show()
}
}.onFailure {
if (it.message != null){
Handler(Looper.getMainLooper()).post {
UiUtil.showErrorDialog(context, it.message!!)
}
}
}
}
private fun getGithubReleases(): List<GithubRelease> {
val url = "https://api.github.com/repos/deniscerri/ytdlnis/releases"
val conn: HttpURLConnection
@ -188,15 +267,8 @@ class UpdateUtil(var context: Context) {
"master" to YoutubeDL.UpdateChannel._MASTER,
)
val channel = sharedPreferences.getString("ytdlp_source", "nightly")
try {
YoutubeDL.getInstance().updateYoutubeDL(context, channelMap[channel] ?: YoutubeDL.UpdateChannel._NIGHTLY).apply {
updatingYTDL = false
}
}catch (e: Exception){
e.printStackTrace()
YoutubeDL.getInstance().updateYoutubeDL(context, channelMap[channel] ?: YoutubeDL.UpdateChannel._NIGHTLY).apply {
updatingYTDL = false
null
}
}

View file

@ -55,7 +55,7 @@ class UpdatePlaylistFormatsWorker(
runBlocking {
resDao.update(r)
dao.delete(d.id)
dao.update(d)
}
}

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="M19.17,12l-4.58,-4.59L16,6l6,6l-3.59,3.59L17,14.17L19.17,12zM1.39,4.22l4.19,4.19L2,12l6,6l1.41,-1.41L4.83,12L7,9.83l12.78,12.78l1.41,-1.41L2.81,2.81L1.39,4.22z"/>
</vector>

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_margin="10dp"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/version"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:fontFamily="monospace"
android:text="1.6.9"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:text="1.1.1970"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/version" />
<TextView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginBottom="10dp"
android:text="## WHATS NEW"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/date" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/assets"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:chipSpacingVertical="-7dp"
app:chipSpacingHorizontal="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/content"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -1112,6 +1112,7 @@
<!--below is for preferred audio language, not related to app's language-->
<string-array name="language_codes">
<item></item>
<item>af</item> <!-- Afrikaans -->
<item>sq</item> <!-- Albanian -->
<item>am</item> <!-- Amharic -->
@ -1222,6 +1223,7 @@
</string-array>
<string-array name="language_names">
<item>@string/defaultValue</item>
<item>Afrikaans</item>
<item>Albanian</item>
<item>Amharic</item>

View file

@ -327,4 +327,7 @@
<string name="buffer_size">Buffer Size</string>
<string name="buffer_size_summary">Size of download buffer, e.g. 1024 or 16K (default is 1024)</string>
<string name="preferred_audio_language">Preferred Audio Language</string>
<string name="changelog">Changelog</string>
<string name="source">Source</string>
<string name="app">App</string>
</resources>

View file

@ -61,7 +61,7 @@
app:summary="@string/save_subs_desc"
app:title="@string/save_subs" />
<EditTextPreference
<Preference
android:icon="@drawable/baseline_subject_24"
app:key="subs_lang"
app:useSimpleSummaryProvider="true"

View file

@ -100,10 +100,6 @@
android:data="https://github.com/deniscerri/ytdlnis" />
</Preference>
<Preference
app:icon="@drawable/ic_info"
app:key="version"
app:title="@string/version"/>
</PreferenceCategory>

View file

@ -2,86 +2,104 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:icon="@drawable/ic_update"
app:key="update_ytdl"
app:summary="@string/ytdl_update_hint"
app:title="@string/update_ytdl" />
<PreferenceCategory android:title="@string/source">
<Preference
app:icon="@drawable/ic_info"
app:key="ytdl-version"
app:title="@string/ytdl_version"/>
<ListPreference
android:defaultValue="nightly"
android:entries="@array/ytdlp_source"
android:entryValues="@array/ytdlp_source_values"
android:icon="@drawable/baseline_source_24"
app:key="ytdlp_source"
app:useSimpleSummaryProvider="true"
app:title="@string/ytdl_source" />
<Preference
app:icon="@drawable/ic_update"
app:key="update_ytdl"
app:summary="@string/ytdl_update_hint"
app:title="@string/update_ytdl" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_update"
android:key="auto_update_ytdlp"
app:title="@string/auto_update_ytdlp" />
<ListPreference
android:defaultValue="yt-dlp"
android:entries="@array/formats_source"
android:entryValues="@array/formats_source_values"
android:icon="@drawable/baseline_manage_search_24"
app:key="formats_source"
app:useSimpleSummaryProvider="true"
app:title="@string/format_source" />
<ListPreference
android:defaultValue="filesize"
android:entries="@array/format_ordering"
android:entryValues="@array/format_ordering_values"
android:icon="@drawable/baseline_sort_24"
app:key="format_order"
app:useSimpleSummaryProvider="true"
app:title="@string/format_order" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_format"
android:key="update_formats"
app:summary="@string/update_formats_summary"
app:title="@string/update_formats" />
<Preference
app:key="piped_instance"
app:defaultValue=""
android:summary="@string/piped_instance_summary"
app:title="@string/piped_instance" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/app">
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_update_app"
android:key="update_app"
app:summary="@string/update_app_summary"
app:title="@string/update_app" />
<EditTextPreference
app:isPreferenceVisible="false"
app:key="skip_updates"
app:defaultValue="" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_update_app"
android:key="update_beta"
app:summary="@string/update_app_beta_summary"
app:title="@string/update_app_beta" />
<Preference
app:icon="@drawable/ic_chapters"
app:key="changelog"
app:title="@string/changelog" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_update"
android:key="auto_update_ytdlp"
app:title="@string/auto_update_ytdlp" />
<Preference
app:icon="@drawable/ic_info"
app:key="ytdl-version"
app:title="@string/ytdl_version"/>
<ListPreference
android:defaultValue="nightly"
android:entries="@array/ytdlp_source"
android:entryValues="@array/ytdlp_source_values"
android:icon="@drawable/baseline_source_24"
app:key="ytdlp_source"
app:useSimpleSummaryProvider="true"
app:title="@string/ytdl_source" />
<Preference
app:icon="@drawable/ic_info"
app:key="version"
app:title="@string/version"/>
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_format"
android:key="update_formats"
app:summary="@string/update_formats_summary"
app:title="@string/update_formats" />
<ListPreference
android:defaultValue="yt-dlp"
android:entries="@array/formats_source"
android:entryValues="@array/formats_source_values"
android:icon="@drawable/baseline_manage_search_24"
app:key="formats_source"
app:useSimpleSummaryProvider="true"
app:title="@string/format_source" />
<ListPreference
android:defaultValue="filesize"
android:entries="@array/format_ordering"
android:entryValues="@array/format_ordering_values"
android:icon="@drawable/baseline_sort_24"
app:key="format_order"
app:useSimpleSummaryProvider="true"
app:title="@string/format_order" />
<Preference
app:key="piped_instance"
app:defaultValue=""
android:summary="@string/piped_instance_summary"
app:title="@string/piped_instance" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_update_app"
android:key="update_app"
app:summary="@string/update_app_summary"
app:title="@string/update_app" />
<EditTextPreference
app:isPreferenceVisible="false"
app:key="skip_updates"
app:defaultValue="" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_update_app"
android:key="update_beta"
app:summary="@string/update_app_beta_summary"
app:title="@string/update_app_beta" />
</PreferenceCategory>
</PreferenceScreen>