This commit is contained in:
zaednasr 2024-03-03 11:58:26 +01:00
parent a5bd545f52
commit b43bf26131
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
42 changed files with 615 additions and 407 deletions

View file

@ -11,12 +11,12 @@ def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 7 def versionMinor = 7
def versionPatch = 3 def versionPatch = 3
def versionBuild = 0 // bump for dogfood builds, public betas, etc. def versionBuild = 1 // bump for dogfood builds, public betas, etc.
def versionExt = "" def versionExt = ".${versionBuild}"
if (versionBuild > 0){ //if (versionBuild > 0){
versionExt = ".${versionBuild}-beta" // versionExt = ".${versionBuild}-beta"
} //}
android { android {
namespace 'com.deniscerri.ytdlnis' namespace 'com.deniscerri.ytdlnis'
@ -117,6 +117,13 @@ android {
composeOptions { composeOptions {
kotlinCompilerExtensionVersion composeVer kotlinCompilerExtensionVersion composeVer
} }
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
} }

View file

@ -216,6 +216,9 @@ interface DownloadDao {
@Query("Select url from downloads where status in (:status)") @Query("Select url from downloads where status in (:status)")
fun getURLsByStatus(status: List<String>) : List<String> fun getURLsByStatus(status: List<String>) : List<String>
@Query("Select url from downloads where id in (:ids)")
fun getURLsByID(ids: List<Long>) : List<String>
@Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)") @Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)")
suspend fun resetScheduleTimeForItems(list: List<Long>) suspend fun resetScheduleTimeForItems(list: List<Long>)
@ -243,4 +246,8 @@ interface DownloadDao {
@Query("Update downloads set id=:newId where id=:id") @Query("Update downloads set id=:newId where id=:id")
suspend fun updateDownloadID(id: Long, newId: Long) 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<String>) : List<Long>
} }

View file

@ -21,6 +21,7 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.work.DownloadWorker import com.deniscerri.ytdlnis.work.DownloadWorker
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -63,10 +64,6 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing
} }
private fun List<Status>.toListString() : List<String>{
return this.map { it.toString() }
}
suspend fun insert(item: DownloadItem) : Long { suspend fun insert(item: DownloadItem) : Long {
return downloadDao.insert(item) return downloadDao.insert(item)
} }

View file

@ -9,6 +9,7 @@ import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.util.Calendar
class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) { class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) {
val items : Flow<List<ObserveSourcesItem>> = observeSourcesDao.getAllSourcesFlow() val items : Flow<List<ObserveSourcesItem>> = 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
}
} }

View file

@ -28,7 +28,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
private val repository: CookieRepository private val repository: CookieRepository
val items: LiveData<List<CookieItem>> val items: LiveData<List<CookieItem>>
private val cookieHeader = val cookieHeader =
"# Netscape HTTP Cookie File\n" + "# Netscape HTTP Cookie File\n" +
"# WebView Generated by the YTDLnis app\n" + "# WebView Generated by the YTDLnis app\n" +
"# This is a generated file! Do not edit." "# This is a generated file! Do not edit."

View file

@ -940,5 +940,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
return dao.getURLsByStatus(list.map { it.toString() }) return dao.getURLsByStatus(list.map { it.toString() })
} }
fun getURLsByIds(list: List<Long>) : List<String> {
return dao.getURLsByID(list)
}
fun getIDsBetweenTwoItems(item1: Long, item2: Long, statuses: List<String>) : List<Long> {
return dao.getIDsBetweenTwoItems(item1, item2, statuses)
}
} }

View file

