diff --git a/app/build.gradle b/app/build.gradle index 78502114..4db043aa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,12 +11,12 @@ def properties = new Properties() def versionMajor = 1 def versionMinor = 7 def versionPatch = 3 -def versionBuild = 0 // bump for dogfood builds, public betas, etc. -def versionExt = "" +def versionBuild = 1 // bump for dogfood builds, public betas, etc. +def versionExt = ".${versionBuild}" -if (versionBuild > 0){ - versionExt = ".${versionBuild}-beta" -} +//if (versionBuild > 0){ +// versionExt = ".${versionBuild}-beta" +//} android { namespace 'com.deniscerri.ytdlnis' @@ -117,6 +117,13 @@ android { composeOptions { kotlinCompilerExtensionVersion composeVer } + + dependenciesInfo { + // Disables dependency metadata when building APKs. + includeInApk = false + // Disables dependency metadata when building Android App Bundles. + includeInBundle = false + } } 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 dd6fe092..9bd915b7 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 @@ -216,6 +216,9 @@ interface DownloadDao { @Query("Select url from downloads where status in (:status)") fun getURLsByStatus(status: List) : List + @Query("Select url from downloads where id in (:ids)") + fun getURLsByID(ids: List) : List + @Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)") suspend fun resetScheduleTimeForItems(list: List) @@ -243,4 +246,8 @@ interface DownloadDao { @Query("Update downloads set id=:newId where id=:id") suspend fun updateDownloadID(id: Long, newId: Long) + + + @Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in (:statuses) ORDER BY id DESC") + fun getIDsBetweenTwoItems(item1: Long, item2: Long, statuses: List) : List } \ No newline at end of file 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 e5f8d480..7b9e98ef 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 @@ -21,6 +21,7 @@ import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItemSimple +import com.deniscerri.ytdlnis.util.Extensions.toListString import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.work.DownloadWorker import kotlinx.coroutines.flow.Flow @@ -63,10 +64,6 @@ class DownloadRepository(private val downloadDao: DownloadDao) { Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing } - private fun List.toListString() : List{ - return this.map { it.toString() } - } - suspend fun insert(item: DownloadItem) : Long { return downloadDao.insert(item) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt index 2ffa2dff..ec5dea34 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt @@ -9,6 +9,7 @@ import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver import kotlinx.coroutines.flow.Flow +import java.util.Calendar class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) { val items : Flow> = observeSourcesDao.getAllSourcesFlow() @@ -74,4 +75,47 @@ class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) ) } + fun calculateNextTime(item: ObserveSourcesItem) : Long { + val c = Calendar.getInstance() + + val hourMin = Calendar.getInstance() + hourMin.timeInMillis = item.everyTime + + c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY)) + c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE)) + + if (item.everyNr == 0) item.everyNr = 1 + + when(item.everyCategory){ + EveryCategory.DAY -> { + c.add(Calendar.DAY_OF_MONTH, item.everyNr) + } + EveryCategory.WEEK -> { + if(item.everyWeekDay.isEmpty()){ + c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr) + }else{ + val weekDayNr = c.get(Calendar.DAY_OF_WEEK) + val followingWeekDay = item.everyWeekDay.firstOrNull { it.toInt() > weekDayNr } + if (followingWeekDay == null){ + c.add(Calendar.DAY_OF_MONTH, item.everyWeekDay.minBy { it.toInt() }.toInt() + (7 - weekDayNr)) + item.everyNr-- + }else{ + c.add(Calendar.DAY_OF_MONTH, followingWeekDay.toInt() - weekDayNr) + } + + if (item.everyNr > 1){ + c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr) + } + } + } + EveryCategory.MONTH -> { + c.add(Calendar.MONTH, item.everyNr) + c.set(Calendar.DAY_OF_MONTH, item.everyMonthDay) + } + } + + + return c.timeInMillis + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt index b1784046..aac001af 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt @@ -28,7 +28,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a private val repository: CookieRepository val items: LiveData> - private val cookieHeader = + val cookieHeader = "# Netscape HTTP Cookie File\n" + "# WebView Generated by the YTDLnis app\n" + "# This is a generated file! Do not edit." 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 c7d3d2f1..8a640305 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 @@ -940,5 +940,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel return dao.getURLsByStatus(list.map { it.toString() }) } + fun getURLsByIds(list: List) : List { + return dao.getURLsByID(list) + } + + fun getIDsBetweenTwoItems(item1: Long, item2: Long, statuses: List) : List { + return dao.getIDsBetweenTwoItems(item1, item2, statuses) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt index f7ff0d82..495999cf 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt @@ -75,68 +75,59 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie repository.update(item) } + fun cancelObservationTaskByID(id : Long){ + repository.cancelObservationTaskByID(application, id) + } + private fun observeTask(it: ObserveSourcesItem){ + cancelObservationTaskByID(it.id) + val id = it.id val c = Calendar.getInstance() - val date = Calendar.getInstance() - date.timeInMillis = it.startsTime - val hourMin = Calendar.getInstance() - hourMin.timeInMillis = it.everyTime + + val date = Calendar.getInstance() + date.timeInMillis = it.startsTime + val hourMin = Calendar.getInstance() + hourMin.timeInMillis = it.everyTime c.set(Calendar.DAY_OF_MONTH, date.get(Calendar.DAY_OF_MONTH)) c.set(Calendar.MONTH, date.get(Calendar.MONTH)) c.set(Calendar.YEAR, date.get(Calendar.YEAR)) c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY)) c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE)) - - repository.cancelObservationTaskByID(application, id) - val intent = Intent(application, ObserveAlarmReceiver::class.java) intent.putExtra("id", id) - if (it.everyNr == 0) it.everyNr = 1 when(it.everyCategory){ - ObserveSourcesRepository.EveryCategory.DAY -> { - alarmManager.setExact( - AlarmManager.RTC, - c.timeInMillis, - PendingIntent.getBroadcast(application, it.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - ) - } + ObserveSourcesRepository.EveryCategory.DAY -> {} ObserveSourcesRepository.EveryCategory.WEEK -> { - if (it.everyWeekDay.isNotEmpty()){ - val weekDayID = c.get(Calendar.DAY_OF_WEEK).toString() - val followingWeekDay = (it.everyWeekDay.firstOrNull { it.toInt() > weekDayID.toInt() } ?: it.everyWeekDay.minBy { it.toInt() }).toInt() - c.set(Calendar.DAY_OF_WEEK, followingWeekDay) - if(c.timeInMillis < System.currentTimeMillis()){ - c.add(Calendar.DAY_OF_MONTH, 7) - } + val weekDayNr = c.get(Calendar.DAY_OF_WEEK) + val followingWeekDay = it.everyWeekDay.firstOrNull { it.toInt() >= weekDayNr } + if (followingWeekDay == null){ + c.add(Calendar.DAY_OF_MONTH, it.everyWeekDay.minBy { it.toInt() }.toInt() + (7 - weekDayNr)) + }else{ + c.add(Calendar.DAY_OF_MONTH, followingWeekDay.toInt() - weekDayNr) } - - alarmManager.setExact( - AlarmManager.RTC, - c.timeInMillis, - PendingIntent.getBroadcast(application, it.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - ) } ObserveSourcesRepository.EveryCategory.MONTH -> { val theMonthIndex = Month.values().indexOf(it.startsMonth) val currentMonthIndex = c.get(Calendar.MONTH) if (theMonthIndex != currentMonthIndex){ c.set(Calendar.MONTH, theMonthIndex) - if (c.timeInMillis < System.currentTimeMillis()){ + if (c.timeInMillis < Calendar.getInstance().timeInMillis){ c.add(Calendar.YEAR, 1) } } - alarmManager.setExact( - AlarmManager.RTC, - c.timeInMillis, - PendingIntent.getBroadcast(application, it.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - ) } } + alarmManager.setExact( + AlarmManager.RTC, + c.timeInMillis, + PendingIntent.getBroadcast(application, it.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + ) + } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt index e81eeb9a..cd435f8e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt @@ -26,7 +26,7 @@ class ObserveAlarmReceiver : BroadcastReceiver() { .setInputData(Data.Builder().putLong("id", sourceID).build()) WorkManager.getInstance(p0!!).enqueueUniqueWork( - sourceID.toString(), + "OBSERVE$sourceID", ExistingWorkPolicy.REPLACE, workRequest.build() ) 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 2d26548a..8ab63b6a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -500,7 +500,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti val url = checkClipboard() url?.apply { - combinedList.add(0, SearchSuggestionItem(this.joinToString("\n"), SearchSuggestionType.CLIPBOARD)) + if (this.isNotEmpty()){ + combinedList.add(0, SearchSuggestionItem(this.joinToString("\n"), SearchSuggestionType.CLIPBOARD)) + } } @@ -845,7 +847,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti return kotlin.runCatching { val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager val clip = clipboard.primaryClip!!.getItemAt(0).text - return clip.split("\r\n").filter { Patterns.WEB_URL.matcher(it).matches() } + return clip.split("\r","\n").map { it.trim() }.filter { Patterns.WEB_URL.matcher(it).matches() } }.getOrNull() } @@ -873,27 +875,36 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti } - override fun onSearchSuggestionAdd(text: String) { - val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == text } - if (present == null) { - val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip - chip.text = text - chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK)) - chip.setOnClickListener { - if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE - queriesChipGroup!!.removeView(chip) + override fun onSearchSuggestionAdd(t: String) { + val items = t.split("\n") + + items.forEach {text -> + val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == text } + if (present == null) { + val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip + chip.text = text + chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK)) + chip.setOnClickListener { + if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE + queriesChipGroup!!.removeView(chip) + } + queriesChipGroup!!.addView(chip) } - queriesChipGroup!!.addView(chip) + } + + searchView!!.editText.setText("") if (queriesChipGroup!!.childCount == 0) queriesConstraint!!.visibility = View.GONE else queriesConstraint!!.visibility = View.VISIBLE - searchView!!.editText.setText("") + val clipBoardItem = searchSuggestionsRecyclerView?.layoutManager?.findViewByPosition(0) clipBoardItem?.apply { if ((this as ConstraintLayout).findViewById(R.id.suggestion_text).text == getString(R.string.link_you_copied)){ searchSuggestionsAdapter?.notifyItemRemoved(0) } } + + } override fun onSearchSuggestionLongClick(text: String, position: Int) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt index 84ed7bfc..4545f4df 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt @@ -22,6 +22,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.util.Extensions.dp +import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView @@ -65,17 +66,8 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val thumbnail = card.findViewById(R.id.image_view) // THUMBNAIL ---------------------------------- - if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ - val imageURL = item.thumb - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) - }else{ - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } + val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue") + uiHandler.post { thumbnail.loadThumbnail(hideThumb, item.thumb) } // PROGRESS BAR ---------------------------------------------------- val progressBar = card.findViewById(R.id.progress) @@ -168,7 +160,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val value = animation.animatedValue as Int pauseButton.cornerRadius = value pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) - pauseButton.contentDescription = activity.getString(R.string.start) + pauseButton.contentDescription = activity.getString(R.string.resume) pauseButton.tag = ActiveDownloadAction.Resume pauseButton.isEnabled = true } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt index 3f9668dd..814012bd 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository +import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail import com.deniscerri.ytdlnis.util.Extensions.popup import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton @@ -62,17 +63,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac val thumbnail = card.findViewById(R.id.image_view) // THUMBNAIL ---------------------------------- - if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ - val imageURL = item!!.thumb - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) - }else{ - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } + val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue") + uiHandler.post { thumbnail.loadThumbnail(hideThumb, item!!.thumb) } // PROGRESS BAR ---------------------------------------------------- val progressBar = card.findViewById(R.id.progress) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt index 01799569..f94eb730 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail import com.deniscerri.ytdlnis.util.Extensions.popup import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton @@ -63,17 +64,8 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener val thumbnail = card.findViewById(R.id.downloads_image_view) // THUMBNAIL ---------------------------------- - if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ - val imageURL = item.thumb - if (!imageURL.isNullOrBlank()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) - }else{ - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } + val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home") + uiHandler.post { thumbnail.loadThumbnail(hideThumb, item.thumb) } val duration = card.findViewById(R.id.duration) duration.text = item.duration diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt index f8206899..4d0b2366 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt @@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItemSimple import com.deniscerri.ytdlnis.database.repository.DownloadRepository +import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail import com.deniscerri.ytdlnis.util.Extensions.popup import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton @@ -28,12 +29,12 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: ) { private val onItemClickListener: OnItemClickListener private val activity: Activity - val checkedItems: ArrayList + val checkedItems: MutableSet var inverted: Boolean private val sharedPreferences: SharedPreferences init { - checkedItems = ArrayList() + checkedItems = mutableSetOf() this.onItemClickListener = onItemClickListener this.activity = activity this.inverted = false @@ -65,16 +66,8 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val thumbnail = card.findViewById(R.id.downloads_image_view) // THUMBNAIL ---------------------------------- - if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ - val imageURL = item.thumb - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } - }else{ - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } + val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue") + uiHandler.post { thumbnail.loadThumbnail(hideThumb, item.thumb) } val duration = card.findViewById(R.id.duration) duration.text = item.duration @@ -180,6 +173,14 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: notifyDataSetChanged() } + @SuppressLint("NotifyDataSetChanged") + fun checkMultipleItems(list: List){ + checkedItems.clear() + inverted = false + checkedItems.addAll(list) + notifyDataSetChanged() + } + @SuppressLint("NotifyDataSetChanged") fun invertSelected() { inverted = !inverted diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt index e39e4fc8..646b0fed 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt @@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail import com.deniscerri.ytdlnis.util.Extensions.popup import com.google.android.material.card.MaterialCardView import com.google.android.material.color.MaterialColors @@ -89,17 +90,8 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit val thumbnail = card.findViewById(R.id.downloads_image_view) // THUMBNAIL ---------------------------------- - if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")){ - val imageURL = item!!.thumb - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) - }else{ - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } + val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads") + uiHandler.post { thumbnail.loadThumbnail(hideThumb, item!!.thumb) } // TITLE ---------------------------------- val itemTitle = card.findViewById(R.id.downloads_title) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt index 0dfd561b..45e81773 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt @@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail import com.deniscerri.ytdlnis.util.Extensions.popup import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView @@ -62,16 +63,8 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) val thumbnail = card.findViewById(R.id.result_image_view) // THUMBNAIL ---------------------------------- - if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ - val imageURL = video!!.thumb - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } - }else{ - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } + val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home") + uiHandler.post { thumbnail.loadThumbnail(hideThumb, video!!.thumb) } // TITLE ---------------------------------- val videoTitle = card.findViewById(R.id.result_title) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt index 5ee5390c..1325db32 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt @@ -57,14 +57,12 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity: override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView - val uiHandler = Handler(Looper.getMainLooper()) card.popup() if (item == null) return card.tag = item.url holder.itemView.tag = item.url - val thumbnail = card.findViewById(R.id.result_image_view) // TITLE ---------------------------------- val itemTitle = card.findViewById(R.id.title) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/PlaylistAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/PlaylistAdapter.kt index c3a576c6..e3c7470e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/PlaylistAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/PlaylistAdapter.kt @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.ResultItem +import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail import com.deniscerri.ytdlnis.util.Extensions.popup import com.squareup.picasso.Picasso import java.util.* @@ -62,17 +63,8 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi val thumbnail = card.findViewById(R.id.downloads_image_view) // THUMBNAIL ---------------------------------- - if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ - val imageURL = item!!.thumb - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } - thumbnail.setColorFilter(Color.argb(95, 0, 0, 0)) - }else{ - uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - } + val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home") + uiHandler.post { thumbnail.loadThumbnail(hideThumb, item!!.thumb) } card.findViewById(R.id.title).text = item!!.title card.findViewById(R.id.author).text = item.author 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 c2b33c05..9212b045 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 @@ -9,6 +9,7 @@ import android.content.SharedPreferences import android.content.res.Configuration import android.graphics.Canvas import android.graphics.Color +import android.os.Build import android.os.Bundle import android.text.format.DateFormat import android.util.DisplayMetrics @@ -32,6 +33,7 @@ import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.Format +import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel @@ -73,6 +75,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure private lateinit var count : TextView private lateinit var sharedPreferences: SharedPreferences + private lateinit var currentDownloadIDs: List + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] @@ -81,6 +85,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java] sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) infoUtil = InfoUtil(requireContext()) + + currentDownloadIDs = arguments?.getLongArray("currentDownloadIDs")?.toList() ?: listOf() } @SuppressLint("RestrictedApi", "NotifyDataSetChanged") @@ -137,6 +143,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure lifecycleScope.launch { withContext(Dispatchers.IO){ + downloadViewModel.deleteAllWithID(currentDownloadIDs) downloadViewModel.downloadProcessingDownloads(cal.timeInMillis) } @@ -153,6 +160,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure download.isEnabled = false lifecycleScope.launch { withContext(Dispatchers.IO){ + downloadViewModel.deleteAllWithID(currentDownloadIDs) downloadViewModel.downloadProcessingDownloads() } dismiss() @@ -166,6 +174,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> lifecycleScope.launch{ withContext(Dispatchers.IO){ + downloadViewModel.deleteAllWithID(currentDownloadIDs) downloadViewModel.moveProcessingToSavedCategory() } dismiss() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt index 2edb16bb..ee3933a6 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt @@ -94,6 +94,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() { private lateinit var endsAfterNr: TextInputLayout private lateinit var retryMissingDownloads: MaterialSwitch private lateinit var getOnlyNewUploads: MaterialSwitch + private lateinit var resetProcessedLinks: MaterialSwitch override fun onCreate(savedInstanceState: Bundle?) { @@ -252,6 +253,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() { endsAfterNr = view.findViewById(R.id.after_nr) retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads) getOnlyNewUploads = view.findViewById(R.id.get_new_uploads) + resetProcessedLinks = view.findViewById(R.id.reset_processed_links) okButton = view.findViewById(R.id.okButton) title.editText!!.setText(currentItem?.name ?: "") @@ -446,6 +448,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() { retryMissingDownloads.isChecked = currentItem?.retryMissingDownloads ?: false getOnlyNewUploads.isChecked = currentItem?.getOnlyNewUploads ?: false + resetProcessedLinks.isVisible = currentItem != null if (currentItem != null) okButton.text = getString(R.string.update) okButton.setOnClickListener { @@ -481,7 +484,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() { 0, getOnlyNewUploads.isChecked, retryMissingDownloads.isChecked, - mutableListOf() + if (resetProcessedLinks.isChecked) mutableListOf() else currentItem?.alreadyProcessedLinks ?: mutableListOf() ) withContext(Dispatchers.IO){ observeSourcesViewModel.insert(observeItem) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsDialog.kt index 8f70d14d..50eb5d2b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/SelectPlaylistItemsDialog.kt @@ -51,8 +51,8 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.FullScreenDialogTheme) - downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] - resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] + downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] + resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java] } override fun onCreateView( @@ -173,6 +173,7 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL ok = toolbar.menu.getItem(0) ok.isEnabled = false ok.setOnMenuItemClickListener { + ok.isEnabled = false lifecycleScope.launch(Dispatchers.IO) { val checkedItems = listAdapter.getCheckedItems() val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) } @@ -196,11 +197,11 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL downloadItems.add(i) } - CoroutineScope(Dispatchers.IO).launch { - downloadViewModel.insertToProcessing(downloadItems) - } + downloadViewModel.insertToProcessing(downloadItems) - findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2) + withContext(Dispatchers.Main){ + findNavController().navigate(R.id.downloadMultipleBottomSheetDialog) + } } dismiss() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/CancelledDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/CancelledDownloadsFragment.kt index ba1bb5b6..0e4d7e74 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/CancelledDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/CancelledDownloadsFragment.kt @@ -17,6 +17,7 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.os.bundleOf +import androidx.core.view.children import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -33,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode +import com.deniscerri.ytdlnis.util.Extensions.toListString import com.deniscerri.ytdlnis.util.UiUtil import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.color.MaterialColors @@ -152,6 +154,22 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic override fun onCardSelect(isChecked: Boolean, position: Int) { lifecycleScope.launch { val selectedObjects = adapter.getSelectedObjectsCount(totalSize) + if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) + actionMode?.apply { + if (selectedObjects == 0){ + this.finish() + }else{ + this.title = "$selectedObjects ${getString(R.string.selected)}" + if (selectedObjects == 2){ + val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString()) + } + this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty() + } + } + } + if (isChecked) { if (actionMode == null){ actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) @@ -200,21 +218,28 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic item: MenuItem? ): Boolean { return when (item!!.itemId) { + R.id.select_between -> { + lifecycleScope.launch { + val selectedIDs = getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString()) + }.toMutableList() + idsInMiddle.addAll(selectedIDs) + if (idsInMiddle.isNotEmpty()){ + adapter.checkMultipleItems(idsInMiddle) + actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}" + } + mode?.menu?.findItem(R.id.select_between)?.isVisible = false + } + true + } R.id.delete_results -> { val deleteDialog = MaterialAlertDialogBuilder(requireContext()) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Cancelled)) - } - }else{ - adapter.checkedItems.toList() - } - + val selectedObjects = getSelectedIDs() adapter.clearCheckedItems() downloadViewModel.deleteAllWithID(selectedObjects) actionMode?.finish() @@ -225,21 +250,15 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic } R.id.redownload -> { lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Cancelled)) - } - }else{ - adapter.checkedItems.toList() - } - + val selectedObjects = getSelectedIDs() if (preferences.getBoolean("download_card", true)){ withContext(Dispatchers.IO){ downloadViewModel.addDownloadsToProcessing(selectedObjects) } withContext(Dispatchers.Main){ - findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2) + val bundle = Bundle() + bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray()) + findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle) } }else{ withContext(Dispatchers.IO) { @@ -266,6 +285,17 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic if (selectedObjects == 0) actionMode?.finish() true } + R.id.copy_urls -> { + lifecycleScope.launch { + val selectedObjects = getSelectedIDs() + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByIds(selectedObjects) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + actionMode?.finish() + } + true + } else -> false } } @@ -274,6 +304,17 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic actionMode = null adapter.clearCheckedItems() } + + suspend fun getSelectedIDs() : List{ + return if (adapter.inverted || adapter.checkedItems.isEmpty()){ + withContext(Dispatchers.IO){ + downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems.toList(), listOf( + DownloadRepository.Status.Cancelled)) + } + }else{ + adapter.checkedItems.toList() + } + } } private var simpleCallback: ItemTouchHelper.SimpleCallback = diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt index ac7cd3c3..4ba9af7e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt @@ -123,40 +123,36 @@ class DownloadQueueMainFragment : Fragment(){ lifecycleScope.launch { downloadViewModel.activeDownloadsCount.collectLatest { tabLayout.getTabAt(0)?.apply { - if (it == 0) removeBadge() - else createBadge(it) + createBadge(it) } } } lifecycleScope.launch { downloadViewModel.queuedDownloadsCount.collectLatest { tabLayout.getTabAt(1)?.apply { - if (it == 0) removeBadge() - else createBadge(it) + createBadge(it) } } } lifecycleScope.launch { downloadViewModel.cancelledDownloadsCount.collectLatest { tabLayout.getTabAt(2)?.apply { - if (it == 0) removeBadge() - else createBadge(it) + createBadge(it) } } } lifecycleScope.launch { downloadViewModel.erroredDownloadsCount.collectLatest { tabLayout.getTabAt(3)?.apply { - if (it == 0) removeBadge() - else createBadge(it) + createBadge(it) } } } lifecycleScope.launch { downloadViewModel.savedDownloadsCount.collectLatest { tabLayout.getTabAt(4)?.apply { - if (it == 0) removeBadge() - else createBadge(it) + removeBadge() + if (it > 0) createBadge(it) } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ErroredDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ErroredDownloadsFragment.kt index aea7147b..eddc7c86 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ErroredDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ErroredDownloadsFragment.kt @@ -34,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode +import com.deniscerri.ytdlnis.util.Extensions.toListString import com.deniscerri.ytdlnis.util.UiUtil import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.color.MaterialColors @@ -154,18 +155,19 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL override fun onCardSelect(isChecked: Boolean, position: Int) { lifecycleScope.launch { val selectedObjects = adapter.getSelectedObjectsCount(totalSize) - if (isChecked) { - if (actionMode == null){ - actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) - - }else{ - actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}" - } - } - else { - actionMode?.title = "$selectedObjects ${getString(R.string.selected)}" + if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) + actionMode?.apply { if (selectedObjects == 0){ - actionMode?.finish() + this.finish() + }else{ + actionMode?.title = "$selectedObjects ${getString(R.string.selected)}" + if(selectedObjects == 2){ + val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Error).toListString()) + } + this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty() + } } } } @@ -207,20 +209,28 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL item: MenuItem? ): Boolean { return when (item!!.itemId) { + R.id.select_between -> { + lifecycleScope.launch { + val selectedIDs = getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString()) + }.toMutableList() + idsInMiddle.addAll(selectedIDs) + if (idsInMiddle.isNotEmpty()){ + adapter.checkMultipleItems(idsInMiddle) + actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}" + } + mode?.menu?.findItem(R.id.select_between)?.isVisible = false + } + true + } R.id.delete_results -> { val deleteDialog = MaterialAlertDialogBuilder(requireContext()) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Error)) - } - }else{ - adapter.checkedItems.toList() - } + val selectedObjects = getSelectedIDs() adapter.clearCheckedItems() downloadViewModel.deleteAllWithID(selectedObjects) actionMode?.finish() @@ -231,21 +241,16 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL } R.id.redownload -> { lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Error)) - } - }else{ - adapter.checkedItems.toList() - } + val selectedObjects = getSelectedIDs() if (preferences.getBoolean("download_card", true)){ withContext(Dispatchers.IO){ downloadViewModel.addDownloadsToProcessing(selectedObjects) } withContext(Dispatchers.Main){ - findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2) + val bundle = Bundle() + bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray()) + findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle) } }else{ withContext(Dispatchers.IO) { @@ -270,6 +275,17 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL if (selectedObjects == 0) actionMode?.finish() true } + R.id.copy_urls -> { + lifecycleScope.launch { + val selectedObjects = getSelectedIDs() + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByIds(selectedObjects) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + actionMode?.finish() + } + true + } else -> false } } @@ -278,6 +294,17 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL actionMode = null adapter.clearCheckedItems() } + + suspend fun getSelectedIDs() : List{ + return if (adapter.inverted || adapter.checkedItems.isEmpty()){ + withContext(Dispatchers.IO){ + downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems.toList(), listOf( + DownloadRepository.Status.Error)) + } + }else{ + adapter.checkedItems.toList() + } + } } private var simpleCallback: ItemTouchHelper.SimpleCallback = diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt index ac192b8b..ec44155a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt @@ -34,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode +import com.deniscerri.ytdlnis.util.Extensions.toListString import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.UiUtil @@ -180,27 +181,26 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi override fun onCardSelect(isChecked: Boolean, position: Int) { lifecycleScope.launch { val selectedObjects = adapter.getSelectedObjectsCount(totalSize) + if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) val now = System.currentTimeMillis() - if (isChecked) { - if (actionMode == null){ - actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) - }else{ - - actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}" - } - } - else { - actionMode?.title = "$selectedObjects ${getString(R.string.selected)}" + actionMode?.apply { if (selectedObjects == 0){ - actionMode?.finish() - } - } - if (actionMode != null){ - actionMode!!.menu.getItem(2).isVisible = withContext(Dispatchers.IO){ - downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems, adapter.inverted, now) - } + this.finish() + }else{ + this.title = "$selectedObjects ${getString(R.string.selected)}" + this.menu.findItem(R.id.download).isVisible = withContext(Dispatchers.IO){ + downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems.toList(), adapter.inverted, now) + } - actionMode!!.menu.getItem(1).isVisible = position > 0 + this.menu.findItem(R.id.up).isVisible = position > 0 + if(selectedObjects == 2){ + val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused).toListString()) + } + this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty() + } + } } } } @@ -250,20 +250,28 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi item: MenuItem? ): Boolean { return when (item!!.itemId) { + R.id.select_between -> { + lifecycleScope.launch { + val selectedIDs = getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString()) + }.toMutableList() + idsInMiddle.addAll(selectedIDs) + if (idsInMiddle.isNotEmpty()){ + adapter.checkMultipleItems(idsInMiddle) + actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}" + } + mode?.menu?.findItem(R.id.select_between)?.isVisible = false + } + true + } R.id.delete_results -> { val deleteDialog = MaterialAlertDialogBuilder(requireContext()) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)) - } - }else{ - adapter.checkedItems.toList() - } + val selectedObjects = getSelectedIDs() adapter.clearCheckedItems() for (id in selectedObjects){ YoutubeDL.getInstance().destroyProcessById(id.toInt().toString()) @@ -280,14 +288,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi } R.id.download -> { lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)) - } - }else{ - adapter.checkedItems.toList() - } + val selectedObjects = getSelectedIDs() adapter.clearCheckedItems() for (id in selectedObjects){ WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString()) @@ -313,15 +314,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi } R.id.up -> { lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)) - } - }else{ - adapter.checkedItems.toList() - } - + val selectedObjects = getSelectedIDs() adapter.clearCheckedItems() withContext(Dispatchers.IO){ downloadViewModel.putAtTopOfQueue(selectedObjects) @@ -330,6 +323,17 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi } true } + R.id.copy_urls -> { + lifecycleScope.launch { + val selectedObjects = getSelectedIDs() + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByIds(selectedObjects) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + actionMode?.finish() + } + true + } else -> false } } @@ -338,6 +342,17 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi actionMode = null adapter.clearCheckedItems() } + + suspend fun getSelectedIDs() : List{ + return if (adapter.inverted || adapter.checkedItems.isEmpty()){ + withContext(Dispatchers.IO){ + downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems.toList(), listOf( + DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)) + } + }else{ + adapter.checkedItems.toList() + } + } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/SavedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/SavedDownloadsFragment.kt index 9db074d3..cd7bd55f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/SavedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/SavedDownloadsFragment.kt @@ -33,6 +33,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode +import com.deniscerri.ytdlnis.util.Extensions.toListString import com.deniscerri.ytdlnis.util.UiUtil import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.color.MaterialColors @@ -152,18 +153,19 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis override fun onCardSelect(isChecked: Boolean, position: Int) { lifecycleScope.launch { val selectedObjects = adapter.getSelectedObjectsCount(totalSize) - if (isChecked) { - if (actionMode == null){ - actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) - - }else{ - actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}" - } - } - else { - actionMode?.title = "$selectedObjects ${getString(R.string.selected)}" + if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) + actionMode?.apply { if (selectedObjects == 0){ - actionMode?.finish() + this.finish() + }else{ + actionMode?.title = "$selectedObjects ${getString(R.string.selected)}" + if(selectedObjects == 2){ + val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Saved).toListString()) + } + this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty() + } } } } @@ -201,20 +203,28 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis item: MenuItem? ): Boolean { return when (item!!.itemId) { + R.id.select_between -> { + lifecycleScope.launch { + val selectedIDs = getSelectedIDs().sortedBy { it } + val idsInMiddle = withContext(Dispatchers.IO){ + downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString()) + }.toMutableList() + idsInMiddle.addAll(selectedIDs) + if (idsInMiddle.isNotEmpty()){ + adapter.checkMultipleItems(idsInMiddle) + actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}" + } + mode?.menu?.findItem(R.id.select_between)?.isVisible = false + } + true + } R.id.delete_results -> { val deleteDialog = MaterialAlertDialogBuilder(requireContext()) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> lifecycleScope.launch { - val selectedObjects = if (adapter.inverted){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Saved)) - } - }else{ - adapter.checkedItems.toList() - } + val selectedObjects = getSelectedIDs() adapter.clearCheckedItems() for (id in selectedObjects){ downloadViewModel.deleteDownload(id) @@ -227,21 +237,16 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis } R.id.download -> { lifecycleScope.launch { - val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ - withContext(Dispatchers.IO){ - downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf( - DownloadRepository.Status.Saved)) - } - }else{ - adapter.checkedItems.toList() - } + val selectedObjects = getSelectedIDs() adapter.clearCheckedItems() if (preferences.getBoolean("download_card", true)){ withContext(Dispatchers.IO){ downloadViewModel.addDownloadsToProcessing(selectedObjects) } withContext(Dispatchers.Main){ - findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2) + val bundle = Bundle() + bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray()) + findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle) } }else{ withContext(Dispatchers.IO) { @@ -265,6 +270,17 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis if (selectedObjects == 0) actionMode?.finish() true } + R.id.copy_urls -> { + lifecycleScope.launch { + val selectedObjects = getSelectedIDs() + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByIds(selectedObjects) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + actionMode?.finish() + } + true + } else -> false } } @@ -273,6 +289,17 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis actionMode = null adapter.clearCheckedItems() } + + suspend fun getSelectedIDs() : List{ + return if (adapter.inverted || adapter.checkedItems.isEmpty()){ + withContext(Dispatchers.IO){ + downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems.toList(), listOf( + DownloadRepository.Status.Saved)) + } + }else{ + adapter.checkedItems.toList() + } + } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesFragment.kt index a3f1aa23..f1a8d1d5 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesFragment.kt @@ -185,7 +185,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener { builder.setNeutralButton( getString(android.R.string.copy) ) { dialog: DialogInterface?, which: Int -> - UiUtil.copyToClipboard(item.content, requireActivity()) + UiUtil.copyToClipboard(cookiesViewModel.cookieHeader + "\n" + item.content, requireActivity()) } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/ObserveSourcesFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/ObserveSourcesFragment.kt index bad3ec7a..4d23c5be 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/ObserveSourcesFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/ObserveSourcesFragment.kt @@ -130,6 +130,7 @@ class ObserveSourcesFragment : Fragment(), ObserveSourcesAdapter.OnItemClickList } override fun onItemSearch(item: ObserveSourcesItem) { runCatching { + observeSourcesViewModel.cancelObservationTaskByID(item.id) val intent = Intent(context, ObserveAlarmReceiver::class.java) intent.putExtra("id", item.id) requireActivity().sendBroadcast(intent) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt index 101c9418..d24b288b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt @@ -20,18 +20,22 @@ import android.view.ViewGroup import android.view.ViewOutlineProvider import android.view.animation.Interpolator import android.widget.EditText +import android.widget.ImageView import android.widget.TextView import androidx.annotation.Px import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.lifecycle.withStarted import androidx.recyclerview.widget.RecyclerView +import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.tabs.TabLayout import com.neo.highlight.core.Highlight import com.neo.highlight.util.listener.HighlightTextWatcher import com.neo.highlight.util.scheme.ColorScheme +import com.squareup.picasso.Picasso import kotlinx.coroutines.launch import me.zhanghai.android.fastscroll.FastScrollerBuilder import org.json.JSONObject @@ -215,10 +219,16 @@ object Extensions { } fun TabLayout.Tab.createBadge(nr: Int){ - this.orCreateBadge.apply { - number = nr - verticalOffset = 5 - horizontalOffset = 10 + removeBadge() + if (nr > 0) { + orCreateBadge.apply { + number = nr + verticalOffset = 3 + horizontalOffset = + if (nr < 10) 7 + else if (nr < 100) 10 + else 20 + } } } @@ -233,6 +243,26 @@ object Extensions { return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}" } + fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){ + if(!hideThumb){ + if (imageURL.isNotEmpty()) { + Picasso.get() + .load(imageURL) + .resize(1280, 0) + .onlyScaleDown() + .into(this) + + } else { + Picasso.get().load(R.color.black).into(this) + } + } + } + + + fun List.toListString() : List{ + return this.map { it.toString() } + } + fun List.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) } class CustomInterpolator : Interpolator { 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 ab79c055..61b0c673 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -761,7 +761,7 @@ class InfoUtil(private val context: Context) { formatProper.format_note = format.getString("format_note") }else{ if (!formatProper.format_note.endsWith("audio", true)){ - formatProper.format_note = "${format.getString("format_note")} audio" + formatProper.format_note = format.getString("format_note").uppercase().removeSuffix("AUDIO") + " AUDIO" } } } @@ -772,8 +772,8 @@ class InfoUtil(private val context: Context) { formatProper.tbr = formatProper.tbr + "k" } - if(formatProper.vcodec.isEmpty() || formatProper.vcodec == "null"){ - if(formatProper.acodec.isEmpty() || formatProper.acodec == "null"){ + if(formatProper.vcodec.isNullOrEmpty() || formatProper.vcodec == "null"){ + if(formatProper.acodec.isNullOrEmpty() || formatProper.acodec == "null"){ formatProper.vcodec = format.getStringByAny("video_ext", "ext").ifEmpty { "unknown" } } } @@ -1032,36 +1032,22 @@ class InfoUtil(private val context: Context) { } } - - - try { - val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##replaceMetadata.txt") - YoutubeDLRequest("").apply { - - if (downloadItem.playlistTitle.isNotBlank()){ - addCommands(listOf("--replace-in-metadata", "playlist", ".+", downloadItem.playlistTitle)) - runCatching { - if (downloadItem.playlistIndex != null){ - addOption("--parse-metadata", downloadItem.playlistIndex.toString() + ":%(playlist_index)s") - } - } + if (downloadItem.playlistTitle.isNotBlank()){ + request.addCommands(listOf("--replace-in-metadata", "video:playlist", ".+", downloadItem.playlistTitle)) + runCatching { + if (downloadItem.playlistIndex != null){ + request.addOption("--parse-metadata", downloadItem.playlistIndex.toString() + ":%(playlist_index)s") } - - if(downloadItem.title.isNotBlank()){ - addCommands(listOf("--replace-in-metadata", "title", ".+", downloadItem.title)) - } - - - if (downloadItem.author.isNotBlank()){ - addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author)) - } - - val configData = parseYTDLRequestString(this).replace(" \" \"", "") - config.writeText(configData) - request.addOption("--config", config.absolutePath) } - } catch (e: Exception) { - e.printStackTrace() + } + + if(downloadItem.title.isNotBlank()){ + request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(150))) + } + + + if (downloadItem.author.isNotBlank()){ + request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30))) } request.addOption("--parse-metadata", "uploader:(?P.+)(?: - Topic)$") @@ -1111,10 +1097,6 @@ class InfoUtil(private val context: Context) { if (sharedPreferences.getBoolean("write_description", false)){ request.addOption("--write-description") } - - filenameTemplate = filenameTemplate - .replace("%(uploader)s", "%(uploader,channel).30B") - .replace("%(title)s", "%(title).170B") } if (sharedPreferences.getBoolean("download_archive", false)){ 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 c29e5a4d..789558cf 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -219,6 +219,7 @@ class NotificationUtil(var context: Context) { } fun createDownloadFinished( + id: Long, title: String?, filepath: List?, res: Resources @@ -234,64 +235,75 @@ class NotificationUtil(var context: Context) { R.drawable.ic_launcher_foreground_large ) ) + .setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString()) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) .setContentText("") .setPriority(NotificationCompat.PRIORITY_MAX) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .clearActions() if (filepath != null){ try{ - val uri = filepath.first().runCatching { - DocumentFile.fromSingleUri(context, Uri.parse(filepath.first())).run{ - if (this?.exists() == true){ - this.uri - }else if (File(this@runCatching).exists()){ - FileProvider.getUriForFile(context, context.packageName + ".fileprovider", - File(this@runCatching)) - }else null - } - }.getOrNull() + val uris = filepath.mapNotNull { + runCatching { + DocumentFile.fromSingleUri(context, Uri.parse(it)).run{ + if (this?.exists() == true){ + this.uri + }else if (File(it).exists()){ + FileProvider.getUriForFile(context, context.packageName + ".fileprovider", + File(it)) + }else null + } + }.getOrNull() + } val openFileIntent = Intent() + val shareFileIntent = Intent() - if (uri != null){ + if (uris.isNotEmpty()){ openFileIntent.apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) action = Intent.ACTION_VIEW - data = uri + data = uris.first() + } + + shareFileIntent.apply { + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + action = Intent.ACTION_SEND_MULTIPLE + putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris)) + type = if (uris.size == 1) uris[0].let { context.contentResolver.getType(it) } ?: "media/*" else "*/*" + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) } } val openNotificationPendingIntent: PendingIntent = TaskStackBuilder.create(context).run { addNextIntentWithParentStack(openFileIntent) - getPendingIntent(0, + getPendingIntent(id.toInt(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } - - //share intent - val shareIntent = Intent(context, ShareFileActivity::class.java) - shareIntent.putExtra("path", filepath.toTypedArray()) - shareIntent.putExtra("notificationID", DOWNLOAD_FINISHED_NOTIFICATION_ID) val shareNotificationPendingIntent: PendingIntent = PendingIntent.getActivity( context, - 0, - shareIntent, - PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE + id.toInt(), + Intent.createChooser(shareFileIntent, res.getString(R.string.share)), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) notificationBuilder.addAction(0, res.getString(R.string.Open_File), openNotificationPendingIntent) notificationBuilder.addAction(0, res.getString(R.string.share), shareNotificationPendingIntent) }catch (_: Exception){} } - notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build()) + notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build()) } - fun createDownloadErrored(title: String?, - error: String?, - logID: Long?, - res: Resources + fun createDownloadErrored( + id: Long, + title: String?, + error: String?, + logID: Long?, + res: Resources ) { val notificationBuilder = getBuilder(DOWNLOAD_ERRORED_CHANNEL_ID) @@ -333,7 +345,7 @@ class NotificationUtil(var context: Context) { notificationBuilder.setContentIntent(errorPendingIntent) notificationBuilder.addAction(0, res.getString(R.string.logs), errorPendingIntent) } - notificationManager.notify(DOWNLOAD_ERRORED_NOTIFICATION_ID, notificationBuilder.build()) + notificationManager.notify(DOWNLOAD_ERRORED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build()) } @@ -422,7 +434,6 @@ class NotificationUtil(var context: Context) { fun cancelDownloadNotification(id: Int) { notificationManager.cancel(id) } - fun createMoveCacheFilesNotification(pendingIntent: PendingIntent?, downloadMiscChannelId: String): Notification { val notificationBuilder = getBuilder(downloadMiscChannelId) @@ -574,7 +585,7 @@ class NotificationUtil(var context: Context) { .createPendingIntent() notificationBuilder - .setContentTitle(resources.getString(R.string.finished_download_notification_channel_name)) + .setContentTitle(resources.getString(R.string.all_queries_finished)) .setSmallIcon(R.drawable.ic_launcher_foreground_large) .setLargeIcon( BitmapFactory.decodeResource( 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 73167270..0ee183e1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt @@ -103,7 +103,7 @@ class UpdateUtil(var context: Context) { d?.dismiss() } .setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> } - .setPositiveButton(context.resources.getString(R.string.update)) { _: DialogInterface?, _: Int -> + .setPositiveButton(context.resources.getString(R.string.update)) { d: DialogInterface?, _: Int -> runCatching { val releaseVersion = v.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) } if (releaseVersion == null){ @@ -140,8 +140,8 @@ class UpdateUtil(var context: Context) { } context.registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) + d?.dismiss() } - } val view = updateDialog.show() val textView = view.findViewById(android.R.id.message) @@ -176,7 +176,7 @@ class UpdateUtil(var context: Context) { LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT ) - layoutParams.setMargins(5, 5, 5, 0) + layoutParams.setMargins(10, 10, 10, 0) scrollView.layoutParams = layoutParams scrollView.addView(linearLayout) val releases = getGithubReleases() 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 fb622dcd..3f0fae86 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -267,7 +267,7 @@ class DownloadWorker( notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) notificationUtil.createDownloadFinished( - downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources + downloadItem.id, downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources ) if (wasQuickDownloaded && createResultItem){ @@ -326,7 +326,9 @@ class DownloadWorker( } notificationUtil.createDownloadErrored( - downloadItem.title.ifEmpty { downloadItem.url }, it.message, + downloadItem.id, + downloadItem.title.ifEmpty { downloadItem.url }, + it.message, downloadItem.logID, resources ) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/ObserveSourceWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/ObserveSourceWorker.kt index 9b62c5bc..58cf3ea7 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/ObserveSourceWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/ObserveSourceWorker.kt @@ -65,22 +65,20 @@ class ObserveSourceWorker( } } .filter { result -> - - val history = historyRepo.getAllByURL(result.url) - if (!item.retryMissingDownloads){ - //all items that are not present in history - history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } } - }else{ - //all items that are not already processed - if(item.alreadyProcessedLinks.isEmpty()){ - !history.map { it.url }.contains(result.url) && !item.alreadyProcessedLinks.contains(result.url) + val history = historyRepo.getAllByURL(result.url) + if (item.retryMissingDownloads){ + //all items that are not present in history + history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } } }else{ - !item.alreadyProcessedLinks.contains(result.url) + //all items that are not already processed + if(item.alreadyProcessedLinks.isEmpty()){ + !history.map { it.url }.contains(result.url) + }else{ + !item.alreadyProcessedLinks.contains(result.url) + } } } - } - val items = mutableListOf() res.forEach { @@ -208,47 +206,18 @@ class ObserveSourceWorker( repo.update(item) } - scheduleForNextTime(item) - return Result.success() - } - - private fun scheduleForNextTime(item: ObserveSourcesItem){ + //schedule for next time val alarmManager = context.getSystemService(AlarmManager::class.java) val intent = Intent(context, ObserveAlarmReceiver::class.java) intent.putExtra("id", item.id) - val c = Calendar.getInstance() - val hourMin = Calendar.getInstance() - hourMin.timeInMillis = item.everyTime - c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY)) - c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE)) - - when(item.everyCategory){ - ObserveSourcesRepository.EveryCategory.DAY -> { - c.add(Calendar.DAY_OF_MONTH, item.everyNr) - } - ObserveSourcesRepository.EveryCategory.WEEK -> { - if(item.everyWeekDay.isEmpty()){ - c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr) - }else{ - val weekDayID = c.get(Calendar.DAY_OF_WEEK).toString() - val followingWeekDay = (item.everyWeekDay.firstOrNull { it.toInt() > weekDayID.toInt() } ?: item.everyWeekDay.minBy { it.toInt() }).toInt() - c.set(Calendar.DAY_OF_WEEK, followingWeekDay) - if (item.everyNr > 1){ - c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr) - } - } - } - ObserveSourcesRepository.EveryCategory.MONTH -> { - c.add(Calendar.MONTH, item.everyNr) - c.set(Calendar.DAY_OF_MONTH, item.everyMonthDay) - } - } alarmManager.setExact( - AlarmManager.RTC, - c.timeInMillis, + AlarmManager.RTC_WAKEUP, + repo.calculateNextTime(item), PendingIntent.getBroadcast(context, item.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) ) + + return Result.success() } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_select_between.xml b/app/src/main/res/drawable/ic_select_between.xml new file mode 100644 index 00000000..3bdf74c5 --- /dev/null +++ b/app/src/main/res/drawable/ic_select_between.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/active_download_card.xml b/app/src/main/res/layout/active_download_card.xml index 5a9e8885..24b56f59 100644 --- a/app/src/main/res/layout/active_download_card.xml +++ b/app/src/main/res/layout/active_download_card.xml @@ -171,6 +171,7 @@ style="?attr/materialIconButtonFilledStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:contentDescription="@string/pause" android:layoutAnimation="@anim/popup_enter" app:backgroundTint="?attr/colorSurface" app:cornerRadius="15dp" diff --git a/app/src/main/res/layout/format_item.xml b/app/src/main/res/layout/format_item.xml index 62bd6da1..62d9d40e 100644 --- a/app/src/main/res/layout/format_item.xml +++ b/app/src/main/res/layout/format_item.xml @@ -63,35 +63,38 @@ + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/observe_sources_bottom_sheet.xml b/app/src/main/res/layout/observe_sources_bottom_sheet.xml index 3511d207..bbcbf156 100644 --- a/app/src/main/res/layout/observe_sources_bottom_sheet.xml +++ b/app/src/main/res/layout/observe_sources_bottom_sheet.xml @@ -500,6 +500,13 @@ android:checked="false" android:text="@string/retry_missing_downloads"/> + + diff --git a/app/src/main/res/menu/cancelled_downloads_menu_context.xml b/app/src/main/res/menu/cancelled_downloads_menu_context.xml index afbf3af9..941bd9df 100644 --- a/app/src/main/res/menu/cancelled_downloads_menu_context.xml +++ b/app/src/main/res/menu/cancelled_downloads_menu_context.xml @@ -3,6 +3,13 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity" > + + + + diff --git a/app/src/main/res/menu/queued_menu_context.xml b/app/src/main/res/menu/queued_menu_context.xml index 55d30337..0cb8e797 100644 --- a/app/src/main/res/menu/queued_menu_context.xml +++ b/app/src/main/res/menu/queued_menu_context.xml @@ -3,6 +3,13 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity" > + + + + diff --git a/app/src/main/res/menu/saved_downloads_menu_context.xml b/app/src/main/res/menu/saved_downloads_menu_context.xml index eecfffeb..a4a8077d 100644 --- a/app/src/main/res/menu/saved_downloads_menu_context.xml +++ b/app/src/main/res/menu/saved_downloads_menu_context.xml @@ -3,6 +3,14 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity" > + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 712ad99d..be1fa2a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -372,4 +372,7 @@ Text Size Add Back + All Queries have been processed + Select Items Between + Reset Already Processed Links \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/10731.txt b/fastlane/metadata/android/en-US/changelogs/10731.txt new file mode 100644 index 00000000..9830b53c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10731.txt @@ -0,0 +1,15 @@ +## What's New + +- Added ability to select all items between two selected items in download queue +- Fixed app only getting one URL when tapping "Link you copied" in search view even if you copied multiple links +- Fixed app crashing on playlist selector when sharing a playlist. Forgor to configure it with the new changes +- Fixed app not removing errored, cancelled, saved downloads after you queued them for re-download +- Added option to copy URLs of selected downloads in the download queue screen +- Made app show all finished and errored notifications, instead of the latest one +- Kinda fixed the app not giving you the correct path when sharing a file from the notification +- Fixed app crashing when loading soundcloud results. They had stupidly large thumbnails, they are resized now +- Added option to reset the recorded links in observe resources when you are trying to update it. From now on they wont get reset like before +- Fixed major bug of app adding extra quotes in the yt-dlp config, making it fail on titles with quotes in them + +Had to push this soon because of mostly the last error +