diff --git a/app/src/main/java/com/deniscerri/ytdlnis/App.kt b/app/src/main/java/com/deniscerri/ytdlnis/App.kt index 0ff29621..280cc8df 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/App.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/App.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.launch import java.util.* import java.util.concurrent.Executors + class App : Application() { override fun onCreate() { @@ -26,7 +27,7 @@ class App : Application() { instance = this createNotificationChannels() val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) - + setDefaultValues() applicationScope = CoroutineScope(SupervisorJob()) applicationScope.launch((Dispatchers.IO)) { try { @@ -59,6 +60,21 @@ class App : Application() { Aria2c.getInstance().init(this) } + private fun setDefaultValues(){ + val SPL = 1 + val sp = PreferenceManager.getDefaultSharedPreferences(this) + if (sp.getInt("spl", 0) != SPL) { + PreferenceManager.setDefaultValues(this, R.xml.root_preferences, true) + PreferenceManager.setDefaultValues(this, R.xml.downloading_preferences, true) + PreferenceManager.setDefaultValues(this, R.xml.appearance_preferences, true) + PreferenceManager.setDefaultValues(this, R.xml.processing_preferences, true) + PreferenceManager.setDefaultValues(this, R.xml.folders_preference, true) + PreferenceManager.setDefaultValues(this, R.xml.updating_preferences, true) + sp.edit().putInt("spl", SPL).apply() + } + + } + private fun createNotificationChannels() { val notificationUtil = NotificationUtil(this) notificationUtil.createNotificationChannel() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt index 856a667e..82df5f43 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -207,6 +207,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene } } + searchView?.addTransitionListener { _, _, newState -> + if (newState == SearchView.TransitionState.SHOWING){ + mainActivity?.hideBottomNavigation() + }else if (newState == SearchView.TransitionState.HIDING){ + mainActivity?.showBottomNavigation() + } + } + mainActivity?.onBackPressedDispatcher?.addCallback(this) { if (searchView?.isShowing == true) { searchView?.hide() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt index 39f848a0..578b976f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt @@ -32,6 +32,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.forEach import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView @@ -215,8 +216,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{ } } R.id.download_queue -> { - val intent = Intent(context, DownloadQueueMainFragment::class.java) - startActivity(intent) + findNavController().navigate(R.id.downloadQueueMainFragment) } R.id.remove_deleted_history -> { if(allhistoryList!!.isEmpty()){ 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 index 8eda9b0b..507ab9bf 100644 --- 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 @@ -4,10 +4,12 @@ import android.app.Activity import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences +import android.os.Build import android.os.Bundle import android.os.Environment import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.edit import androidx.core.os.LocaleListCompat import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -18,7 +20,6 @@ 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 @@ -65,7 +66,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() { 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?.summary = "${if (Build.VERSION.SDK_INT < 33) getString(R.string.language) + ", " else ""}${getString(R.string.Theme)}, ${getString(R.string.accents)}, ${getString(R.string.preferred_search_engine)}" appearance?.setOnPreferenceClickListener { navController.navigate(R.id.action_mainSettingsFragment_to_appearanceSettingsFragment) true @@ -338,34 +339,36 @@ class MainSettingsFragment : PreferenceFragmentCompat() { //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) + PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){ + clear() + 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("\"", "") + putString(key, value) + } + "Boolean" -> { + val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean() + putBoolean(key, value) + } + "Int" -> { + val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt() + putInt(key, value) + } + "HashSet" -> { + val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",") + putStringSet(key, value.toHashSet()) + } } } } + finalMessage.append("${getString(R.string.settings)}: ${prefs.count()}\n") } - finalMessage.append("${getString(R.string.settings)}: ${prefs.count()}\n") } - editor.commit() //history restore if(json.has("downloads")){ @@ -480,14 +483,14 @@ class MainSettingsFragment : PreferenceFragmentCompat() { 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) + val intent: Intent? = requireContext().packageManager + .getLaunchIntentForPackage(requireContext().packageName) + intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) if(json.has("settings")){ AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(preferences.getString("app_language", "en"))) } activity?.finishAffinity() - Runtime.getRuntime().exit(0) } // handle the negative button of the alert dialog 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 4dfdc5e5..f1d122b5 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -540,6 +540,11 @@ class InfoUtil(private val context: Context) { request.addOption("--cookies", cookiesFile.absolutePath) } + val proxy = sharedPreferences.getString("proxy", "") + if (proxy!!.isNotBlank()){ + request.addOption("--proxy", proxy) + } + YoutubeDL.getInstance().execute(request){ progress, _, line -> try{ val formats = mutableListOf() @@ -601,6 +606,11 @@ class InfoUtil(private val context: Context) { request.addOption("--cookies", cookiesFile.absolutePath) } + val proxy = sharedPreferences.getString("proxy", "") + if (proxy!!.isNotBlank()){ + request.addOption("--proxy", proxy) + } + val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val results: Array = try { val lineSeparator = System.getProperty("line.separator") @@ -697,6 +707,11 @@ class InfoUtil(private val context: Context) { request.addOption("--cookies", cookiesFile.absolutePath) } + val proxy = sharedPreferences.getString("proxy", "") + if (proxy!!.isNotBlank()){ + request.addOption("--proxy", proxy) + } + val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val jsonObject = JSONObject(youtubeDLResponse.out) @@ -836,6 +851,11 @@ class InfoUtil(private val context: Context) { request.addOption("--cookies", cookiesFile.absolutePath) } + val proxy = sharedPreferences.getString("proxy", "") + if (proxy!!.isNotBlank()){ + request.addOption("--proxy", proxy) + } + val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val results: Array = try { val lineSeparator = System.getProperty("line.separator") diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt index 05a5c2c3..124bc9df 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -147,24 +147,6 @@ class NotificationUtil(var context: Context) { notificationManager.notify(DOWNLOAD_RESUME_NOTIFICATION_ID, notificationBuilder.build()) } - fun createUpdatingItemNotification(channel: String){ - val notificationBuilder = getBuilder(channel) - - notificationBuilder - .setContentTitle(context.getString(R.string.updating_download_data)) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setLargeIcon( - BitmapFactory.decodeResource( - context.resources, - android.R.drawable.stat_sys_download - ) - ) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .clearActions() - notificationManager.notify(DOWNLOAD_UPDATING_NOTIFICATION_ID, notificationBuilder.build()) - } - fun createDownloadFinished(title: String?, filepath: String?, channel: String @@ -289,7 +271,6 @@ class NotificationUtil(var context: Context) { const val DOWNLOAD_FINISHED_CHANNEL_ID = "3" const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3 const val DOWNLOAD_RESUME_NOTIFICATION_ID = 4 - const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 5 private const val PROGRESS_MAX = 100 private const val PROGRESS_CURR = 0 } 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 f0de08b2..415b4049 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -16,6 +16,8 @@ import androidx.work.workDataOf import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.DBManager +import com.deniscerri.ytdlnis.database.dao.DownloadDao +import com.deniscerri.ytdlnis.database.dao.ResultDao import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository @@ -25,6 +27,9 @@ import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.NotificationUtil import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDLRequest +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.io.File @@ -62,25 +67,9 @@ class DownloadWorker( repository.setDownloadStatus(downloadItem, DownloadRepository.Status.Active) } - var wasQuickDownloaded = false - //update item if its incomplete - if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){ - notificationUtil.createUpdatingItemNotification(NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID); - - runCatching { - setProgressAsync(workDataOf("progress" to 0, "output" to context.getString(R.string.updating_download_data), "id" to downloadItem.id, "log" to false)) - val info = infoUtil.getMissingInfo(downloadItem.url) - if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString() - if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString() - downloadItem.duration = info?.duration.toString() - downloadItem.website = info?.website.toString() - if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString() - runBlocking { - wasQuickDownloaded = resultDao.getCountInt() == 0 - dao.update(downloadItem) - } - } - notificationUtil.cancelDownloadNotification(NotificationUtil.DOWNLOAD_UPDATING_NOTIFICATION_ID) + CoroutineScope(Dispatchers.IO).launch { + //update item if its incomplete + updateDownloadItem(downloadItem, infoUtil, dao, resultDao) } val intent = Intent(context, MainActivity::class.java) @@ -344,7 +333,7 @@ class DownloadWorker( NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID ) - if (wasQuickDownloaded){ + if (updateDownloadItem(downloadItem, infoUtil, dao, resultDao)){ runCatching { setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false)) runBlocking { @@ -398,10 +387,30 @@ class DownloadWorker( ) } } - return Result.success() } + private fun updateDownloadItem(downloadItem: DownloadItem, infoUtil: InfoUtil, dao: DownloadDao, resultDao: ResultDao) : Boolean { + var wasQuickDownloaded = false + if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){ + + runCatching { + setProgressAsync(workDataOf("progress" to 0, "output" to context.getString(R.string.updating_download_data), "id" to downloadItem.id, "log" to false)) + val info = infoUtil.getMissingInfo(downloadItem.url) + if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString() + if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString() + downloadItem.duration = info?.duration.toString() + downloadItem.website = info?.website.toString() + if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString() + runBlocking { + wasQuickDownloaded = resultDao.getCountInt() == 0 + dao.update(downloadItem) + } + } + } + return wasQuickDownloaded + } + override fun onStopped() { YoutubeDL.getInstance().destroyProcessById(itemId.toInt().toString()) super.onStopped() diff --git a/app/src/main/res/drawable/baseline_network_locked_24.xml b/app/src/main/res/drawable/baseline_network_locked_24.xml new file mode 100644 index 00000000..85025449 --- /dev/null +++ b/app/src/main/res/drawable/baseline_network_locked_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index b9584b19..fa1fb8cb 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -95,6 +95,7 @@ bn de es + fa fr in it diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0cd22a02..9c3dc186 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,11 +50,11 @@ Maximum download rate (in bytes per second), e.g. 50K or 4.2M Use aria2c as downloader instead of the default Processing - Adds subtitles in the video + Add subtitles in the video Thumbnail Covers - Uses the thumbnail as cover art + Use the thumbnail as cover art Chapters in Videos - Marks YouTube / SponsorBlock segments as chapters for the video + Mark YouTube / SponsorBlock segments as chapters for the video Save Thumbnail Save the thumbnail in the download folder Audio Format @@ -66,13 +66,13 @@ Command Downloads Notification showing the progress of downloading using a yt-dlp command item(s) left - History empty. + History is empty. Deleting URL Link copied to clipboard Use API Key Faster results from YouTube - Announces new versions of the app when it is opened + Announce new versions of the app when it is opened Downloads Remove Deleted Remove Duplicates @@ -260,4 +260,6 @@ Backup file saved in the downloads folder Appearance Backup And Restore + Use the specified HTTP/HTTPS/SOCKS proxy + Socks5 Proxy URL \ No newline at end of file diff --git a/app/src/main/res/xml/downloading_preferences.xml b/app/src/main/res/xml/downloading_preferences.xml index 4150ba7e..bdef0624 100644 --- a/app/src/main/res/xml/downloading_preferences.xml +++ b/app/src/main/res/xml/downloading_preferences.xml @@ -35,6 +35,13 @@ app:summary="@string/download_over_metered_networks_summary" app:title="@string/download_over_metered_networks" /> + + + @@ -13,12 +14,12 @@ - + - + - +