@ -75,68 +75,59 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
repository.update(item) repository.update(item)
} }
fun cancelObservationTaskByID(id : Long){
repository.cancelObservationTaskByID(application, id)
}
private fun observeTask(it: ObserveSourcesItem){ private fun observeTask(it: ObserveSourcesItem){
cancelObservationTaskByID(it.id)
val id = it.id val id = it.id
val c = Calendar.getInstance() val c = Calendar.getInstance()
val date = Calendar.getInstance()
date.timeInMillis = it.startsTime val date = Calendar.getInstance()
val hourMin = Calendar.getInstance() date.timeInMillis = it.startsTime
hourMin.timeInMillis = it.everyTime val hourMin = Calendar.getInstance()
hourMin.timeInMillis = it.everyTime
c.set(Calendar.DAY_OF_MONTH, date.get(Calendar.DAY_OF_MONTH)) c.set(Calendar.DAY_OF_MONTH, date.get(Calendar.DAY_OF_MONTH))
c.set(Calendar.MONTH, date.get(Calendar.MONTH)) c.set(Calendar.MONTH, date.get(Calendar.MONTH))
c.set(Calendar.YEAR, date.get(Calendar.YEAR)) c.set(Calendar.YEAR, date.get(Calendar.YEAR))
c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY)) c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY))
c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE)) c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
repository.cancelObservationTaskByID(application, id)
val intent = Intent(application, ObserveAlarmReceiver::class.java) val intent = Intent(application, ObserveAlarmReceiver::class.java)
intent.putExtra("id", id) intent.putExtra("id", id)
if (it.everyNr == 0) it.everyNr = 1
when(it.everyCategory){ when(it.everyCategory){
ObserveSourcesRepository.EveryCategory.DAY -> { 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.WEEK -> { ObserveSourcesRepository.EveryCategory.WEEK -> {
if (it.everyWeekDay.isNotEmpty()){ val weekDayNr = c.get(Calendar.DAY_OF_WEEK)
val weekDayID = c.get(Calendar.DAY_OF_WEEK).toString() val followingWeekDay = it.everyWeekDay.firstOrNull { it.toInt() >= weekDayNr }
val followingWeekDay = (it.everyWeekDay.firstOrNull { it.toInt() > weekDayID.toInt() } ?: it.everyWeekDay.minBy { it.toInt() }).toInt() if (followingWeekDay == null){
c.set(Calendar.DAY_OF_WEEK, followingWeekDay) c.add(Calendar.DAY_OF_MONTH, it.everyWeekDay.minBy { it.toInt() }.toInt() + (7 - weekDayNr))
if(c.timeInMillis < System.currentTimeMillis()){ }else{
c.add(Calendar.DAY_OF_MONTH, 7) 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 -> { ObserveSourcesRepository.EveryCategory.MONTH -> {
val theMonthIndex = Month.values().indexOf(it.startsMonth) val theMonthIndex = Month.values().indexOf(it.startsMonth)
val currentMonthIndex = c.get(Calendar.MONTH) val currentMonthIndex = c.get(Calendar.MONTH)
if (theMonthIndex != currentMonthIndex){ if (theMonthIndex != currentMonthIndex){
c.set(Calendar.MONTH, theMonthIndex) c.set(Calendar.MONTH, theMonthIndex)
if (c.timeInMillis < System.currentTimeMillis()){ if (c.timeInMillis < Calendar.getInstance().timeInMillis){
c.add(Calendar.YEAR, 1) 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)
)
} }

View file

@ -26,7 +26,7 @@ class ObserveAlarmReceiver : BroadcastReceiver() {
.setInputData(Data.Builder().putLong("id", sourceID).build()) .setInputData(Data.Builder().putLong("id", sourceID).build())
WorkManager.getInstance(p0!!).enqueueUniqueWork( WorkManager.getInstance(p0!!).enqueueUniqueWork(
sourceID.toString(), "OBSERVE$sourceID",
ExistingWorkPolicy.REPLACE, ExistingWorkPolicy.REPLACE,
workRequest.build() workRequest.build()
) )

View file

@ -500,7 +500,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
val url = checkClipboard() val url = checkClipboard()
url?.apply { 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 { return kotlin.runCatching {
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = clipboard.primaryClip!!.getItemAt(0).text 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() }.getOrNull()
} }
@ -873,27 +875,36 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
} }
override fun onSearchSuggestionAdd(text: String) { override fun onSearchSuggestionAdd(t: String) {
val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == text } val items = t.split("\n")
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip items.forEach {text ->
chip.text = text val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == text }
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK)) if (present == null) {
chip.setOnClickListener { val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE chip.text = text
queriesChipGroup!!.removeView(chip) 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 if (queriesChipGroup!!.childCount == 0) queriesConstraint!!.visibility = View.GONE
else queriesConstraint!!.visibility = View.VISIBLE else queriesConstraint!!.visibility = View.VISIBLE
searchView!!.editText.setText("")
val clipBoardItem = searchSuggestionsRecyclerView?.layoutManager?.findViewByPosition(0) val clipBoardItem = searchSuggestionsRecyclerView?.layoutManager?.findViewByPosition(0)
clipBoardItem?.apply { clipBoardItem?.apply {
if ((this as ConstraintLayout).findViewById<TextView>(R.id.suggestion_text).text == getString(R.string.link_you_copied)){ if ((this as ConstraintLayout).findViewById<TextView>(R.id.suggestion_text).text == getString(R.string.link_you_copied)){
searchSuggestionsAdapter?.notifyItemRemoved(0) searchSuggestionsAdapter?.notifyItemRemoved(0)
} }
} }
} }
override fun onSearchSuggestionLongClick(text: String, position: Int) { override fun onSearchSuggestionLongClick(text: String, position: Int) {

View file

@ -22,6 +22,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.Extensions.dp import com.deniscerri.ytdlnis.util.Extensions.dp
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
@ -65,17 +66,8 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val thumbnail = card.findViewById<ImageView>(R.id.image_view) val thumbnail = card.findViewById<ImageView>(R.id.image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
val imageURL = item.thumb uiHandler.post { thumbnail.loadThumbnail(hideThumb, 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) }
}
// PROGRESS BAR ---------------------------------------------------- // PROGRESS BAR ----------------------------------------------------
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress) val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
@ -168,7 +160,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val value = animation.animatedValue as Int val value = animation.animatedValue as Int
pauseButton.cornerRadius = value pauseButton.cornerRadius = value
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) 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.tag = ActiveDownloadAction.Resume
pauseButton.isEnabled = true pauseButton.isEnabled = true
} }

View file

@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository 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.Extensions.popup
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -62,17 +63,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
val thumbnail = card.findViewById<ImageView>(R.id.image_view) val thumbnail = card.findViewById<ImageView>(R.id.image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
val imageURL = item!!.thumb uiHandler.post { thumbnail.loadThumbnail(hideThumb, 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) }
}
// PROGRESS BAR ---------------------------------------------------- // PROGRESS BAR ----------------------------------------------------
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress) val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)

View file

@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel 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.Extensions.popup
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -63,17 +64,8 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view) val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")
val imageURL = item.thumb uiHandler.post { thumbnail.loadThumbnail(hideThumb, 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 duration = card.findViewById<TextView>(R.id.duration) val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration duration.text = item.duration

View file

@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.repository.DownloadRepository 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.Extensions.popup
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -28,12 +29,12 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
) { ) {
private val onItemClickListener: OnItemClickListener private val onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
val checkedItems: ArrayList<Long> val checkedItems: MutableSet<Long>
var inverted: Boolean var inverted: Boolean
private val sharedPreferences: SharedPreferences private val sharedPreferences: SharedPreferences
init { init {
checkedItems = ArrayList() checkedItems = mutableSetOf()
this.onItemClickListener = onItemClickListener this.onItemClickListener = onItemClickListener
this.activity = activity this.activity = activity
this.inverted = false this.inverted = false
@ -65,16 +66,8 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view) val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
val imageURL = item.thumb uiHandler.post { thumbnail.loadThumbnail(hideThumb, 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 duration = card.findViewById<TextView>(R.id.duration) val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration duration.text = item.duration
@ -180,6 +173,14 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
notifyDataSetChanged() notifyDataSetChanged()
} }
@SuppressLint("NotifyDataSetChanged")
fun checkMultipleItems(list: List<Long>){
checkedItems.clear()
inverted = false
checkedItems.addAll(list)
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun invertSelected() { fun invertSelected() {
inverted = !inverted inverted = !inverted

View file

@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel 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.Extensions.popup
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
@ -89,17 +90,8 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view) val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")){ val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")
val imageURL = item!!.thumb uiHandler.post { thumbnail.loadThumbnail(hideThumb, 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) }
}
// TITLE ---------------------------------- // TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.downloads_title) val itemTitle = card.findViewById<TextView>(R.id.downloads_title)

View file

@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel 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.Extensions.popup
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
@ -62,16 +63,8 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view) val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")
val imageURL = video!!.thumb uiHandler.post { thumbnail.loadThumbnail(hideThumb, 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) }
}
// TITLE ---------------------------------- // TITLE ----------------------------------
val videoTitle = card.findViewById<TextView>(R.id.result_title) val videoTitle = card.findViewById<TextView>(R.id.result_title)

View file

@ -57,14 +57,12 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity:
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
val card = holder.cardView val card = holder.cardView
val uiHandler = Handler(Looper.getMainLooper())
card.popup() card.popup()
if (item == null) return if (item == null) return
card.tag = item.url card.tag = item.url
holder.itemView.tag = item.url holder.itemView.tag = item.url
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
// TITLE ---------------------------------- // TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title) val itemTitle = card.findViewById<TextView>(R.id.title)

View file

@ -20,6 +20,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
import com.deniscerri.ytdlnis.util.Extensions.popup import com.deniscerri.ytdlnis.util.Extensions.popup
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import java.util.* import java.util.*
@ -62,17 +63,8 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view) val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")
val imageURL = item!!.thumb uiHandler.post { thumbnail.loadThumbnail(hideThumb, 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) }
}
card.findViewById<TextView>(R.id.title).text = item!!.title card.findViewById<TextView>(R.id.title).text = item!!.title
card.findViewById<TextView>(R.id.author).text = item.author card.findViewById<TextView>(R.id.author).text = item.author

View file

@ -9,6 +9,7 @@ import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -32,6 +33,7 @@ import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format 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.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
@ -73,6 +75,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var count : TextView private lateinit var count : TextView
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
private lateinit var currentDownloadIDs: List<Long>
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
@ -81,6 +85,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java] commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
infoUtil = InfoUtil(requireContext()) infoUtil = InfoUtil(requireContext())
currentDownloadIDs = arguments?.getLongArray("currentDownloadIDs")?.toList() ?: listOf()
} }
@SuppressLint("RestrictedApi", "NotifyDataSetChanged") @SuppressLint("RestrictedApi", "NotifyDataSetChanged")
@ -137,6 +143,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.downloadProcessingDownloads(cal.timeInMillis) downloadViewModel.downloadProcessingDownloads(cal.timeInMillis)
} }
@ -153,6 +160,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
download.isEnabled = false download.isEnabled = false
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.downloadProcessingDownloads() downloadViewModel.downloadProcessingDownloads()
} }
dismiss() dismiss()
@ -166,6 +174,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.moveProcessingToSavedCategory() downloadViewModel.moveProcessingToSavedCategory()
} }
dismiss() dismiss()

