From 880abd503e6988aeaecc55f36866a33d00846b16 Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sat, 13 May 2023 22:17:43 +0200 Subject: [PATCH] added multiple pages to settings & more... Dismiss download multiple sheet when all items are dismissed Add backup for history, queued downloads, cancelled downloads, cookies, shortcuts, templates Add long click format to show details in audio video tab removed check icon from checked state in material cards Implement format fetching on playlist from invidious Invert selected items in playlist bottom sheet Make filename templates for both audio and video and add them as a chip on both tabs Fixed errors when there is no internet and u ask for the latest version of the app Fixed "default" format error --- app/build.gradle | 3 +- app/proguard-rules.pro | 1 - .../main/java/com/deniscerri/ytdlnis/App.kt | 10 +- .../com/deniscerri/ytdlnis/MainActivity.kt | 1 - .../ytdlnis/adapter/PlaylistAdapter.kt | 11 + .../ytdlnis/database/dao/DownloadDao.kt | 21 +- .../ytdlnis/database/dao/HistoryDao.kt | 3 + .../ytdlnis/database/dao/ResultDao.kt | 5 +- .../database/models/AudioPreferences.kt | 2 +- .../database/models/VideoPreferences.kt | 2 +- .../database/repository/DownloadRepository.kt | 23 +- .../database/repository/HistoryRepository.kt | 4 + .../database/repository/ResultRepository.kt | 3 +- .../database/viewmodel/DownloadViewModel.kt | 35 +- .../database/viewmodel/HistoryViewModel.kt | 5 +- .../database/viewmodel/ResultViewModel.kt | 3 +- .../downloadcard/CutVideoBottomSheetDialog.kt | 18 +- .../ui/downloadcard/DownloadAudioFragment.kt | 55 +- .../downloadcard/DownloadBottomSheetDialog.kt | 4 +- .../DownloadMultipleBottomSheetDialog.kt | 19 +- .../ui/downloadcard/DownloadVideoFragment.kt | 46 +- .../FormatSelectionBottomSheetDialog.kt | 99 +--- .../SelectPlaylistItemsBottomSheetDialog.kt | 27 +- .../ytdlnis/ui/more/CookiesActivity.kt | 2 + .../settings/AppearanceSettingsFragment.kt | 158 ++++++ .../ui/more/settings/BaseSettingsFragment.kt | 13 + .../more/settings/DownloadSettingsFragment.kt | 67 +++ .../more/settings/FolderSettingsFragment.kt | 179 ++++++ .../ui/more/settings/MainSettingsFragment.kt | 501 +++++++++++++++++ .../settings/ProcessingSettingsFragment.kt | 20 + .../ui/more/settings/SettingsActivity.kt | 52 +- .../ui/more/settings/SettingsFragment.kt | 522 ------------------ .../more/settings/UpdateSettingsFragment.kt | 89 +++ .../com/deniscerri/ytdlnis/util/InfoUtil.kt | 91 +-- .../com/deniscerri/ytdlnis/util/UiUtil.kt | 101 ++++ .../com/deniscerri/ytdlnis/util/UpdateUtil.kt | 2 +- .../deniscerri/ytdlnis/work/DownloadWorker.kt | 10 +- .../main/res/layout-sw650dp/activity_main.xml | 2 +- .../res/layout-sw800dp-land/activity_main.xml | 2 +- .../res/layout/activity_download_log_list.xml | 1 + app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/layout/activity_settings.xml | 46 +- .../main/res/layout/download_bottom_sheet.xml | 271 +++++---- app/src/main/res/layout/download_card.xml | 1 + .../res/layout/fragment_download_audio.xml | 24 +- .../res/layout/fragment_download_video.xml | 58 +- app/src/main/res/layout/fragment_history.xml | 1 + app/src/main/res/layout/history_card.xml | 1 + app/src/main/res/layout/result_card.xml | 1 + .../main/res/layout/select_playlist_items.xml | 10 +- app/src/main/res/layout/textinput.xml | 1 - .../main/res/menu/select_playlist_menu.xml | 10 + app/src/main/res/navigation/nav_graph.xml | 25 +- app/src/main/res/navigation/settings_nav.xml | 47 ++ app/src/main/res/values/arrays.xml | 39 +- app/src/main/res/values/strings.xml | 9 + .../main/res/xml/appearance_preferences.xml | 39 ++ .../main/res/xml/downloading_preferences.xml | 140 +++++ app/src/main/res/xml/folders_preference.xml | 43 ++ .../main/res/xml/processing_preferences.xml | 127 +++++ app/src/main/res/xml/root_preferences.xml | 407 +------------- app/src/main/res/xml/updating_preferences.xml | 52 ++ 62 files changed, 2242 insertions(+), 1324 deletions(-) create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/AppearanceSettingsFragment.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt delete mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsFragment.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt create mode 100644 app/src/main/res/menu/select_playlist_menu.xml create mode 100644 app/src/main/res/navigation/settings_nav.xml create mode 100644 app/src/main/res/xml/appearance_preferences.xml create mode 100644 app/src/main/res/xml/downloading_preferences.xml create mode 100644 app/src/main/res/xml/folders_preference.xml create mode 100644 app/src/main/res/xml/processing_preferences.xml create mode 100644 app/src/main/res/xml/updating_preferences.xml diff --git a/app/build.gradle b/app/build.gradle index 79d24c9b..2c89c8e8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -58,7 +58,7 @@ android { buildTypes { release { - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' debuggable false signingConfig signingConfigs.debug @@ -178,4 +178,5 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0' implementation "com.google.android.exoplayer:exoplayer:2.18.6" implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4' + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 79905da3..aa44b74a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -61,7 +61,6 @@ public (android.content.Context,androidx.work.WorkerParameters); } --dontobfuscate -dontwarn com.google.android.exoplayer2.** # Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken diff --git a/app/src/main/java/com/deniscerri/ytdlnis/App.kt b/app/src/main/java/com/deniscerri/ytdlnis/App.kt index 21f1eac4..79049a63 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/App.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/App.kt @@ -29,15 +29,8 @@ class App : Application() { override fun onCreate() { super.onCreate() + instance = this createNotificationChannels() - PreferenceManager.setDefaultValues( - this, - "root_preferences", - MODE_PRIVATE, - R.xml.root_preferences, - false - ) - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) applicationScope = CoroutineScope(SupervisorJob()) @@ -80,5 +73,6 @@ class App : Application() { companion object { private const val TAG = "App" private lateinit var applicationScope: CoroutineScope + lateinit var instance: App } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index 97a8a83e..2af3cfdc 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -123,7 +123,6 @@ class MainActivity : BaseActivity() { startActivity(intent) } R.id.moreFragment -> { - //navController.navigate(R.id.settingsFragment) val intent = Intent(context, SettingsActivity::class.java) startActivity(intent) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt index bb7eb808..c403270d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt @@ -17,6 +17,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.ResultItem import com.squareup.picasso.Picasso import java.util.* @@ -119,6 +120,16 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi } } + fun invertSelected(items: List?){ + val invertedList = mutableListOf() + items?.forEach { + if (!checkedItems.contains(it!!.url)) invertedList.add(it.url) + } + checkedItems.clear() + checkedItems.addAll(invertedList) + notifyDataSetChanged() + } + fun checkRange(start: Int, end: Int){ checkedItems.clear() if (start == end ){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt index b2cfc74e..aa74ab16 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt @@ -3,18 +3,19 @@ package com.deniscerri.ytdlnis.database.dao import androidx.lifecycle.LiveData import androidx.room.* import com.deniscerri.ytdlnis.database.models.DownloadItem +import kotlinx.coroutines.flow.Flow @Dao interface DownloadDao { @Query("SELECT * FROM downloads ORDER BY status") - fun getAllDownloads() : LiveData> + fun getAllDownloads() : Flow> @Query("SELECT * FROM downloads WHERE status='Active'") - fun getActiveDownloads() : LiveData> + fun getActiveDownloads() : Flow> @Query("SELECT COUNT(*) FROM downloads WHERE status='Active'") - fun getActiveDownloadsCount() : LiveData + fun getActiveDownloadsCount() : Flow @Query("SELECT * FROM downloads WHERE status='Active'") fun getActiveDownloadsList() : List @@ -23,16 +24,22 @@ interface DownloadDao { fun getActiveAndQueuedDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") - fun getQueuedDownloads() : LiveData> + fun getQueuedDownloads() : Flow> + + @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") + fun getQueuedDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC") - fun getCancelledDownloads() : LiveData> + fun getCancelledDownloads() : Flow> + + @Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC") + fun getCancelledDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC") - fun getErroredDownloads() : LiveData> + fun getErroredDownloads() : Flow> @Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC") - fun getProcessingDownloads() : LiveData> + fun getProcessingDownloads() : Flow> @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") fun getDownloadById(id: Long) : DownloadItem diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt index b3b6b81e..3eff7b22 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt @@ -29,6 +29,9 @@ interface HistoryDao { @Query("SELECT * FROM history") fun getAllHistory() : LiveData> + @Query("SELECT * FROM history") + fun getAllHistoryList() : List + @Query("SELECT * FROM history WHERE id=:id LIMIT 1") suspend fun getHistoryItem(id: Int) : HistoryItem diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt index 66b4a861..f0abe2f0 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt @@ -3,14 +3,15 @@ package com.deniscerri.ytdlnis.database.dao import androidx.lifecycle.LiveData import androidx.room.* import com.deniscerri.ytdlnis.database.models.ResultItem +import kotlinx.coroutines.flow.Flow @Dao interface ResultDao { @Query("SELECT * FROM results") - fun getResults() : LiveData> + fun getResults() : Flow> @Query("SELECT COUNT(id) FROM results") - fun getCount() : LiveData + fun getCount() : Flow @Query("SELECT COUNT(id) FROM results") fun getCountInt() :Int diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt index b0fe8a49..db6f1697 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt @@ -3,5 +3,5 @@ package com.deniscerri.ytdlnis.database.models data class AudioPreferences( var embedThumb: Boolean = true, var splitByChapters: Boolean = false, - var sponsorBlockFilters: ArrayList + var sponsorBlockFilters: ArrayList = arrayListOf() ) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt index 29d1601f..8c8e3b2b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt @@ -4,7 +4,7 @@ data class VideoPreferences ( var embedSubs: Boolean = true, var addChapters: Boolean = true, var splitByChapters: Boolean = false, - var sponsorBlockFilters: ArrayList, + var sponsorBlockFilters: ArrayList = arrayListOf(), var writeSubs: Boolean = false, var subsLanguages: String = "en.*,.*-orig", var removeAudio: Boolean = false diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt index fd43898f..654b83dc 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt @@ -5,15 +5,16 @@ import com.deniscerri.ytdlnis.database.Converters import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.ResultItem +import kotlinx.coroutines.flow.Flow class DownloadRepository(private val downloadDao: DownloadDao) { - val allDownloads : LiveData> = downloadDao.getAllDownloads() - val activeDownloads : LiveData> = downloadDao.getActiveDownloads() - val activeDownloadsCount : LiveData = downloadDao.getActiveDownloadsCount() - val queuedDownloads : LiveData> = downloadDao.getQueuedDownloads() - val cancelledDownloads : LiveData> = downloadDao.getCancelledDownloads() - val erroredDownloads : LiveData> = downloadDao.getErroredDownloads() - val processingDownloads : LiveData> = downloadDao.getProcessingDownloads() + val allDownloads : Flow> = downloadDao.getAllDownloads() + val activeDownloads : Flow> = downloadDao.getActiveDownloads() + val activeDownloadsCount : Flow = downloadDao.getActiveDownloadsCount() + val queuedDownloads : Flow> = downloadDao.getQueuedDownloads() + val cancelledDownloads : Flow> = downloadDao.getCancelledDownloads() + val erroredDownloads : Flow> = downloadDao.getErroredDownloads() + val processingDownloads : Flow> = downloadDao.getProcessingDownloads() enum class Status { Active, Queued, Error, Processing, Cancelled @@ -53,6 +54,14 @@ class DownloadRepository(private val downloadDao: DownloadDao) { return downloadDao.getActiveAndQueuedDownloadsList() } + fun getQueuedDownloads() : List { + return downloadDao.getQueuedDownloadsList(); + } + + fun getCancelledDownloads() : List { + return downloadDao.getCancelledDownloadsList() + } + suspend fun deleteCancelled(){ downloadDao.deleteCancelled() } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt index 18726708..b4370ffa 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt @@ -19,6 +19,10 @@ class HistoryRepository(private val historyDao: HistoryDao) { return historyDao.getHistoryItem(id) } + fun getAll() : List { + return historyDao.getAllHistoryList() + } + fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: HistorySort) : List { return when(sortType){ HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString()) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt index 21ba7047..96336ba4 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt @@ -9,10 +9,11 @@ import com.deniscerri.ytdlnis.database.dao.ResultDao import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.util.InfoUtil +import kotlinx.coroutines.flow.Flow class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) { private val tag: String = "ResultRepository" - val allResults : LiveData> = resultDao.getResults() + val allResults : Flow> = resultDao.getResults() var itemCount = MutableLiveData(-1) suspend fun insert(it: ResultItem){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt index 7c8b5811..14492c2d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt @@ -8,6 +8,7 @@ import android.net.ConnectivityManager import android.widget.Toast import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import androidx.work.* @@ -52,13 +53,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) commandTemplateDao = DBManager.getInstance(application).commandTemplateDao - allDownloads = repository.allDownloads - queuedDownloads = repository.queuedDownloads - activeDownloads = repository.activeDownloads - activeDownloadsCount = repository.activeDownloadsCount - processingDownloads = repository.processingDownloads - cancelledDownloads = repository.cancelledDownloads - erroredDownloads = repository.erroredDownloads + allDownloads = repository.allDownloads.asLiveData() + queuedDownloads = repository.queuedDownloads.asLiveData() + activeDownloads = repository.activeDownloads.asLiveData() + activeDownloadsCount = repository.activeDownloadsCount.asLiveData() + processingDownloads = repository.processingDownloads.asLiveData() + cancelledDownloads = repository.cancelledDownloads.asLiveData() + erroredDownloads = repository.erroredDownloads.asLiveData() videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString() formatIDPreference = sharedPreferences.getString("format_id", "").toString() @@ -114,7 +115,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val addChapters = sharedPreferences.getBoolean("add_chapters", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) - val customFileNameTemplate = sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") + val customFileNameTemplate = when(type) { + Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s") + Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") + else -> "" + } val downloadPath = when(type){ Type.audio -> sharedPreferences.getString("music_path", getApplication().resources.getString(R.string.music_path)) @@ -205,7 +210,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val addChapters = sharedPreferences.getBoolean("add_chapters", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) - val customFileNameTemplate = sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") + val customFileNameTemplate = when(historyItem.type) { + Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s") + Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") + else -> "" + } val downloadPath = when(historyItem.type){ Type.audio -> sharedPreferences.getString("music_path", getApplication().resources.getString(R.string.music_path)) @@ -345,6 +354,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel repository.cancelQueued() } + fun getQueued() : List { + return repository.getQueuedDownloads() + } + + fun getCancelled() : List { + return repository.getCancelledDownloads() + } + private fun cloneFormat(item: Format) : Format { val string = Gson().toJson(item, Format::class.java) return Gson().fromJson(string, Format::class.java) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt index 1661de55..093c217c 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt @@ -48,7 +48,6 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) return _items } - fun setSorting(sort: HistorySortType){ if (sortType.value != sort){ sortOrder.value = HistorySort.DESC @@ -76,6 +75,10 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) _items.postValue(repository.getFiltered(query, format, site, sortType, sort)) } + fun getAll() : List { + return repository.getAll() + } + fun insert(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO){ repository.insert(item) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt index 313b1131..7d833175 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt @@ -7,6 +7,7 @@ import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.App @@ -35,7 +36,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application) val commandDao = DBManager.getInstance(application).commandTemplateDao repository = ResultRepository(dao, commandDao, getApplication().applicationContext) searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao) - items = repository.allResults + items = repository.allResults.asLiveData() loadingItems.postValue(false) itemCount = repository.itemCount sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt index 8d77d8c4..2fc8527d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt @@ -21,12 +21,14 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil +import com.google.android.exoplayer2.C import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.DefaultRenderersFactory import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem.fromUri import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.mediacodec.MediaCodecInfo import com.google.android.exoplayer2.mediacodec.MediaCodecSelector import com.google.android.exoplayer2.source.DefaultMediaSourceFactory @@ -131,9 +133,18 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list val trackSelector = DefaultTrackSelector(requireContext()) val loadControl = DefaultLoadControl() - player = ExoPlayer.Builder(requireContext(), renderersFactory) + player = ExoPlayer.Builder(requireContext()) + .setUsePlatformDiagnostics(false) .setTrackSelector(trackSelector) .setLoadControl(loadControl) + .setHandleAudioBecomingNoisy(true) + .setAudioAttributes( + AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) + .build() + , + true) .build() val frame = view.findViewById(R.id.frame_layout) val videoView = view.findViewById(R.id.video_view) @@ -207,9 +218,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list progress.visibility = View.GONE populateSuggestedChapters() - player.prepare().apply { - player.play() - } + player.prepare() + player.play() }catch (e: Exception){ progress.visibility = View.GONE frame.visibility = View.GONE diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt index dcf9feae..8106d306 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt @@ -3,18 +3,25 @@ package com.deniscerri.ytdlnis.ui.downloadcard import android.app.Activity import android.content.DialogInterface import android.content.Intent +import android.net.Uri import android.os.Bundle import android.text.Editable import android.text.TextWatcher +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView +import android.widget.EditText import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -142,10 +149,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr override fun onFormatClick(allFormats: List>, item: List) { downloadItem.format = item.first() uiUtil.populateFormatCard(formatCard, item.first()) - if (containers.contains(item.first().container)){ - downloadItem.format.container = item.first().container - containerAutoCompleteTextView.setText(item.first().container, false) - } lifecycleScope.launch { withContext(Dispatchers.IO){ resultItem.formats.removeAll(formats.toSet()) @@ -162,6 +165,10 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr bottomSheet.show(parentFragmentManager, "formatSheet") } } + formatCard.setOnLongClickListener { + uiUtil.showFormatDetails(downloadItem.format, requireActivity()) + true + } container?.isEnabled = true @@ -173,12 +180,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr ) ) - downloadItem.format.container = containerPreference!! - containerAutoCompleteTextView.setText(downloadItem.format.container, false) + downloadItem.format.container = if (containerPreference == getString(R.string.defaultValue)) "" else containerPreference!! + containerAutoCompleteTextView.setText(downloadItem.format.container.ifEmpty { getString(R.string.defaultValue) }, false) (container!!.editText as AutoCompleteTextView?)!!.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long -> downloadItem.format.container = containers[index] + if (containers[index] == getString(R.string.defaultValue)) downloadItem.format.container = "" } val embedThumb = view.findViewById(R.id.embed_thumb) @@ -199,6 +207,41 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr downloadItem.audioPreferences.splitByChapters = splitByChapters.isChecked } + val filenameTemplate = view.findViewById(R.id.filename_template) + filenameTemplate.setOnClickListener { + val builder = MaterialAlertDialogBuilder(requireContext()) + builder.setTitle(getString(R.string.file_name_template)) + val inputLayout = layoutInflater.inflate(R.layout.textinput, null) + val editText = inputLayout.findViewById(R.id.url_edittext) + inputLayout.findViewById(R.id.url_textinput).hint = getString(R.string.file_name_template) + editText.setText(downloadItem.customFileNameTemplate) + editText.setSelection(editText.text.length) + builder.setView(inputLayout) + builder.setPositiveButton( + getString(R.string.ok) + ) { dialog: DialogInterface?, which: Int -> + downloadItem.customFileNameTemplate = editText.text.toString() + } + + // handle the negative button of the alert dialog + builder.setNegativeButton( + getString(R.string.cancel) + ) { dialog: DialogInterface?, which: Int -> } + + val dialog = builder.create() + editText.doOnTextChanged { text, start, before, count -> + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty() + } + dialog.show() + val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager + editText!!.postDelayed({ + editText.requestFocus() + imm.showSoftInput(editText, 0) + }, 300) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty() + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START + } + val sponsorblock = view.findViewById(R.id.sponsorblock_filters) sponsorblock!!.setOnClickListener { val builder = MaterialAlertDialogBuilder(requireContext()) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt index 553214d3..5e7e2590 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt @@ -71,7 +71,9 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val behavior = BottomSheetBehavior.from(view.parent as View) val displayMetrics = DisplayMetrics() requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) - behavior.state = BottomSheetBehavior.STATE_EXPANDED + if(resources.getBoolean(R.bool.isTablet)){ + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index 85f4162a..81ec5f0d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -79,7 +79,9 @@ class DownloadMultipleBottomSheetDialog(private var results: List, behavior = BottomSheetBehavior.from(view.parent as View) val displayMetrics = DisplayMetrics() requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) - behavior.state = BottomSheetBehavior.STATE_EXPANDED + if(resources.getBoolean(R.bool.isTablet)){ + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } } listAdapter = @@ -409,11 +411,16 @@ class DownloadMultipleBottomSheetDialog(private var results: List, val deletedItem = items[position] items.remove(deletedItem) listAdapter.submitList(items.toList()) - Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG) - .setAction(getString(R.string.undo)) { - items.add(position, deletedItem) - listAdapter.submitList(items.toList()) - }.show() + if (items.isNotEmpty()){ + Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG) + .setAction(getString(R.string.undo)) { + items.add(position, deletedItem) + listAdapter.submitList(items.toList()) + }.show() + }else{ + dismiss() + } + } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt index fa6e47bb..5cbfeb8a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt @@ -148,11 +148,6 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr val listener = object : OnFormatClickListener { override fun onFormatClick(allFormats: List>, item: List) { downloadItem.format = item.first() - - if (containers.contains(item.first().container)){ - downloadItem.format.container = item.first().container - containerAutoCompleteTextView.setText(item.first().container, false) - } lifecycleScope.launch { withContext(Dispatchers.IO){ resultItem.formats.removeAll(formats.toSet()) @@ -171,6 +166,11 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr } } + formatCard.setOnLongClickListener { + uiUtil.showFormatDetails(downloadItem.format, requireActivity()) + true + } + container?.isEnabled = true containerAutoCompleteTextView?.setAdapter( ArrayAdapter( @@ -187,6 +187,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr (container!!.editText as AutoCompleteTextView?)!!.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long -> downloadItem.format.container = containers[index] + if (containers[index] == getString(R.string.defaultValue)) downloadItem.format.container = "" } @@ -313,6 +314,41 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr cut.isEnabled = false } + val filenameTemplate = view.findViewById(R.id.filename_template) + filenameTemplate.setOnClickListener { + val builder = MaterialAlertDialogBuilder(requireContext()) + builder.setTitle(getString(R.string.file_name_template)) + val inputLayout = layoutInflater.inflate(R.layout.textinput, null) + val editText = inputLayout.findViewById(R.id.url_edittext) + inputLayout.findViewById(R.id.url_textinput).hint = getString(R.string.file_name_template) + editText.setText(downloadItem.customFileNameTemplate) + editText.setSelection(editText.text.length) + builder.setView(inputLayout) + builder.setPositiveButton( + getString(R.string.ok) + ) { dialog: DialogInterface?, which: Int -> + downloadItem.customFileNameTemplate = editText.text.toString() + } + + // handle the negative button of the alert dialog + builder.setNegativeButton( + getString(R.string.cancel) + ) { dialog: DialogInterface?, which: Int -> } + + val dialog = builder.create() + editText.doOnTextChanged { text, start, before, count -> + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty() + } + dialog.show() + val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager + editText!!.postDelayed({ + editText.requestFocus() + imm.showSoftInput(editText, 0) + }, 300) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty() + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START + } + val saveSubtitles = view.findViewById(R.id.save_subtitles) val subtitleLanguages = view.findViewById(R.id.subtitle_languages) if (downloadItem.videoPreferences.writeSubs) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt index 82b7b043..17758019 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt @@ -210,110 +210,13 @@ class FormatSelectionBottomSheetDialog(private val items: List, p dismiss() } formatItem.setOnLongClickListener { - val bottomSheet = BottomSheetDialog(requireContext()) - bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) - bottomSheet.setContentView(R.layout.format_details_sheet) - - val formatIdParent = bottomSheet.findViewById(R.id.format_id_parent) - val containerParent = bottomSheet.findViewById(R.id.container_parent) - val codecParent = bottomSheet.findViewById(R.id.codec_parent) - val filesizeParent = bottomSheet.findViewById(R.id.filesize_parent) - val formatnoteParent = bottomSheet.findViewById(R.id.format_note_parent) - val fpsParent = bottomSheet.findViewById(R.id.fps_parent) - val asrParent = bottomSheet.findViewById(R.id.asr_parent) - - if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE - else { - formatIdParent?.findViewById(R.id.format_id_value)?.text = format.format_id - formatIdParent?.setOnClickListener { - copyToClipboard(format.format_id) - } - } - - - if (format.container.isBlank()) containerParent?.visibility = View.GONE - else { - containerParent?.findViewById(R.id.container_value)?.text = format.container - containerParent?.setOnClickListener { - copyToClipboard(format.container) - } - } - - val codecField = - if (format.encoding != "") { - format.encoding.uppercase() - }else if (format.vcodec != "none" && format.vcodec != ""){ - format.vcodec.uppercase() - } else { - format.acodec.uppercase() - } - - if (codecField.isBlank()) codecParent?.visibility = View.GONE - else { - codecParent?.findViewById(R.id.codec_value)?.text = codecField - codecParent?.setOnClickListener { - copyToClipboard(codecField) - } - } - - if (format.filesize != 0L) filesizeParent?.visibility = View.GONE - else { - filesizeParent?.findViewById(R.id.filesize_value)?.text = fileUtil.convertFileSize(format.filesize) - filesizeParent?.setOnClickListener { - copyToClipboard(fileUtil.convertFileSize(format.filesize)) - } - } - - if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE - else { - formatnoteParent?.findViewById(R.id.format_note_value)?.text = format.format_note - formatnoteParent?.setOnClickListener { - copyToClipboard(format.format_note) - } - } - - if (format.fps.isNullOrBlank()) fpsParent?.visibility = View.GONE - else { - fpsParent?.findViewById(R.id.fps_value)?.text = format.fps - fpsParent?.setOnClickListener { - copyToClipboard(format.fps!!) - } - } - - if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE - else { - asrParent?.findViewById(R.id.asr_value)?.text = format.asr - asrParent?.setOnClickListener { - copyToClipboard(format.asr!!) - } - } - - - - bottomSheet.show() - val displayMetrics = DisplayMetrics() - requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) - bottomSheet.behavior.peekHeight = displayMetrics.heightPixels - bottomSheet.window!!.setLayout( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - + uiUtil.showFormatDetails(format, requireActivity()) true } linearLayout.addView(formatItem) } } - private fun copyToClipboard(text: String){ - val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText(text, text) - clipboard.setPrimaryClip(clip) - Toast.makeText(context, requireContext().getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT) - .show() - } - - override fun onCancel(dialog: DialogInterface) { super.onCancel(dialog) cleanUp() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsBottomSheetDialog.kt index c06d0934..cafc15f5 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsBottomSheetDialog.kt @@ -2,13 +2,16 @@ package com.deniscerri.ytdlnis.ui.downloadcard import android.annotation.SuppressLint import android.app.Dialog +import android.content.ClipboardManager import android.content.DialogInterface import android.os.Bundle import android.util.DisplayMetrics import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.widget.Button import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity import androidx.core.widget.doAfterTextChanged import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -21,8 +24,10 @@ import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.receiver.ShareActivity +import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.textfield.TextInputLayout import kotlinx.coroutines.Dispatchers @@ -115,12 +120,14 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List, } } - val checkAll = view.findViewById(R.id.check_all) + val checkAll = view.findViewById(R.id.check_all) checkAll!!.setOnClickListener { if (listAdapter.getCheckedItems().size != items.size){ fromTextInput.editText!!.setText("1") toTextInput.editText!!.setText(items.size.toString()) listAdapter.checkAll() + fromTextInput.isEnabled = true + toTextInput.isEnabled = true selectedText.text = resources.getString(R.string.all_items_selected) }else{ reset() @@ -160,6 +167,24 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List, } } + + view.findViewById(R.id.bottomAppBar).setOnMenuItemClickListener { m: MenuItem -> + val itemId = m.itemId + if (itemId == R.id.invert_selected) { + listAdapter.invertSelected(items) + val checkedItems = listAdapter.getCheckedItems() + if (checkedItems.size == items.size){ + selectedText.text = resources.getString(R.string.all_items_selected) + }else{ + selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}" + } + if(checkedItems.isNotEmpty() && checkedItems.size < items.size){ + fromTextInput.isEnabled = false + toTextInput.isEnabled = false + } + } + true + } } override fun onCancel(dialog: DialogInterface) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt index 2fa6f454..622e9119 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt @@ -32,6 +32,7 @@ import com.google.android.material.chip.Chip import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar +import com.google.android.material.textfield.TextInputLayout import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -124,6 +125,7 @@ class CookiesActivity : BaseActivity(), CookieAdapter.OnItemClickListener { builder.setTitle(getString(R.string.cookies)) val inputLayout = layoutInflater.inflate(R.layout.textinput, null) val editText = inputLayout.findViewById(R.id.url_edittext) + inputLayout.findViewById(R.id.url_textinput).hint = "URL" val text = if (url.isNullOrBlank()) "https://" else url editText.setText(text) editText.setSelection(editText.text.length) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/AppearanceSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/AppearanceSettingsFragment.kt new file mode 100644 index 00000000..3e098762 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/AppearanceSettingsFragment.kt @@ -0,0 +1,158 @@ +package com.deniscerri.ytdlnis.ui.more.settings + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context.POWER_SERVICE +import android.content.DialogInterface +import android.content.Intent +import android.content.SharedPreferences +import android.net.Uri +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import android.os.Bundle +import android.os.Environment +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.os.LocaleListCompat +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceManager +import androidx.preference.SeekBarPreference +import androidx.preference.SwitchPreferenceCompat +import androidx.work.WorkInfo +import androidx.work.WorkManager +import com.deniscerri.ytdlnis.BuildConfig +import com.deniscerri.ytdlnis.MainActivity +import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.database.models.CommandTemplate +import com.deniscerri.ytdlnis.database.models.CookieItem +import com.deniscerri.ytdlnis.database.models.DownloadItem +import com.deniscerri.ytdlnis.database.models.HistoryItem +import com.deniscerri.ytdlnis.database.models.SearchHistoryItem +import com.deniscerri.ytdlnis.database.models.TemplateShortcut +import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel +import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel +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.ThemeUtil +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.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.yausername.youtubedl_android.YoutubeDL +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.time.LocalDateTime +import java.util.Locale + + +class AppearanceSettingsFragment : BaseSettingsFragment() { + override val title: Int = R.string.appearance + + private var language: ListPreference? = null + private var theme: ListPreference? = null + private var accent: ListPreference? = null + private var highContrast: SwitchPreferenceCompat? = null + private var locale: ListPreference? = null + + private var updateUtil: UpdateUtil? = null + private var fileUtil: FileUtil? = null + private var activeDownloadCount = 0 + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.appearance_preferences, rootKey) + updateUtil = UpdateUtil(requireContext()) + fileUtil = FileUtil() + + WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){ + activeDownloadCount = 0 + it.forEach {w -> + if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++ + } + } + + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + language = findPreference("app_language") + theme = findPreference("ytdlnis_theme") + accent = findPreference("theme_accent") + highContrast = findPreference("high_contrast") + locale = findPreference("locale") + + if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){ + val values = resources.getStringArray(R.array.language_values) + val entries = mutableListOf() + values.forEach { + entries.add(Locale(it).getDisplayName(Locale(it))) + } + language!!.entries = entries.toTypedArray() + }else{ + language!!.isVisible = false + } + + if(language!!.value == null) language!!.value = Locale.getDefault().language + language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value)) + language!!.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> + language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString())) + AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString())) + true + } + + theme!!.summary = theme!!.entry + theme!!.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> + when(newValue){ + "System" -> { + theme!!.summary = getString(R.string.system) + } + "Dark" -> { + theme!!.summary = getString(R.string.dark) + } + else -> { + theme!!.summary = getString(R.string.light) + } + } + ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) + val intent = Intent(requireContext(), MainActivity::class.java) + this.startActivity(intent) + requireActivity().finishAffinity() + true + } + accent!!.summary = accent!!.entry + accent!!.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> + ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) + val intent = Intent(requireContext(), MainActivity::class.java) + this.startActivity(intent) + requireActivity().finishAffinity() + true + } + highContrast!!.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> + ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) + val intent = Intent(requireContext(), MainActivity::class.java) + this.startActivity(intent) + requireActivity().finishAffinity() + + true + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt new file mode 100644 index 00000000..4b3c9950 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/BaseSettingsFragment.kt @@ -0,0 +1,13 @@ +package com.deniscerri.ytdlnis.ui.more.settings + +import androidx.preference.PreferenceFragmentCompat + + +abstract class BaseSettingsFragment : PreferenceFragmentCompat() { + abstract val title: Int + + override fun onStart() { + super.onStart() + (activity as? SettingsActivity)?.changeTopAppbarTitle(getString(title)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt new file mode 100644 index 00000000..250c2743 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/DownloadSettingsFragment.kt @@ -0,0 +1,67 @@ +package com.deniscerri.ytdlnis.ui.more.settings + +import android.app.Activity +import android.content.Context.POWER_SERVICE +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.PowerManager +import android.provider.Settings +import androidx.preference.Preference +import androidx.preference.SeekBarPreference +import com.deniscerri.ytdlnis.MainActivity +import com.deniscerri.ytdlnis.R +import com.google.android.material.dialog.MaterialAlertDialogBuilder + + +class DownloadSettingsFragment : BaseSettingsFragment() { + override val title: Int = R.string.downloading + + private var concurrentDownloads: SeekBarPreference? = null + private var ignoreBatteryOptimization: Preference? = null + + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.downloading_preferences, rootKey) + + ignoreBatteryOptimization = findPreference("ignore_battery") + val packageName: String = requireContext().packageName + val pm = requireContext().applicationContext.getSystemService(POWER_SERVICE) as PowerManager + if (pm.isIgnoringBatteryOptimizations(packageName)) { + ignoreBatteryOptimization!!.isVisible = false + } + + concurrentDownloads = findPreference("concurrent_downloads") + concurrentDownloads!!.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> + MaterialAlertDialogBuilder(requireContext()) + .setTitle(resources.getString(R.string.warning)) + .setMessage(resources.getString(R.string.workmanager_updated)) + .setNegativeButton(resources.getString(R.string.cancel)) { dialog, _ -> + dialog.cancel() + } + .setPositiveButton(resources.getString(R.string.restart)) { _, _ -> + val intent = Intent(context, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + requireContext().startActivity(intent) + if (context is Activity) { + (context as Activity).finish() + } + Runtime.getRuntime().exit(0) + } + .show() + + true + } + ignoreBatteryOptimization = findPreference("ignore_battery") + ignoreBatteryOptimization!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val intent = Intent() + intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + intent.data = Uri.parse("package:" + requireContext().packageName) + startActivity(intent) + true + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt new file mode 100644 index 00000000..4747d587 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/FolderSettingsFragment.kt @@ -0,0 +1,179 @@ +package com.deniscerri.ytdlnis.ui.more.settings + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Build.VERSION +import android.os.Bundle +import android.os.Environment +import android.provider.Settings +import androidx.activity.result.contract.ActivityResultContracts +import androidx.preference.Preference +import androidx.preference.PreferenceManager +import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.util.FileUtil +import com.deniscerri.ytdlnis.util.UpdateUtil +import com.google.android.material.snackbar.Snackbar +import java.io.File + + +class FolderSettingsFragment : BaseSettingsFragment() { + override val title: Int = R.string.directories + + private var musicPath: Preference? = null + private var videoPath: Preference? = null + private var commandPath: Preference? = null + private var accessAllFiles : Preference? = null + private var clearCache: Preference? = null + + private var fileUtil: FileUtil? = null + private var activeDownloadCount = 0 + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.folders_preference, rootKey) + + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + val editor = preferences.edit() + fileUtil = FileUtil() + + musicPath = findPreference("music_path") + videoPath = findPreference("video_path") + commandPath = findPreference("command_path") + accessAllFiles = findPreference("access_all_files") + clearCache = findPreference("clear_cache") + + if (preferences.getString("music_path", "")!!.isEmpty()) { + editor.putString("music_path", getString(R.string.music_path)) + } + if (preferences.getString("video_path", "")!!.isEmpty()) { + editor.putString("video_path", getString(R.string.video_path)) + } + if (preferences.getString("command_path", "")!!.isEmpty()) { + editor.putString("command_path", getString(R.string.command_path)) + } + + if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) || + VERSION.SDK_INT < 30) { + accessAllFiles!!.isVisible = false + } + + editor.apply() + + musicPath!!.summary = fileUtil?.formatPath(preferences.getString("music_path", "")!!) + musicPath!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + musicPathResultLauncher.launch(intent) + true + } + videoPath!!.summary = fileUtil?.formatPath(preferences.getString("video_path", "")!!) + videoPath!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + videoPathResultLauncher.launch(intent) + true + } + commandPath!!.summary = fileUtil?.formatPath(preferences.getString("command_path", "")!!) + commandPath!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + commandPathResultLauncher.launch(intent) + true + } + accessAllFiles!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) + val uri = Uri.parse("package:" + requireContext().packageName) + intent.data = uri + startActivity(intent) + true + } + + var cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() } + clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}" + clearCache!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + if (activeDownloadCount == 0){ + File(requireContext().cacheDir.absolutePath + "/downloads").deleteRecursively() + Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show() + cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() } + clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}" + }else{ + Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show() + } + true + } + + } + + private var musicPathResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { + activity?.contentResolver?.takePersistableUriPermission( + it, + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } + changePath(musicPath, result.data, MUSIC_PATH_CODE) + } + } + private var videoPathResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { + activity?.contentResolver?.takePersistableUriPermission( + it, + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } + changePath(videoPath, result.data, VIDEO_PATH_CODE) + } + } + private var commandPathResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { + activity?.contentResolver?.takePersistableUriPermission( + it, + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } + changePath(commandPath, result.data, COMMAND_PATH_CODE) + } + } + + private fun changePath(p: Preference?, data: Intent?, requestCode: Int) { + val path = data!!.data.toString() + p!!.summary = fileUtil?.formatPath(data.data.toString()) + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + val editor = sharedPreferences.edit() + when (requestCode) { + MUSIC_PATH_CODE -> editor.putString("music_path", path) + VIDEO_PATH_CODE -> editor.putString("video_path", path) + COMMAND_PATH_CODE -> editor.putString("command_path", path) + } + editor.apply() + } + + companion object { + const val MUSIC_PATH_CODE = 33333 + const val VIDEO_PATH_CODE = 55555 + const val COMMAND_PATH_CODE = 77777 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt new file mode 100644 index 00000000..179d5f7b --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt @@ -0,0 +1,501 @@ +package com.deniscerri.ytdlnis.ui.more.settings + +import android.app.Activity +import android.content.DialogInterface +import android.content.Intent +import android.content.SharedPreferences +import android.os.Bundle +import android.os.Environment +import androidx.activity.result.contract.ActivityResultContracts +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceManager +import androidx.work.WorkInfo +import androidx.work.WorkManager +import com.deniscerri.ytdlnis.BuildConfig +import com.deniscerri.ytdlnis.MainActivity +import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.database.models.CommandTemplate +import com.deniscerri.ytdlnis.database.models.CookieItem +import com.deniscerri.ytdlnis.database.models.DownloadItem +import com.deniscerri.ytdlnis.database.models.HistoryItem +import com.deniscerri.ytdlnis.database.models.SearchHistoryItem +import com.deniscerri.ytdlnis.database.models.TemplateShortcut +import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel +import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel +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.UpdateUtil +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.util.Calendar + + +class MainSettingsFragment : PreferenceFragmentCompat() { + private var backup : Preference? = null + private var restore : Preference? = null + + private var updateUtil: UpdateUtil? = null + private var fileUtil: FileUtil? = null + private var activeDownloadCount = 0 + + private lateinit var resultViewModel: ResultViewModel + private lateinit var historyViewModel: HistoryViewModel + private lateinit var downloadViewModel: DownloadViewModel + private lateinit var cookieViewModel: CookieViewModel + private lateinit var commandTemplateViewModel: CommandTemplateViewModel + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.root_preferences, rootKey) + val navController = findNavController() + val appearance = findPreference("appearance") + appearance?.summary = "${getString(R.string.language)}, ${getString(R.string.Theme)}, ${getString(R.string.accents)}" + appearance?.setOnPreferenceClickListener { + navController.navigate(R.id.action_mainSettingsFragment_to_appearanceSettingsFragment) + true + } + + val folders = findPreference("folders") + folders?.summary = "${getString(R.string.music_directory)}, ${getString(R.string.video_directory)}, ${getString(R.string.command_directory)}" + folders?.setOnPreferenceClickListener { + navController.navigate(R.id.action_mainSettingsFragment_to_folderSettingsFragment) + true + } + + val downloading = findPreference("downloading") + downloading?.summary = "${getString(R.string.quick_download)}, ${getString(R.string.concurrent_downloads)}, ${getString(R.string.limit_rate)}" + downloading?.setOnPreferenceClickListener { + navController.navigate(R.id.action_mainSettingsFragment_to_downloadSettingsFragment) + true + } + + val processing = findPreference("processing") + processing?.summary = "${getString(R.string.sponsorblock)}, ${getString(R.string.embed_subtitles)}, ${getString(R.string.add_chapters)}" + processing?.setOnPreferenceClickListener { + navController.navigate(R.id.action_mainSettingsFragment_to_processingSettingsFragment) + true + } + + val updating = findPreference("updating") + updating?.summary = "${getString(R.string.update_ytdl)}, ${getString(R.string.format_source)}, ${getString(R.string.update_app)}" + updating?.setOnPreferenceClickListener { + navController.navigate(R.id.action_mainSettingsFragment_to_updateSettingsFragment) + true + } + + //about section ------------------------- + updateUtil = UpdateUtil(requireContext()) + fileUtil = FileUtil() + + WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){ + activeDownloadCount = 0 + it.forEach {w -> + if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++ + } + } + + resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] + historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java] + downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] + cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java] + commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] + + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + backup = findPreference("backup") + restore = findPreference("restore") + + backup!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val builder = MaterialAlertDialogBuilder(requireContext()) + builder.setTitle(getString(R.string.select_backup_categories)) + val values = resources.getStringArray(R.array.backup_category_values) + val entries = resources.getStringArray(R.array.backup_category_entries) + val checkedItems : ArrayList = arrayListOf() + values.forEach { _ -> + checkedItems.add(true) + } + + builder.setMultiChoiceItems( + entries, + checkedItems.toBooleanArray() + ) { _, which, isChecked -> + checkedItems[which] = isChecked + } + + builder.setPositiveButton( + getString(R.string.ok) + ) { _: DialogInterface?, _: Int -> + lifecycleScope.launch(Dispatchers.IO) { + 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() ) + "cookies" -> json.add("cookies", backupCookies() ) + "templates" -> json.add("templates", backupCommandTemplates() ) + "shortcuts" -> json.add("shortcuts", backupShortcuts() ) + "searchHistory" -> json.add("search_history", backupSearchHistory() ) + } + } + } + + val currentTime = Calendar.getInstance() + val saveFile = File( + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS), "YTDLnis_${BuildConfig.VERSION_NAME}_${currentTime.get( + Calendar.YEAR)}-${currentTime.get(Calendar.MONTH) + 1}-${currentTime.get( + Calendar.DAY_OF_MONTH)}.json") + saveFile.delete() + saveFile.createNewFile() + saveFile.writeText(Gson().toJson(json)) + Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG).show() + } + } + + // handle the negative button of the alert dialog + builder.setNegativeButton( + getString(R.string.cancel) + ) { _: DialogInterface?, _: Int -> } + + + val dialog = builder.create() + dialog.show() + true + } + restore!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/json" + } + appRestoreResultLauncher.launch(intent) + true + } + } + + private fun backupSettings(preferences: SharedPreferences) : JsonArray { + runCatching { + val prefs = preferences.all + val arr = JsonArray() + prefs.forEach { + val obj = JsonObject() + obj.addProperty("key", it.key) + obj.addProperty("value", it.value.toString()) + obj.addProperty("type", it.value!!::class.simpleName) + arr.add(obj) + } + return arr + } + return JsonArray() + } + + private suspend fun backupHistory() : JsonArray { + runCatching { + val historyItems = withContext(Dispatchers.IO) { + historyViewModel.getAll() + } + val arr = JsonArray() + historyItems.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + + private suspend fun backupQueuedDownloads() : JsonArray { + runCatching { + val items = withContext(Dispatchers.IO) { + downloadViewModel.getQueued() + } + val arr = JsonArray() + items.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + + private suspend fun backupCancelledDownloads() : JsonArray { + runCatching { + val items = withContext(Dispatchers.IO) { + downloadViewModel.getCancelled() + } + val arr = JsonArray() + items.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + + private suspend fun backupCookies() : JsonArray { + runCatching { + val items = withContext(Dispatchers.IO) { + cookieViewModel.getAll() + } + val arr = JsonArray() + items.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + + private suspend fun backupCommandTemplates() : JsonArray { + runCatching { + val items = withContext(Dispatchers.IO) { + commandTemplateViewModel.getAll() + } + val arr = JsonArray() + items.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + + private suspend fun backupShortcuts() : JsonArray { + runCatching { + val items = withContext(Dispatchers.IO) { + commandTemplateViewModel.getAllShortcuts() + } + val arr = JsonArray() + items.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + + private suspend fun backupSearchHistory() : JsonArray { + runCatching { + val historyItems = withContext(Dispatchers.IO) { + resultViewModel.getSearchHistory() + } + val arr = JsonArray() + historyItems.forEach { + arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject) + } + return arr + } + return JsonArray() + } + + private var appRestoreResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { + activity?.contentResolver?.takePersistableUriPermission( + it, + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + ) + } + + lifecycleScope.launch { + val preferences = + PreferenceManager.getDefaultSharedPreferences(requireContext()) + val editor = preferences.edit() + + runCatching { + val ip = requireContext().contentResolver.openInputStream(result.data!!.data!!) + val r = BufferedReader(InputStreamReader(ip)) + val total: java.lang.StringBuilder = java.lang.StringBuilder() + var line: String? + while (r.readLine().also { line = it } != null) { + total.append(line).append('\n') + } + + val json = Gson().fromJson(total.toString(), JsonObject::class.java) + val finalMessage = StringBuilder("${getString(R.string.restore_complete)}\n") + + //preference restore + if(json.has("settings")){ + val prefs = json.getAsJsonArray("settings") + val preferencesKeys = preferences.all.map { it.key } + prefs.forEach { + val key : String = it.asJsonObject.get("key").toString().replace("\"", "") + if (preferencesKeys.contains(key)){ + when(it.asJsonObject.get("type").toString().replace("\"", "")){ + "String" -> { + val value = it.asJsonObject.get("value").toString().replace("\"", "") + editor.putString(key, value) + } + "Boolean" -> { + val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean() + editor.putBoolean(key, value) + } + "Int" -> { + val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt() + editor.putInt(key, value) + } + "HashSet" -> { + val value = hashSetOf(it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])".toRegex(), "")) + editor.putStringSet(key, value) + } + } + } + } + finalMessage.append("${getString(R.string.settings)}: ${prefs.count()}\n") + } + //history restore + if(json.has("downloads")){ + val items = json.getAsJsonArray("downloads") + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), HistoryItem::class.java) + item.id = 0L + withContext(Dispatchers.IO){ + historyViewModel.insert(item) + } + } + finalMessage.append("${getString(R.string.downloads)}: ${items.count()}\n") + } + + //queued downloads restore + if(json.has("queued")){ + val items = json.getAsJsonArray("queued") + val queued = mutableListOf() + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java) + item.id = 0L + queued.add(item) + } + if (queued.isNotEmpty()) { + finalMessage.append("${getString(R.string.in_queue)}: ${queued.count()}\n") + withContext(Dispatchers.IO){downloadViewModel.queueDownloads(queued)} + } + } + + //cancelled downloads restore + if(json.has("cancelled")){ + val items = json.getAsJsonArray("cancelled") + val cancelled = mutableListOf() + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java) + item.id = 0L + cancelled.add(item) + } + if(cancelled.isNotEmpty()){ + finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n") + } + } + + //cookies restore + if(json.has("cookies")){ + val items = json.getAsJsonArray("cookies") + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CookieItem::class.java) + item.id = 0L + withContext(Dispatchers.IO){ + cookieViewModel.insert(item) + } + } + if(items.count() > 0){ + finalMessage.append("${getString(R.string.cookies)}: ${items.count()}\n") + } + } + + //command template restore + if(json.has("templates")){ + val items = json.getAsJsonArray("templates") + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java) + item.id = 0L + withContext(Dispatchers.IO){ + commandTemplateViewModel.insert(item) + } + } + if(items.count() > 0){ + finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n") + } + } + + //shortcuts restore + if(json.has("shortcuts")){ + val items = json.getAsJsonArray("shortcuts") + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), TemplateShortcut::class.java) + item.id = 0L + withContext(Dispatchers.IO){ + commandTemplateViewModel.insertShortcut(item) + } + } + if(items.count() > 0){ + finalMessage.append("${getString(R.string.shortcuts)}: ${items.count()}\n") + } + } + + + //search history restore + if(json.has("search_history")){ + val items = json.getAsJsonArray("search_history") + items.forEach { + val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), SearchHistoryItem::class.java) + withContext(Dispatchers.IO){ + resultViewModel.addSearchQueryToHistory(item.query) + } + } + if(items.count() > 0){ + finalMessage.append("${getString(R.string.search_history)}: ${items.count()}\n") + } + } + + finalMessage.append("${getString(R.string.restart)}?") + + val builder = MaterialAlertDialogBuilder(requireContext()) + builder.setTitle(getString(R.string.restore)) + builder.setMessage(finalMessage) + builder.setPositiveButton( + getString(R.string.restart) + ) { _: DialogInterface?, _: Int -> + val intent = Intent(context, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + requireContext().startActivity(intent) + if (context is Activity) { + (context as Activity).finish() + } + Runtime.getRuntime().exit(0) + } + + // handle the negative button of the alert dialog + builder.setNegativeButton( + getString(R.string.cancel) + ) { _: DialogInterface?, _: Int -> } + + val dialog = builder.create() + dialog.show() + }.onFailure { + it.printStackTrace() + Snackbar.make(requireView(), getString(R.string.couldnt_parse_file), Snackbar.LENGTH_LONG).show() + } + } + + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt new file mode 100644 index 00000000..12e6e979 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt @@ -0,0 +1,20 @@ +package com.deniscerri.ytdlnis.ui.more.settings + +import android.os.Bundle +import androidx.preference.EditTextPreference +import androidx.preference.Preference +import com.deniscerri.ytdlnis.R + + +class ProcessingSettingsFragment : BaseSettingsFragment() { + override val title: Int = R.string.processing + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.processing_preferences, rootKey) + findPreference("file_name_template")?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]" + findPreference("file_name_template")?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]" + + findPreference("file_name_template_audio")?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]" + findPreference("file_name_template_audio")?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsActivity.kt index 0e37a84d..f89ca12f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsActivity.kt @@ -3,28 +3,56 @@ package com.deniscerri.ytdlnis.ui.more.settings import android.content.Context import android.content.SharedPreferences import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.addCallback import androidx.fragment.app.FragmentManager +import androidx.fragment.app.commit +import androidx.fragment.app.replace +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController +import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.databinding.ActivitySettingsBinding import com.deniscerri.ytdlnis.ui.BaseActivity import com.google.android.material.appbar.MaterialToolbar class SettingsActivity : BaseActivity() { - private var fm: FragmentManager? = null - private var topAppBar: MaterialToolbar? = null - private lateinit var sharedPreferences: SharedPreferences - private lateinit var editor: SharedPreferences.Editor var context: Context? = null + lateinit var binding: ActivitySettingsBinding public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_settings) context = baseContext - topAppBar = findViewById(R.id.settings_toolbar) - topAppBar!!.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() } - fm = supportFragmentManager - fm!!.beginTransaction() - .replace(R.id.settings_frame_layout, SettingsFragment()) - .commit() + binding = ActivitySettingsBinding.inflate(layoutInflater) + setContentView(binding.root) + + val navHostFragment = supportFragmentManager.findFragmentById(R.id.frame_layout) as NavHostFragment + val navController = navHostFragment.findNavController() + + val listener = NavController.OnDestinationChangedListener { controller, destination, arguments -> + if (destination.id == R.id.mainSettingsFragment){ + changeTopAppbarTitle(getString(R.string.settings)) + } + } + + navController.addOnDestinationChangedListener(listener) + binding.settingsToolbar.setNavigationOnClickListener { + onBackPressedDispatcher.onBackPressed() + } + + onBackPressedDispatcher.addCallback(this) { + if (navController.currentDestination?.id == R.id.mainSettingsFragment) { + navController.popBackStack() + finishAndRemoveTask() + }else{ + navController.navigateUp() + } + } + + navController.navigate(R.id.mainSettingsFragment) + } + + fun changeTopAppbarTitle(text: String) { + if (this::binding.isInitialized) binding.collapsingToolbar.title = text } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsFragment.kt deleted file mode 100644 index fab5c573..00000000 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/SettingsFragment.kt +++ /dev/null @@ -1,522 +0,0 @@ -package com.deniscerri.ytdlnis.ui.more.settings - -import android.annotation.SuppressLint -import android.app.Activity -import android.content.ClipboardManager -import android.content.Context.POWER_SERVICE -import android.content.Intent -import android.net.Uri -import android.os.Build.VERSION -import android.os.Build.VERSION_CODES -import android.os.Bundle -import android.os.Environment -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.content.edit -import androidx.core.os.LocaleListCompat -import androidx.lifecycle.lifecycleScope -import androidx.preference.EditTextPreference -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.PreferenceManager -import androidx.preference.SeekBarPreference -import androidx.preference.SwitchPreferenceCompat -import androidx.work.WorkInfo -import androidx.work.WorkManager -import com.deniscerri.ytdlnis.BuildConfig -import com.deniscerri.ytdlnis.MainActivity -import com.deniscerri.ytdlnis.R -import com.deniscerri.ytdlnis.util.FileUtil -import com.deniscerri.ytdlnis.util.ThemeUtil -import com.deniscerri.ytdlnis.util.UpdateUtil -import com.google.android.material.dialog.MaterialAlertDialogBuilder -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 -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonArray -import java.io.File -import java.util.Locale - - -class SettingsFragment : PreferenceFragmentCompat() { - private var language: ListPreference? = null - private var theme: ListPreference? = null - private var accent: ListPreference? = null - private var highContrast: SwitchPreferenceCompat? = null - private var ignoreBatteryOptimization: Preference? = null - private var musicPath: Preference? = null - private var videoPath: Preference? = null - private var commandPath: Preference? = null - private var accessAllFiles : Preference? = null - private var clearCache: Preference? = null - private var locale: ListPreference? = null - private var concurrentFragments: SeekBarPreference? = null - private var concurrentDownloads: SeekBarPreference? = null - private var audioFormat: ListPreference? = null - private var videoFormat: ListPreference? = null - private var videoQuality: ListPreference? = null - private var formatID: EditTextPreference? = null - private var updateYTDL: Preference? = null - private var formatSource: ListPreference? = null - private var exportPreferences : Preference? = null - private var importPreferences : Preference? = null - private var ytdlVersion: Preference? = null - private var version: Preference? = null - - - private var updateUtil: UpdateUtil? = null - private var fileUtil: FileUtil? = null - private var activeDownloadCount = 0 - - private val jsonFormat = Json { prettyPrint = true } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.root_preferences, rootKey) - updateUtil = UpdateUtil(requireContext()) - fileUtil = FileUtil() - - WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){ - activeDownloadCount = 0 - it.forEach {w -> - if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++ - } - } - - initPreferences() - initListeners() - } - - private fun initPreferences() { - val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) - val editor = preferences.edit() - language = findPreference("app_language") - theme = findPreference("ytdlnis_theme") - accent = findPreference("theme_accent") - highContrast = findPreference("high_contrast") - ignoreBatteryOptimization = findPreference("ignore_battery") - musicPath = findPreference("music_path") - videoPath = findPreference("video_path") - commandPath = findPreference("command_path") - accessAllFiles = findPreference("access_all_files") - clearCache = findPreference("clear_cache") - - locale = findPreference("locale") - concurrentFragments = findPreference("concurrent_fragments") - concurrentDownloads = findPreference("concurrent_downloads") - - audioFormat = findPreference("audio_format") - videoFormat = findPreference("video_format") - videoQuality = findPreference("video_quality") - formatID = findPreference("format_id") - updateYTDL = findPreference("update_ytdl") - formatSource = findPreference("formats_source") - exportPreferences = findPreference("export_preferences") - importPreferences = findPreference("import_preferences") - ytdlVersion = findPreference("ytdl-version") - ytdlVersion!!.summary = preferences.getString("ytdl-version", "NULL") - version = findPreference("version") - version!!.summary = BuildConfig.VERSION_NAME - - if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){ - val values = resources.getStringArray(R.array.language_values) - val entries = mutableListOf() - values.forEach { - entries.add(Locale(it).getDisplayName(Locale(it))) - } - language!!.entries = entries.toTypedArray() - }else{ - language!!.isVisible = false - } - - val packageName: String = requireContext().packageName - val pm = requireContext().applicationContext.getSystemService(POWER_SERVICE) as PowerManager - if (pm.isIgnoringBatteryOptimizations(packageName)) { - ignoreBatteryOptimization!!.isVisible = false - } - - - if (preferences.getString("music_path", "")!!.isEmpty()) { - editor.putString("music_path", getString(R.string.music_path)) - } - if (preferences.getString("video_path", "")!!.isEmpty()) { - editor.putString("video_path", getString(R.string.video_path)) - } - if (preferences.getString("command_path", "")!!.isEmpty()) { - editor.putString("command_path", getString(R.string.command_path)) - } - - if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) || - VERSION.SDK_INT < 30) { - accessAllFiles!!.isVisible = false - } - - editor.apply() - } - - @SuppressLint("BatteryLife", "InlinedApi") - private fun initListeners() { - val preferences = - PreferenceManager.getDefaultSharedPreferences(requireContext()) - val editor = preferences.edit() - - if(language!!.value == null) language!!.value = Locale.getDefault().language - language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value)) - language!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> - language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString())) - AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString())) - true - } - - theme!!.summary = theme!!.entry - theme!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> - when(newValue){ - "System" -> { - theme!!.summary = getString(R.string.system) - } - "Dark" -> { - theme!!.summary = getString(R.string.dark) - } - else -> { - theme!!.summary = getString(R.string.light) - } - } - ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) - val intent = Intent(requireContext(), MainActivity::class.java) - this.startActivity(intent) - requireActivity().finishAffinity() - true - } - accent!!.summary = accent!!.entry - accent!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> - ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) - val intent = Intent(requireContext(), MainActivity::class.java) - this.startActivity(intent) - requireActivity().finishAffinity() - true - } - highContrast!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> - ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) - val intent = Intent(requireContext(), MainActivity::class.java) - this.startActivity(intent) - requireActivity().finishAffinity() - - true - } - ignoreBatteryOptimization!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - val intent = Intent() - intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - intent.data = Uri.parse("package:" + requireContext().packageName) - startActivity(intent) - true - } - - musicPath!!.summary = fileUtil?.formatPath(preferences.getString("music_path", "")!!) - musicPath!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - musicPathResultLauncher.launch(intent) - true - } - videoPath!!.summary = fileUtil?.formatPath(preferences.getString("video_path", "")!!) - videoPath!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - videoPathResultLauncher.launch(intent) - true - } - commandPath!!.summary = fileUtil?.formatPath(preferences.getString("command_path", "")!!) - commandPath!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - commandPathResultLauncher.launch(intent) - true - } - accessAllFiles!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) - val uri = Uri.parse("package:" + requireContext().packageName) - intent.data = uri - startActivity(intent) - true - } - - var cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() } - clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}" - clearCache!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - if (activeDownloadCount == 0){ - File(requireContext().cacheDir.absolutePath + "/downloads").deleteRecursively() - Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show() - cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() } - clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}" - }else{ - Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show() - } - true - } - - locale!!.summary = locale!!.value - concurrentDownloads!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> - MaterialAlertDialogBuilder(requireContext()) - .setTitle(resources.getString(R.string.warning)) - .setMessage(resources.getString(R.string.workmanager_updated)) - .setNegativeButton(resources.getString(R.string.cancel)) { dialog, _ -> - dialog.cancel() - } - .setPositiveButton(resources.getString(R.string.restart)) { _, _ -> - val intent = Intent(context, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - requireContext().startActivity(intent) - if (context is Activity) { - (context as Activity).finish() - } - Runtime.getRuntime().exit(0) - } - .show() - - true - } - - audioFormat!!.summary = preferences.getString("audio_format", "")!!.replace("Default", getString(R.string.defaultValue)) - audioFormat!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> - audioFormat!!.summary = newValue.toString().replace("Default", getString(R.string.defaultValue)) - true - } - videoFormat!!.summary = preferences.getString("video_format", "")!!.replace("Default", getString(R.string.defaultValue)) - videoFormat!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> - videoFormat!!.summary = newValue.toString().replace("Default", getString(R.string.defaultValue)) - true - } - - videoQuality!!.summary = preferences.getString("video_quality", "") - videoQuality!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> - videoQuality!!.summary = newValue.toString() - true - } - formatID!!.summary = formatID!!.text - formatID!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> - formatID!!.summary = newValue.toString() - true - } - YoutubeDL.getInstance().version(context)?.let { - editor.putString("ytdl-version", it) - editor.apply() - ytdlVersion!!.summary = it - } - updateYTDL!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - lifecycleScope.launch { - 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() - - 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() - } - } - } - true - } - formatSource!!.summary = preferences.getString("formats_source", "")!!.replace("Default", getString(R.string.defaultValue)) - formatSource!!.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> - formatSource!!.summary = newValue.toString().replace("Default", getString(R.string.defaultValue)) - true - } - exportPreferences!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - val prefs = preferences.all - val json = buildJsonObject { - putJsonArray("YTDLnis_Preferences") { - prefs.forEach { - add(buildJsonObject { - put("key", it.key) - put("value", it.value.toString()) - put("type", it.value!!::class.simpleName) - }) - } - } - } - val string = jsonFormat.encodeToString(json) - val clipboard: ClipboardManager = - requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.setText(string) - Snackbar.make(requireView(), getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show() - true - } - importPreferences!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - val clipboard = requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager - val clip = clipboard.primaryClip!!.getItemAt(0).text.toString() - try{ - PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true) { - clear() - - val preferencesKeys = preferences.all.map { it.key } - jsonFormat.decodeFromString(clip).run { - val prefs = this.jsonObject["YTDLnis_Preferences"]!!.jsonArray - prefs.forEach { - val key : String = it.jsonObject["key"].toString().replace("\"", "") - if (preferencesKeys.contains(key)){ - when(it.jsonObject["type"].toString().replace("\"", "")){ - "String" -> { - val value = it.jsonObject["value"].toString().replace("\"", "") - putString(key, value) - } - "Boolean" -> { - val value = it.jsonObject["value"].toString().replace("\"", "").toBoolean() - putBoolean(key, value) - } - "Int" -> { - val value = it.jsonObject["value"].toString().replace("\"", "").toInt() - putInt(key, value) - } - "HashSet" -> { - val value = hashSetOf(it.jsonObject["value"].toString().replace("(\")|(\\[)|(])".toRegex(), "")) - putStringSet(key, value) - } - } - } - } - } - } - - val intent = Intent(context, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - requireContext().startActivity(intent) - if (context is Activity) { - (context as Activity).finish() - } - Runtime.getRuntime().exit(0) - }catch (e: Exception){ - e.printStackTrace() - } - - true - } - 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 var musicPathResultLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.data?.let { - activity?.contentResolver?.takePersistableUriPermission( - it, - Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - } - changePath(musicPath, result.data, MUSIC_PATH_CODE) - } - } - private var videoPathResultLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.data?.let { - activity?.contentResolver?.takePersistableUriPermission( - it, - Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - } - changePath(videoPath, result.data, VIDEO_PATH_CODE) - } - } - private var commandPathResultLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.data?.let { - activity?.contentResolver?.takePersistableUriPermission( - it, - Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION - ) - } - changePath(commandPath, result.data, COMMAND_PATH_CODE) - } - } - - private fun changePath(p: Preference?, data: Intent?, requestCode: Int) { - val path = data!!.data.toString() - p!!.summary = fileUtil?.formatPath(data.data.toString()) - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) - val editor = sharedPreferences.edit() - when (requestCode) { - MUSIC_PATH_CODE -> editor.putString("music_path", path) - VIDEO_PATH_CODE -> editor.putString("video_path", path) - COMMAND_PATH_CODE -> editor.putString("command_path", path) - } - editor.apply() - } - - companion object { - const val MUSIC_PATH_CODE = 33333 - const val VIDEO_PATH_CODE = 55555 - const val COMMAND_PATH_CODE = 77777 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt new file mode 100644 index 00000000..e0bc1b63 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt @@ -0,0 +1,89 @@ +package com.deniscerri.ytdlnis.ui.more.settings + +import android.os.Bundle +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.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() { + override val title: Int = R.string.updating + private var updateYTDL: Preference? = null + private var ytdlVersion: Preference? = null + private var version: Preference? = null + private var updateUtil: UpdateUtil? = null + + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.updating_preferences, rootKey) + updateUtil = UpdateUtil(requireContext()) + val preferences = + PreferenceManager.getDefaultSharedPreferences(requireContext()) + val editor = preferences.edit() + updateYTDL = findPreference("update_ytdl") + ytdlVersion = findPreference("ytdl-version") + version = findPreference("version") + version!!.summary = BuildConfig.VERSION_NAME + + YoutubeDL.getInstance().version(context)?.let { + editor.putString("ytdl-version", it) + editor.apply() + ytdlVersion!!.summary = it + } + updateYTDL!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + lifecycleScope.launch { + 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() + + 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() + } + } + } + true + } + + 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 + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt index 9658ab05..144fdd03 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -525,49 +525,63 @@ class InfoUtil(private val context: Context) { urls.forEach { urlsFile.appendText(it+"\n") } - try { - val request = YoutubeDLRequest(emptyList()) - request.addOption("--print", "%(formats)s") - request.addOption("-a", urlsFile.absolutePath) - request.addOption("--skip-download") - request.addOption("-R", "1") - request.addOption("--socket-timeout", "5") - val cookiesFile = File(context.cacheDir, "cookies.txt") - if (cookiesFile.exists()){ - request.addOption("--cookies", cookiesFile.absolutePath) - } - YoutubeDL.getInstance().execute(request){ progress, _, line -> - try{ - val formats = mutableListOf() - val listOfStrings = JSONArray(line) - for (f in 0 until listOfStrings.length()){ - val format = listOfStrings.get(f) as JSONObject - try{ - if (format.getString("filesize") == "None") continue - }catch (e: Exception) { continue } - val formatProper = Gson().fromJson(format.toString(), Format::class.java) - if (formatProper.filesize > 0){ - if ( !formatProper!!.format_note!!.contains("audio only", true)) { - formatProper.format_note = format.getString("format_note") - }else{ - formatProper.format_note = "${format.getString("format_note")} audio" - } - formatProper.container = format.getString("ext") - formats.add(formatProper) - } - } - progress(formats) - }catch (e: Exception){ - progress(emptyList()) + val formatSource = sharedPreferences.getString("formats_source", "yt-dlp") + val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?") + val allYoutubeLinks = urls.any {p.matcher(it).find() } + if (formatSource == "yt-dlp" || !allYoutubeLinks){ + try { + val request = YoutubeDLRequest(emptyList()) + request.addOption("--print", "%(formats)s") + request.addOption("-a", urlsFile.absolutePath) + request.addOption("--skip-download") + request.addOption("-R", "1") + request.addOption("--socket-timeout", "5") + val cookiesFile = File(context.cacheDir, "cookies.txt") + if (cookiesFile.exists()){ + request.addOption("--cookies", cookiesFile.absolutePath) } + + YoutubeDL.getInstance().execute(request){ progress, _, line -> + try{ + val formats = mutableListOf() + val listOfStrings = JSONArray(line) + for (f in 0 until listOfStrings.length()){ + val format = listOfStrings.get(f) as JSONObject + try{ + if (format.getString("filesize") == "None") continue + }catch (e: Exception) { continue } + val formatProper = Gson().fromJson(format.toString(), Format::class.java) + if (formatProper.filesize > 0){ + if ( !formatProper!!.format_note!!.contains("audio only", true)) { + formatProper.format_note = format.getString("format_note") + }else{ + formatProper.format_note = "${format.getString("format_note")} audio" + } + formatProper.container = format.getString("ext") + formats.add(formatProper) + } + } + progress(formats) + }catch (e: Exception){ + progress(emptyList()) + } + } + } catch (e: Exception) { + Looper.prepare().run { + Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() + } + e.printStackTrace() } - } catch (e: Exception) { - Looper.prepare().run { - Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() + }else{ + urls.forEach { + val id = getIDFromYoutubeURL(it) + val res = genericRequest(invidousURL + "videos/" + id) + val vid = createVideoFromInvidiousJSON(res) + progress(vid!!.formats) } - e.printStackTrace() } + urlsFile.delete() } @@ -741,6 +755,7 @@ class InfoUtil(private val context: Context) { 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() val suggestionList = ArrayList() try { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt index 789c2b32..a6396a97 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt @@ -303,6 +303,107 @@ class UiUtil(private val fileUtil: FileUtil) { datePicker.show(fragmentManager, "datepicker") } + fun showFormatDetails(format: Format, activity: Activity){ + val bottomSheet = BottomSheetDialog(activity) + bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) + bottomSheet.setContentView(R.layout.format_details_sheet) + + val formatIdParent = bottomSheet.findViewById(R.id.format_id_parent) + val containerParent = bottomSheet.findViewById(R.id.container_parent) + val codecParent = bottomSheet.findViewById(R.id.codec_parent) + val filesizeParent = bottomSheet.findViewById(R.id.filesize_parent) + val formatnoteParent = bottomSheet.findViewById(R.id.format_note_parent) + val fpsParent = bottomSheet.findViewById(R.id.fps_parent) + val asrParent = bottomSheet.findViewById(R.id.asr_parent) + + if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE + else { + formatIdParent?.findViewById(R.id.format_id_value)?.text = format.format_id + formatIdParent?.setOnClickListener { + copyToClipboard(format.format_id, activity) + } + } + + + if (format.container.isBlank()) containerParent?.visibility = View.GONE + else { + containerParent?.findViewById(R.id.container_value)?.text = format.container + containerParent?.setOnClickListener { + copyToClipboard(format.container, activity) + } + } + + val codecField = + if (format.encoding != "") { + format.encoding.uppercase() + }else if (format.vcodec != "none" && format.vcodec != ""){ + format.vcodec.uppercase() + } else { + format.acodec.uppercase() + } + + if (codecField.isBlank()) codecParent?.visibility = View.GONE + else { + codecParent?.findViewById(R.id.codec_value)?.text = codecField + codecParent?.setOnClickListener { + copyToClipboard(codecField, activity) + } + } + + if (format.filesize != 0L) filesizeParent?.visibility = View.GONE + else { + filesizeParent?.findViewById(R.id.filesize_value)?.text = fileUtil.convertFileSize(format.filesize) + filesizeParent?.setOnClickListener { + copyToClipboard(fileUtil.convertFileSize(format.filesize), activity) + } + } + + if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE + else { + formatnoteParent?.findViewById(R.id.format_note_value)?.text = format.format_note + formatnoteParent?.setOnClickListener { + copyToClipboard(format.format_note, activity) + } + } + + if (format.fps.isNullOrBlank()) fpsParent?.visibility = View.GONE + else { + fpsParent?.findViewById(R.id.fps_value)?.text = format.fps + fpsParent?.setOnClickListener { + copyToClipboard(format.fps!!, activity) + } + } + + if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE + else { + asrParent?.findViewById(R.id.asr_value)?.text = format.asr + asrParent?.setOnClickListener { + copyToClipboard(format.asr!!, activity) + } + } + + + + bottomSheet.show() + val displayMetrics = DisplayMetrics() + activity.windowManager.defaultDisplay.getMetrics(displayMetrics) + bottomSheet.behavior.peekHeight = displayMetrics.heightPixels + bottomSheet.window!!.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + + + private fun copyToClipboard(text: String, activity: Activity){ + val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText(text, text) + clipboard.setPrimaryClip(clip) + Toast.makeText(activity, activity.getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT) + .show() + } + + suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: CommandTemplate) -> Unit) { val bottomSheet = BottomSheetDialog(activity) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt index e5a4da73..e29265c9 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt @@ -47,7 +47,7 @@ class UpdateUtil(var context: Context) { version = res.getString("tag_name") body = res.getString("body") } catch (e: JSONException) { - result(e.message.toString()) + result(context.getString(R.string.network_error)) return } val versionNameInt = version.split("v")[1].replace(".","").toInt() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt index e289d0a9..605fb995 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -178,8 +178,8 @@ class DownloadWorker( when(type){ DownloadViewModel.Type.audio -> { var audioQualityId : String = downloadItem.format.format_id - if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality)) audioQualityId = "" - else if (audioQualityId == context.getString(R.string.worst_quality)) audioQualityId = "worstaudio" + if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality) || audioQualityId == "best") audioQualityId = "" + else if (audioQualityId == context.getString(R.string.worst_quality) || audioQualityId == "worst") audioQualityId = "worstaudio" val ext = downloadItem.format.container if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId) @@ -236,15 +236,15 @@ class DownloadWorker( Log.e(TAG, videoFormatID) var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best" if (videoFormatID.isNotEmpty()) { - if (videoFormatID == context.resources.getString(R.string.best_quality)) videoFormatID = "bestvideo" - else if (videoFormatID == context.resources.getString(R.string.worst_quality)) videoFormatID = "worst" + if (videoFormatID == context.resources.getString(R.string.best_quality) || videoFormatID == "best") videoFormatID = "bestvideo" + else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst" else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]" if (!downloadItem.videoPreferences.removeAudio) formatArgument = "$videoFormatID+bestaudio/best/$videoFormatID" } Log.e(TAG, formatArgument) request.addOption("-f", formatArgument) val format = downloadItem.format.container - if(format.isNotEmpty()){ + if(format.isNotEmpty() && format != "Default" && format != context.getString(R.string.defaultValue)){ request.addOption("--merge-output-format", format) if (format != "webm") { val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) diff --git a/app/src/main/res/layout-sw650dp/activity_main.xml b/app/src/main/res/layout-sw650dp/activity_main.xml index 22ec92b3..c1a619ce 100644 --- a/app/src/main/res/layout-sw650dp/activity_main.xml +++ b/app/src/main/res/layout-sw650dp/activity_main.xml @@ -11,7 +11,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:text="Incognito" + android:text="@string/incognito" android:textStyle="bold" android:translationZ="20dp" android:visibility="gone" diff --git a/app/src/main/res/layout-sw800dp-land/activity_main.xml b/app/src/main/res/layout-sw800dp-land/activity_main.xml index 0be84496..e063d53a 100644 --- a/app/src/main/res/layout-sw800dp-land/activity_main.xml +++ b/app/src/main/res/layout-sw800dp-land/activity_main.xml @@ -11,7 +11,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:text="Incognito" + android:text="@string/incognito" android:textStyle="bold" android:translationZ="20dp" android:visibility="gone" diff --git a/app/src/main/res/layout/activity_download_log_list.xml b/app/src/main/res/layout/activity_download_log_list.xml index d46d91fa..d4e15f0c 100644 --- a/app/src/main/res/layout/activity_download_log_list.xml +++ b/app/src/main/res/layout/activity_download_log_list.xml @@ -29,6 +29,7 @@ app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/logs_recyclerview" android:layout_width="match_parent" + android:scrollbars="vertical" android:layout_height="match_parent" android:clipToPadding="false" > diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f3950009..6e72cc86 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,7 +11,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:text="Incognito" + android:text="@string/incognito" android:textStyle="bold" android:visibility="gone" android:padding="5dp" diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 632b560f..37540d58 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -6,32 +6,44 @@ android:layout_height="match_parent" tools:context="com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity"> - + app:layout_constraintTop_toBottomOf="@+id/incognito_header" + /> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fitsSystemWindows="true"> - + app:title="@string/settings" + app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" + android:layout_height="?attr/collapsingToolbarLayoutLargeSize"> + + + + diff --git a/app/src/main/res/layout/download_bottom_sheet.xml b/app/src/main/res/layout/download_bottom_sheet.xml index ba3b4087..f38144bb 100644 --- a/app/src/main/res/layout/download_bottom_sheet.xml +++ b/app/src/main/res/layout/download_bottom_sheet.xml @@ -1,154 +1,147 @@ - + xmlns:android="http://schemas.android.com/apk/res/android"> - + android:orientation="vertical"> + + + + + + + + +