View file

@ -94,6 +94,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var endsAfterNr: TextInputLayout private lateinit var endsAfterNr: TextInputLayout
private lateinit var retryMissingDownloads: MaterialSwitch private lateinit var retryMissingDownloads: MaterialSwitch
private lateinit var getOnlyNewUploads: MaterialSwitch private lateinit var getOnlyNewUploads: MaterialSwitch
private lateinit var resetProcessedLinks: MaterialSwitch
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -252,6 +253,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
endsAfterNr = view.findViewById(R.id.after_nr) endsAfterNr = view.findViewById(R.id.after_nr)
retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads) retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads)
getOnlyNewUploads = view.findViewById(R.id.get_new_uploads) getOnlyNewUploads = view.findViewById(R.id.get_new_uploads)
resetProcessedLinks = view.findViewById(R.id.reset_processed_links)
okButton = view.findViewById(R.id.okButton) okButton = view.findViewById(R.id.okButton)
title.editText!!.setText(currentItem?.name ?: "") title.editText!!.setText(currentItem?.name ?: "")
@ -446,6 +448,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
retryMissingDownloads.isChecked = currentItem?.retryMissingDownloads ?: false retryMissingDownloads.isChecked = currentItem?.retryMissingDownloads ?: false
getOnlyNewUploads.isChecked = currentItem?.getOnlyNewUploads ?: false getOnlyNewUploads.isChecked = currentItem?.getOnlyNewUploads ?: false
resetProcessedLinks.isVisible = currentItem != null
if (currentItem != null) okButton.text = getString(R.string.update) if (currentItem != null) okButton.text = getString(R.string.update)
okButton.setOnClickListener { okButton.setOnClickListener {
@ -481,7 +484,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
0, 0,
getOnlyNewUploads.isChecked, getOnlyNewUploads.isChecked,
retryMissingDownloads.isChecked, retryMissingDownloads.isChecked,
mutableListOf() if (resetProcessedLinks.isChecked) mutableListOf() else currentItem?.alreadyProcessedLinks ?: mutableListOf()
) )
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
observeSourcesViewModel.insert(observeItem) observeSourcesViewModel.insert(observeItem)

View file

@ -51,8 +51,8 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.FullScreenDialogTheme) setStyle(STYLE_NORMAL, R.style.FullScreenDialogTheme)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
} }
override fun onCreateView( override fun onCreateView(
@ -173,6 +173,7 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
ok = toolbar.menu.getItem(0) ok = toolbar.menu.getItem(0)
ok.isEnabled = false ok.isEnabled = false
ok.setOnMenuItemClickListener { ok.setOnMenuItemClickListener {
ok.isEnabled = false
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val checkedItems = listAdapter.getCheckedItems() val checkedItems = listAdapter.getCheckedItems()
val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) } val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) }
@ -196,11 +197,11 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
downloadItems.add(i) 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() dismiss()

View file

@ -17,6 +17,7 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope 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.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
@ -152,6 +154,22 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
override fun onCardSelect(isChecked: Boolean, position: Int) { override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize) 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 (isChecked) {
if (actionMode == null){ if (actionMode == null){
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
@ -200,21 +218,28 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
item: MenuItem? item: MenuItem?
): Boolean { ): Boolean {
return when (item!!.itemId) { 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 -> { R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext()) val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Cancelled))
}
}else{
adapter.checkedItems.toList()
}
adapter.clearCheckedItems() adapter.clearCheckedItems()
downloadViewModel.deleteAllWithID(selectedObjects) downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish() actionMode?.finish()
@ -225,21 +250,15 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
} }
R.id.redownload -> { R.id.redownload -> {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Cancelled))
}
}else{
adapter.checkedItems.toList()
}
if (preferences.getBoolean("download_card", true)){ if (preferences.getBoolean("download_card", true)){
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.addDownloadsToProcessing(selectedObjects) downloadViewModel.addDownloadsToProcessing(selectedObjects)
} }
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2) val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle)
} }
}else{ }else{
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -266,6 +285,17 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
if (selectedObjects == 0) actionMode?.finish() if (selectedObjects == 0) actionMode?.finish()
true 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 else -> false
} }
} }
@ -274,6 +304,17 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
actionMode = null actionMode = null
adapter.clearCheckedItems() adapter.clearCheckedItems()
} }
suspend fun getSelectedIDs() : List<Long>{
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 = private var simpleCallback: ItemTouchHelper.SimpleCallback =

View file

@ -123,40 +123,36 @@ class DownloadQueueMainFragment : Fragment(){
lifecycleScope.launch { lifecycleScope.launch {
downloadViewModel.activeDownloadsCount.collectLatest { downloadViewModel.activeDownloadsCount.collectLatest {
tabLayout.getTabAt(0)?.apply { tabLayout.getTabAt(0)?.apply {
if (it == 0) removeBadge() createBadge(it)
else createBadge(it)
} }
} }
} }
lifecycleScope.launch { lifecycleScope.launch {
downloadViewModel.queuedDownloadsCount.collectLatest { downloadViewModel.queuedDownloadsCount.collectLatest {
tabLayout.getTabAt(1)?.apply { tabLayout.getTabAt(1)?.apply {
if (it == 0) removeBadge() createBadge(it)
else createBadge(it)
} }
} }
} }
lifecycleScope.launch { lifecycleScope.launch {
downloadViewModel.cancelledDownloadsCount.collectLatest { downloadViewModel.cancelledDownloadsCount.collectLatest {
tabLayout.getTabAt(2)?.apply { tabLayout.getTabAt(2)?.apply {
if (it == 0) removeBadge() createBadge(it)
else createBadge(it)
} }
} }
} }
lifecycleScope.launch { lifecycleScope.launch {
downloadViewModel.erroredDownloadsCount.collectLatest { downloadViewModel.erroredDownloadsCount.collectLatest {
tabLayout.getTabAt(3)?.apply { tabLayout.getTabAt(3)?.apply {
if (it == 0) removeBadge() createBadge(it)
else createBadge(it)
} }
} }
} }
lifecycleScope.launch { lifecycleScope.launch {
downloadViewModel.savedDownloadsCount.collectLatest { downloadViewModel.savedDownloadsCount.collectLatest {
tabLayout.getTabAt(4)?.apply { tabLayout.getTabAt(4)?.apply {
if (it == 0) removeBadge() removeBadge()
else createBadge(it) if (it > 0) createBadge(it)
} }
} }
} }

View file

@ -34,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
@ -154,18 +155,19 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
override fun onCardSelect(isChecked: Boolean, position: Int) { override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize) val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
if (isChecked) { if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
if (actionMode == null){ actionMode?.apply {
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
}else{
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
}
}
else {
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
if (selectedObjects == 0){ 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? item: MenuItem?
): Boolean { ): Boolean {
return when (item!!.itemId) { 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 -> { R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext()) val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Error))
}
}else{
adapter.checkedItems.toList()
}
adapter.clearCheckedItems() adapter.clearCheckedItems()
downloadViewModel.deleteAllWithID(selectedObjects) downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish() actionMode?.finish()
@ -231,21 +241,16 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
} }
R.id.redownload -> { R.id.redownload -> {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Error))
}
}else{
adapter.checkedItems.toList()
}
if (preferences.getBoolean("download_card", true)){ if (preferences.getBoolean("download_card", true)){
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.addDownloadsToProcessing(selectedObjects) downloadViewModel.addDownloadsToProcessing(selectedObjects)
} }
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2) val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle)
} }
}else{ }else{
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -270,6 +275,17 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
if (selectedObjects == 0) actionMode?.finish() if (selectedObjects == 0) actionMode?.finish()
true 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 else -> false
} }
} }
@ -278,6 +294,17 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
actionMode = null actionMode = null
adapter.clearCheckedItems() adapter.clearCheckedItems()
} }
suspend fun getSelectedIDs() : List<Long>{
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 = private var simpleCallback: ItemTouchHelper.SimpleCallback =

View file

@ -34,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode 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.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
@ -180,27 +181,26 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
override fun onCardSelect(isChecked: Boolean, position: Int) { override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize) val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (isChecked) { actionMode?.apply {
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 (selectedObjects == 0){ if (selectedObjects == 0){
actionMode?.finish() this.finish()
} }else{
} this.title = "$selectedObjects ${getString(R.string.selected)}"
if (actionMode != null){ this.menu.findItem(R.id.download).isVisible = withContext(Dispatchers.IO){
actionMode!!.menu.getItem(2).isVisible = withContext(Dispatchers.IO){ downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems.toList(), adapter.inverted, now)
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems, 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? item: MenuItem?
): Boolean { ): Boolean {
return when (item!!.itemId) { 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 -> { R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext()) val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
}
}else{
adapter.checkedItems.toList()
}
adapter.clearCheckedItems() adapter.clearCheckedItems()
for (id in selectedObjects){ for (id in selectedObjects){
YoutubeDL.getInstance().destroyProcessById(id.toInt().toString()) YoutubeDL.getInstance().destroyProcessById(id.toInt().toString())
@ -280,14 +288,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
} }
R.id.download -> { R.id.download -> {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
}
}else{
adapter.checkedItems.toList()
}
adapter.clearCheckedItems() adapter.clearCheckedItems()
for (id in selectedObjects){ for (id in selectedObjects){
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString()) WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString())
@ -313,15 +314,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
} }
R.id.up -> { R.id.up -> {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
}
}else{
adapter.checkedItems.toList()
}
adapter.clearCheckedItems() adapter.clearCheckedItems()
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.putAtTopOfQueue(selectedObjects) downloadViewModel.putAtTopOfQueue(selectedObjects)
@ -330,6 +323,17 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
} }
true 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 else -> false
} }
} }
@ -338,6 +342,17 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
actionMode = null actionMode = null
adapter.clearCheckedItems() adapter.clearCheckedItems()
} }
suspend fun getSelectedIDs() : List<Long>{
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()
}
}
} }

View file

@ -33,6 +33,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
@ -152,18 +153,19 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
override fun onCardSelect(isChecked: Boolean, position: Int) { override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize) val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
if (isChecked) { if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
if (actionMode == null){ actionMode?.apply {
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
}else{
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
}
}
else {
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
if (selectedObjects == 0){ 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? item: MenuItem?
): Boolean { ): Boolean {
return when (item!!.itemId) { 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 -> { R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext()) val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Saved))
}
}else{
adapter.checkedItems.toList()
}
adapter.clearCheckedItems() adapter.clearCheckedItems()
for (id in selectedObjects){ for (id in selectedObjects){
downloadViewModel.deleteDownload(id) downloadViewModel.deleteDownload(id)
@ -227,21 +237,16 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
} }
R.id.download -> { R.id.download -> {
lifecycleScope.launch { lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){ val selectedObjects = getSelectedIDs()
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Saved))
}
}else{
adapter.checkedItems.toList()
}
adapter.clearCheckedItems() adapter.clearCheckedItems()
if (preferences.getBoolean("download_card", true)){ if (preferences.getBoolean("download_card", true)){
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.addDownloadsToProcessing(selectedObjects) downloadViewModel.addDownloadsToProcessing(selectedObjects)
} }
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2) val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle)
} }
}else{ }else{
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -265,6 +270,17 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
if (selectedObjects == 0) actionMode?.finish() if (selectedObjects == 0) actionMode?.finish()
true 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 else -> false
} }
} }
@ -273,6 +289,17 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
actionMode = null actionMode = null
adapter.clearCheckedItems() adapter.clearCheckedItems()
} }
suspend fun getSelectedIDs() : List<Long>{
return if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems.toList(), listOf(
DownloadRepository.Status.Saved))
}
}else{
adapter.checkedItems.toList()
}
}
} }

View file

@ -185,7 +185,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
builder.setNeutralButton( builder.setNeutralButton(
getString(android.R.string.copy) getString(android.R.string.copy)
) { dialog: DialogInterface?, which: Int -> ) { dialog: DialogInterface?, which: Int ->
UiUtil.copyToClipboard(item.content, requireActivity()) UiUtil.copyToClipboard(cookiesViewModel.cookieHeader + "\n" + item.content, requireActivity())
} }
} }

View file

@ -130,6 +130,7 @@ class ObserveSourcesFragment : Fragment(), ObserveSourcesAdapter.OnItemClickList
} }
override fun onItemSearch(item: ObserveSourcesItem) { override fun onItemSearch(item: ObserveSourcesItem) {
runCatching { runCatching {
observeSourcesViewModel.cancelObservationTaskByID(item.id)
val intent = Intent(context, ObserveAlarmReceiver::class.java) val intent = Intent(context, ObserveAlarmReceiver::class.java)
intent.putExtra("id", item.id) intent.putExtra("id", item.id)
requireActivity().sendBroadcast(intent) requireActivity().sendBroadcast(intent)

View file

@ -20,18 +20,22 @@ import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import android.view.animation.Interpolator import android.view.animation.Interpolator
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.Px import androidx.annotation.Px
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.withStarted import androidx.lifecycle.withStarted
import androidx.recyclerview.widget.RecyclerView 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.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.neo.highlight.core.Highlight import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme import com.neo.highlight.util.scheme.ColorScheme
import com.squareup.picasso.Picasso
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
import org.json.JSONObject import org.json.JSONObject
@ -215,10 +219,16 @@ object Extensions {
} }
fun TabLayout.Tab.createBadge(nr: Int){ fun TabLayout.Tab.createBadge(nr: Int){
this.orCreateBadge.apply { removeBadge()
number = nr if (nr > 0) {
verticalOffset = 5 orCreateBadge.apply {
horizontalOffset = 10 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}" 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<DownloadRepository.Status>.toListString() : List<String>{
return this.map { it.toString() }
}
fun List<String>.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) } fun List<String>.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) }
class CustomInterpolator : Interpolator { class CustomInterpolator : Interpolator {

View file

@ -761,7 +761,7 @@ class InfoUtil(private val context: Context) {
formatProper.format_note = format.getString("format_note") formatProper.format_note = format.getString("format_note")
}else{ }else{
if (!formatProper.format_note.endsWith("audio", true)){ 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" formatProper.tbr = formatProper.tbr + "k"
} }
if(formatProper.vcodec.isEmpty() || formatProper.vcodec == "null"){ if(formatProper.vcodec.isNullOrEmpty() || formatProper.vcodec == "null"){
if(formatProper.acodec.isEmpty() || formatProper.acodec == "null"){ if(formatProper.acodec.isNullOrEmpty() || formatProper.acodec == "null"){
formatProper.vcodec = format.getStringByAny("video_ext", "ext").ifEmpty { "unknown" } formatProper.vcodec = format.getStringByAny("video_ext", "ext").ifEmpty { "unknown" }
} }
} }
@ -1032,36 +1032,22 @@ class InfoUtil(private val context: Context) {
} }
} }
if (downloadItem.playlistTitle.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata", "video:playlist", ".+", downloadItem.playlistTitle))
try { runCatching {
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##replaceMetadata.txt") if (downloadItem.playlistIndex != null){
YoutubeDLRequest("").apply { request.addOption("--parse-metadata", downloadItem.playlistIndex.toString() + ":%(playlist_index)s")
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.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<uploader>.+)(?: - Topic)$") request.addOption("--parse-metadata", "uploader:(?P<uploader>.+)(?: - Topic)$")
@ -1111,10 +1097,6 @@ class InfoUtil(private val context: Context) {
if (sharedPreferences.getBoolean("write_description", false)){ if (sharedPreferences.getBoolean("write_description", false)){
request.addOption("--write-description") request.addOption("--write-description")
} }
filenameTemplate = filenameTemplate
.replace("%(uploader)s", "%(uploader,channel).30B")
.replace("%(title)s", "%(title).170B")
} }
if (sharedPreferences.getBoolean("download_archive", false)){ if (sharedPreferences.getBoolean("download_archive", false)){

View file

@ -219,6 +219,7 @@ class NotificationUtil(var context: Context) {
} }
fun createDownloadFinished( fun createDownloadFinished(
id: Long,
title: String?, title: String?,
filepath: List<String>?, filepath: List<String>?,
res: Resources res: Resources
@ -234,64 +235,75 @@ class NotificationUtil(var context: Context) {
R.drawable.ic_launcher_foreground_large R.drawable.ic_launcher_foreground_large
) )
) )
.setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString())
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
.setContentText("") .setContentText("")
.setPriority(NotificationCompat.PRIORITY_MAX) .setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions() .clearActions()
if (filepath != null){ if (filepath != null){
try{ try{
val uri = filepath.first().runCatching { val uris = filepath.mapNotNull {
DocumentFile.fromSingleUri(context, Uri.parse(filepath.first())).run{ runCatching {
if (this?.exists() == true){ DocumentFile.fromSingleUri(context, Uri.parse(it)).run{
this.uri if (this?.exists() == true){
}else if (File(this@runCatching).exists()){ this.uri
FileProvider.getUriForFile(context, context.packageName + ".fileprovider", }else if (File(it).exists()){
File(this@runCatching)) FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
}else null File(it))
} }else null
}.getOrNull() }
}.getOrNull()
}
val openFileIntent = Intent() val openFileIntent = Intent()
val shareFileIntent = Intent()
if (uri != null){ if (uris.isNotEmpty()){
openFileIntent.apply { openFileIntent.apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = Intent.ACTION_VIEW 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 { val openNotificationPendingIntent: PendingIntent = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(openFileIntent) addNextIntentWithParentStack(openFileIntent)
getPendingIntent(0, getPendingIntent(id.toInt(),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
} }
//share intent //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( val shareNotificationPendingIntent: PendingIntent = PendingIntent.getActivity(
context, context,
0, id.toInt(),
shareIntent, Intent.createChooser(shareFileIntent, res.getString(R.string.share)),
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE 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.Open_File), openNotificationPendingIntent)
notificationBuilder.addAction(0, res.getString(R.string.share), shareNotificationPendingIntent) notificationBuilder.addAction(0, res.getString(R.string.share), shareNotificationPendingIntent)
}catch (_: Exception){} }catch (_: Exception){}
} }
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build()) notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build())
} }
fun createDownloadErrored(title: String?, fun createDownloadErrored(
error: String?, id: Long,
logID: Long?, title: String?,
res: Resources error: String?,
logID: Long?,
res: Resources
) { ) {
val notificationBuilder = getBuilder(DOWNLOAD_ERRORED_CHANNEL_ID) val notificationBuilder = getBuilder(DOWNLOAD_ERRORED_CHANNEL_ID)
@ -333,7 +345,7 @@ class NotificationUtil(var context: Context) {
notificationBuilder.setContentIntent(errorPendingIntent) notificationBuilder.setContentIntent(errorPendingIntent)
notificationBuilder.addAction(0, res.getString(R.string.logs), 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) { fun cancelDownloadNotification(id: Int) {
notificationManager.cancel(id) notificationManager.cancel(id)
} }
fun createMoveCacheFilesNotification(pendingIntent: PendingIntent?, downloadMiscChannelId: String): Notification { fun createMoveCacheFilesNotification(pendingIntent: PendingIntent?, downloadMiscChannelId: String): Notification {
val notificationBuilder = getBuilder(downloadMiscChannelId) val notificationBuilder = getBuilder(downloadMiscChannelId)
@ -574,7 +585,7 @@ class NotificationUtil(var context: Context) {
.createPendingIntent() .createPendingIntent()
notificationBuilder 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) .setSmallIcon(R.drawable.ic_launcher_foreground_large)
.setLargeIcon( .setLargeIcon(
BitmapFactory.decodeResource( BitmapFactory.decodeResource(

View file

@ -103,7 +103,7 @@ class UpdateUtil(var context: Context) {
d?.dismiss() d?.dismiss()
} }
.setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> } .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 { runCatching {
val releaseVersion = v.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) } val releaseVersion = v.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }
if (releaseVersion == null){ if (releaseVersion == null){
@ -140,8 +140,8 @@ class UpdateUtil(var context: Context) {
} }
context.registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) context.registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
d?.dismiss()
} }
} }
val view = updateDialog.show() val view = updateDialog.show()
val textView = view.findViewById<TextView>(android.R.id.message) val textView = view.findViewById<TextView>(android.R.id.message)
@ -176,7 +176,7 @@ class UpdateUtil(var context: Context) {
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
) )
layoutParams.setMargins(5, 5, 5, 0) layoutParams.setMargins(10, 10, 10, 0)
scrollView.layoutParams = layoutParams scrollView.layoutParams = layoutParams
scrollView.addView(linearLayout) scrollView.addView(linearLayout)
val releases = getGithubReleases() val releases = getGithubReleases()

View file

@ -267,7 +267,7 @@ class DownloadWorker(
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished( 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){ if (wasQuickDownloaded && createResultItem){
@ -326,7 +326,9 @@ class DownloadWorker(
} }
notificationUtil.createDownloadErrored( notificationUtil.createDownloadErrored(
downloadItem.title.ifEmpty { downloadItem.url }, it.message, downloadItem.id,
downloadItem.title.ifEmpty { downloadItem.url },
it.message,
downloadItem.logID, downloadItem.logID,
resources resources
) )

View file

@ -65,22 +65,20 @@ class ObserveSourceWorker(
} }
} }
.filter { result -> .filter { result ->
val history = historyRepo.getAllByURL(result.url)
val history = historyRepo.getAllByURL(result.url) if (item.retryMissingDownloads){
if (!item.retryMissingDownloads){ //all items that are not present in history
//all items that are not present in history history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } }
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)
}else{ }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<DownloadItem>() val items = mutableListOf<DownloadItem>()
res.forEach { res.forEach {
@ -208,47 +206,18 @@ class ObserveSourceWorker(
repo.update(item) repo.update(item)
} }
scheduleForNextTime(item) //schedule for next time
return Result.success()
}
private fun scheduleForNextTime(item: ObserveSourcesItem){
val alarmManager = context.getSystemService(AlarmManager::class.java) val alarmManager = context.getSystemService(AlarmManager::class.java)
val intent = Intent(context, ObserveAlarmReceiver::class.java) val intent = Intent(context, ObserveAlarmReceiver::class.java)
intent.putExtra("id", item.id) 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.setExact(
AlarmManager.RTC, AlarmManager.RTC_WAKEUP,
c.timeInMillis, repo.calculateNextTime(item),
PendingIntent.getBroadcast(context, item.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) PendingIntent.getBroadcast(context, item.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
) )
return Result.success()
} }
} }

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:textColorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M440,880L440,712L376,776L320,720L480,560L640,720L584,776L520,712L520,880L440,880ZM160,520L160,440L800,440L800,520L160,520ZM480,400L320,240L376,184L440,248L440,80L520,80L520,248L584,184L640,240L480,400Z"/>
</vector>

View file

@ -171,6 +171,7 @@
style="?attr/materialIconButtonFilledStyle" style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/pause"
android:layoutAnimation="@anim/popup_enter" android:layoutAnimation="@anim/popup_enter"
app:backgroundTint="?attr/colorSurface" app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp" app:cornerRadius="15dp"

View file

@ -63,35 +63,38 @@
<TextView <TextView
android:id="@+id/format_note" android:id="@+id/format_note"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="10dp" android:textDirection="locale"
android:ellipsize="end" android:ellipsize="end"
android:gravity="start" android:gravity="start"
android:maxLines="2" android:maxLines="2"
android:paddingHorizontal="10dp"
android:textSize="20sp" android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/format_id"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout <RelativeLayout
android:layout_width="0dp" android:id="@+id/relativeLayout"
app:layout_constraintHorizontal_chainStyle="spread_inside"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/format_id" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/format_note"> app:layout_constraintTop_toBottomOf="@id/format_note">
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_marginEnd="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:clickable="false" android:clickable="false"
android:focusable="false" android:focusable="false"
android:layout_marginStart="10dp"
android:scrollbars="none"> android:scrollbars="none">
<LinearLayout <LinearLayout
@ -111,14 +114,14 @@
android:clickable="false" android:clickable="false"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center" android:gravity="center"
android:maxLength="10"
android:minWidth="30dp" android:minWidth="30dp"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:textColor="@color/white" android:textColor="@color/white"
android:textCursorDrawable="@string/audio_format"
android:textStyle="bold" android:textStyle="bold"
app:cornerRadius="10dp" app:cornerRadius="10dp"
android:maxLength="10"
app:drawableStartCompat="@drawable/ic_music_formatcard" app:drawableStartCompat="@drawable/ic_music_formatcard"
android:textCursorDrawable="@string/audio_format"
app:drawableTint="@color/white" app:drawableTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -175,16 +178,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginBottom="3dp"
android:clickable="false" android:clickable="false"
android:ellipsize="end"
android:gravity="bottom|end" android:gravity="bottom|end"
android:maxLength="10" android:maxLength="10"
android:ellipsize="end"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:layout_constraintVertical_bias="1.0" app:layout_constraintBottom_toBottomOf="@+id/format_note"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/format_note" /> app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -500,6 +500,13 @@
android:checked="false" android:checked="false"
android:text="@string/retry_missing_downloads"/> android:text="@string/retry_missing_downloads"/>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/reset_processed_links"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/reset_processed_links"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -3,6 +3,13 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" > tools:context=".MainActivity" >
<item
android:id="@+id/select_between"
android:visible="false"
android:title="@string/select_between"
android:icon="@drawable/ic_select_between"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/delete_results" android:id="@+id/delete_results"
android:title="@string/remove_results" android:title="@string/remove_results"
@ -25,5 +32,11 @@
android:title="@string/invert_selected" android:title="@string/invert_selected"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:icon="@drawable/ic_delete_all"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu> </menu>

View file

@ -3,6 +3,13 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" > tools:context=".MainActivity" >
<item
android:id="@+id/select_between"
android:visible="false"
android:title="@string/select_between"
android:icon="@drawable/ic_select_between"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/delete_results" android:id="@+id/delete_results"
android:title="@string/remove_results" android:title="@string/remove_results"
@ -32,5 +39,11 @@
android:title="@string/invert_selected" android:title="@string/invert_selected"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:icon="@drawable/ic_delete_all"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu> </menu>

View file

@ -3,6 +3,14 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" > tools:context=".MainActivity" >
<item
android:id="@+id/select_between"
android:visible="false"
android:title="@string/select_between"
android:icon="@drawable/ic_select_between"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/delete_results" android:id="@+id/delete_results"
android:title="@string/remove_results" android:title="@string/remove_results"
@ -25,5 +33,11 @@
android:title="@string/invert_selected" android:title="@string/invert_selected"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:icon="@drawable/ic_delete_all"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu> </menu>

View file

@ -372,4 +372,7 @@
<string name="text_size">Text Size</string> <string name="text_size">Text Size</string>
<string name="add">Add</string> <string name="add">Add</string>
<string name="back">Back</string> <string name="back">Back</string>
<string name="all_queries_finished">All Queries have been processed</string>
<string name="select_between">Select Items Between</string>
<string name="reset_processed_links">Reset Already Processed Links</string>
</resources> </resources>

View file

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