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

View file

@ -216,6 +216,9 @@ interface DownloadDao {
@Query("Select url from downloads where status in (:status)")
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)")
suspend fun resetScheduleTimeForItems(list: List<Long>)
@ -243,4 +246,8 @@ interface DownloadDao {
@Query("Update downloads set id=:newId where id=:id")
suspend fun updateDownloadID(id: Long, newId: Long)
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in (:statuses) ORDER BY id DESC")
fun getIDsBetweenTwoItems(item1: Long, item2: Long, statuses: List<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.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.work.DownloadWorker
import kotlinx.coroutines.flow.Flow
@ -63,10 +64,6 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing
}
private fun List<Status>.toListString() : List<String>{
return this.map { it.toString() }
}
suspend fun insert(item: DownloadItem) : Long {
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.receiver.ObserveAlarmReceiver
import kotlinx.coroutines.flow.Flow
import java.util.Calendar
class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) {
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
val items: LiveData<List<CookieItem>>
private val cookieHeader =
val cookieHeader =
"# Netscape HTTP Cookie File\n" +
"# WebView Generated by the YTDLnis app\n" +
"# This is a generated file! Do not edit."

View file

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

View file

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

View file

@ -500,7 +500,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
val url = checkClipboard()
url?.apply {
combinedList.add(0, SearchSuggestionItem(this.joinToString("\n"), SearchSuggestionType.CLIPBOARD))
if (this.isNotEmpty()){
combinedList.add(0, SearchSuggestionItem(this.joinToString("\n"), SearchSuggestionType.CLIPBOARD))
}
}
@ -845,7 +847,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
return kotlin.runCatching {
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = clipboard.primaryClip!!.getItemAt(0).text
return clip.split("\r\n").filter { Patterns.WEB_URL.matcher(it).matches() }
return clip.split("\r","\n").map { it.trim() }.filter { Patterns.WEB_URL.matcher(it).matches() }
}.getOrNull()
}
@ -873,27 +875,36 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
}
override fun onSearchSuggestionAdd(text: String) {
val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == text }
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = text
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE
queriesChipGroup!!.removeView(chip)
override fun onSearchSuggestionAdd(t: String) {
val items = t.split("\n")
items.forEach {text ->
val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == text }
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = text
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE
queriesChipGroup!!.removeView(chip)
}
queriesChipGroup!!.addView(chip)
}
queriesChipGroup!!.addView(chip)
}
searchView!!.editText.setText("")
if (queriesChipGroup!!.childCount == 0) queriesConstraint!!.visibility = View.GONE
else queriesConstraint!!.visibility = View.VISIBLE
searchView!!.editText.setText("")
val clipBoardItem = searchSuggestionsRecyclerView?.layoutManager?.findViewByPosition(0)
clipBoardItem?.apply {
if ((this as ConstraintLayout).findViewById<TextView>(R.id.suggestion_text).text == getString(R.string.link_you_copied)){
searchSuggestionsAdapter?.notifyItemRemoved(0)
}
}
}
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.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.Extensions.dp
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
@ -65,17 +66,8 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
// THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
val imageURL = item.thumb
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
}else{
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
uiHandler.post { thumbnail.loadThumbnail(hideThumb, item.thumb) }
// PROGRESS BAR ----------------------------------------------------
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
@ -168,7 +160,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val value = animation.animatedValue as Int
pauseButton.cornerRadius = value
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
pauseButton.contentDescription = activity.getString(R.string.start)
pauseButton.contentDescription = activity.getString(R.string.resume)
pauseButton.tag = ActiveDownloadAction.Resume
pauseButton.isEnabled = true
}

View file

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

View file

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

View file

@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
import com.deniscerri.ytdlnis.util.Extensions.popup
import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.MaterialColors
@ -89,17 +90,8 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
// THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")){
val imageURL = item!!.thumb
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
}else{
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")
uiHandler.post { thumbnail.loadThumbnail(hideThumb, item!!.thumb) }
// TITLE ----------------------------------
val itemTitle = card.findViewById<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.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
import com.deniscerri.ytdlnis.util.Extensions.popup
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
@ -62,16 +63,8 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
// THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
val imageURL = video!!.thumb
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
}else{
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")
uiHandler.post { thumbnail.loadThumbnail(hideThumb, video!!.thumb) }
// TITLE ----------------------------------
val videoTitle = card.findViewById<TextView>(R.id.result_title)

View file

@ -57,14 +57,12 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val card = holder.cardView
val uiHandler = Handler(Looper.getMainLooper())
card.popup()
if (item == null) return
card.tag = item.url
holder.itemView.tag = item.url
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
// 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 com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
import com.deniscerri.ytdlnis.util.Extensions.popup
import com.squareup.picasso.Picasso
import java.util.*
@ -62,17 +63,8 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
// THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
val imageURL = item!!.thumb
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
}else{
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")
uiHandler.post { thumbnail.loadThumbnail(hideThumb, item!!.thumb) }
card.findViewById<TextView>(R.id.title).text = item!!.title
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.graphics.Canvas
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.text.format.DateFormat
import android.util.DisplayMetrics
@ -32,6 +33,7 @@ import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
@ -73,6 +75,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var count : TextView
private lateinit var sharedPreferences: SharedPreferences
private lateinit var currentDownloadIDs: List<Long>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
@ -81,6 +85,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
infoUtil = InfoUtil(requireContext())
currentDownloadIDs = arguments?.getLongArray("currentDownloadIDs")?.toList() ?: listOf()
}
@SuppressLint("RestrictedApi", "NotifyDataSetChanged")
@ -137,6 +143,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.downloadProcessingDownloads(cal.timeInMillis)
}
@ -153,6 +160,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
download.isEnabled = false
lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.downloadProcessingDownloads()
}
dismiss()
@ -166,6 +174,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch{
withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.moveProcessingToSavedCategory()
}
dismiss()

View file

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

View file

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

View file

@ -17,6 +17,7 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -33,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors
@ -152,6 +154,22 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
actionMode?.apply {
if (selectedObjects == 0){
this.finish()
}else{
this.title = "$selectedObjects ${getString(R.string.selected)}"
if (selectedObjects == 2){
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
}
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
}
}
}
if (isChecked) {
if (actionMode == null){
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
@ -200,21 +218,28 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
item: MenuItem?
): Boolean {
return when (item!!.itemId) {
R.id.select_between -> {
lifecycleScope.launch {
val selectedIDs = getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
}.toMutableList()
idsInMiddle.addAll(selectedIDs)
if (idsInMiddle.isNotEmpty()){
adapter.checkMultipleItems(idsInMiddle)
actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}"
}
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
}
true
}
R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Cancelled))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
adapter.clearCheckedItems()
downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish()
@ -225,21 +250,15 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
}
R.id.redownload -> {
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Cancelled))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
if (preferences.getBoolean("download_card", true)){
withContext(Dispatchers.IO){
downloadViewModel.addDownloadsToProcessing(selectedObjects)
}
withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle)
}
}else{
withContext(Dispatchers.IO) {
@ -266,6 +285,17 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
if (selectedObjects == 0) actionMode?.finish()
true
}
R.id.copy_urls -> {
lifecycleScope.launch {
val selectedObjects = getSelectedIDs()
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByIds(selectedObjects)
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
actionMode?.finish()
}
true
}
else -> false
}
}
@ -274,6 +304,17 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
actionMode = null
adapter.clearCheckedItems()
}
suspend fun getSelectedIDs() : List<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 =

View file

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

View file

@ -34,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors
@ -154,18 +155,19 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
if (isChecked) {
if (actionMode == null){
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
}else{
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
}
}
else {
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
actionMode?.apply {
if (selectedObjects == 0){
actionMode?.finish()
this.finish()
}else{
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
if(selectedObjects == 2){
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Error).toListString())
}
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
}
}
}
}
@ -207,20 +209,28 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
item: MenuItem?
): Boolean {
return when (item!!.itemId) {
R.id.select_between -> {
lifecycleScope.launch {
val selectedIDs = getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
}.toMutableList()
idsInMiddle.addAll(selectedIDs)
if (idsInMiddle.isNotEmpty()){
adapter.checkMultipleItems(idsInMiddle)
actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}"
}
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
}
true
}
R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Error))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
adapter.clearCheckedItems()
downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish()
@ -231,21 +241,16 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
}
R.id.redownload -> {
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Error))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
if (preferences.getBoolean("download_card", true)){
withContext(Dispatchers.IO){
downloadViewModel.addDownloadsToProcessing(selectedObjects)
}
withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle)
}
}else{
withContext(Dispatchers.IO) {
@ -270,6 +275,17 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
if (selectedObjects == 0) actionMode?.finish()
true
}
R.id.copy_urls -> {
lifecycleScope.launch {
val selectedObjects = getSelectedIDs()
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByIds(selectedObjects)
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
actionMode?.finish()
}
true
}
else -> false
}
}
@ -278,6 +294,17 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
actionMode = null
adapter.clearCheckedItems()
}
suspend fun getSelectedIDs() : List<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 =

View file

@ -34,6 +34,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil
@ -180,27 +181,26 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
val now = System.currentTimeMillis()
if (isChecked) {
if (actionMode == null){
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
}else{
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
}
}
else {
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
actionMode?.apply {
if (selectedObjects == 0){
actionMode?.finish()
}
}
if (actionMode != null){
actionMode!!.menu.getItem(2).isVisible = withContext(Dispatchers.IO){
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems, adapter.inverted, now)
}
this.finish()
}else{
this.title = "$selectedObjects ${getString(R.string.selected)}"
this.menu.findItem(R.id.download).isVisible = withContext(Dispatchers.IO){
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems.toList(), adapter.inverted, now)
}
actionMode!!.menu.getItem(1).isVisible = position > 0
this.menu.findItem(R.id.up).isVisible = position > 0
if(selectedObjects == 2){
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused).toListString())
}
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
}
}
}
}
}
@ -250,20 +250,28 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
item: MenuItem?
): Boolean {
return when (item!!.itemId) {
R.id.select_between -> {
lifecycleScope.launch {
val selectedIDs = getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
}.toMutableList()
idsInMiddle.addAll(selectedIDs)
if (idsInMiddle.isNotEmpty()){
adapter.checkMultipleItems(idsInMiddle)
actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}"
}
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
}
true
}
R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
adapter.clearCheckedItems()
for (id in selectedObjects){
YoutubeDL.getInstance().destroyProcessById(id.toInt().toString())
@ -280,14 +288,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
}
R.id.download -> {
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
adapter.clearCheckedItems()
for (id in selectedObjects){
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString())
@ -313,15 +314,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
}
R.id.up -> {
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
adapter.clearCheckedItems()
withContext(Dispatchers.IO){
downloadViewModel.putAtTopOfQueue(selectedObjects)
@ -330,6 +323,17 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
}
true
}
R.id.copy_urls -> {
lifecycleScope.launch {
val selectedObjects = getSelectedIDs()
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByIds(selectedObjects)
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
actionMode?.finish()
}
true
}
else -> false
}
}
@ -338,6 +342,17 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
actionMode = null
adapter.clearCheckedItems()
}
suspend fun getSelectedIDs() : List<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.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.toListString
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors
@ -152,18 +153,19 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
override fun onCardSelect(isChecked: Boolean, position: Int) {
lifecycleScope.launch {
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
if (isChecked) {
if (actionMode == null){
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
}else{
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
}
}
else {
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
actionMode?.apply {
if (selectedObjects == 0){
actionMode?.finish()
this.finish()
}else{
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
if(selectedObjects == 2){
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Saved).toListString())
}
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
}
}
}
}
@ -201,20 +203,28 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
item: MenuItem?
): Boolean {
return when (item!!.itemId) {
R.id.select_between -> {
lifecycleScope.launch {
val selectedIDs = getSelectedIDs().sortedBy { it }
val idsInMiddle = withContext(Dispatchers.IO){
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
}.toMutableList()
idsInMiddle.addAll(selectedIDs)
if (idsInMiddle.isNotEmpty()){
adapter.checkMultipleItems(idsInMiddle)
actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}"
}
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
}
true
}
R.id.delete_results -> {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Saved))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
adapter.clearCheckedItems()
for (id in selectedObjects){
downloadViewModel.deleteDownload(id)
@ -227,21 +237,16 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
}
R.id.download -> {
lifecycleScope.launch {
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
withContext(Dispatchers.IO){
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
DownloadRepository.Status.Saved))
}
}else{
adapter.checkedItems.toList()
}
val selectedObjects = getSelectedIDs()
adapter.clearCheckedItems()
if (preferences.getBoolean("download_card", true)){
withContext(Dispatchers.IO){
downloadViewModel.addDownloadsToProcessing(selectedObjects)
}
withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2,bundle)
}
}else{
withContext(Dispatchers.IO) {
@ -265,6 +270,17 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
if (selectedObjects == 0) actionMode?.finish()
true
}
R.id.copy_urls -> {
lifecycleScope.launch {
val selectedObjects = getSelectedIDs()
val urls = withContext(Dispatchers.IO){
downloadViewModel.getURLsByIds(selectedObjects)
}
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
actionMode?.finish()
}
true
}
else -> false
}
}
@ -273,6 +289,17 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
actionMode = null
adapter.clearCheckedItems()
}
suspend fun getSelectedIDs() : List<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(
getString(android.R.string.copy)
) { 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) {
runCatching {
observeSourcesViewModel.cancelObservationTaskByID(item.id)
val intent = Intent(context, ObserveAlarmReceiver::class.java)
intent.putExtra("id", item.id)
requireActivity().sendBroadcast(intent)

View file

@ -20,18 +20,22 @@ import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.animation.Interpolator
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.Px
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.withStarted
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.tabs.TabLayout
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import com.squareup.picasso.Picasso
import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import org.json.JSONObject
@ -215,10 +219,16 @@ object Extensions {
}
fun TabLayout.Tab.createBadge(nr: Int){
this.orCreateBadge.apply {
number = nr
verticalOffset = 5
horizontalOffset = 10
removeBadge()
if (nr > 0) {
orCreateBadge.apply {
number = nr
verticalOffset = 3
horizontalOffset =
if (nr < 10) 7
else if (nr < 100) 10
else 20
}
}
}
@ -233,6 +243,26 @@ object Extensions {
return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
}
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
if(!hideThumb){
if (imageURL.isNotEmpty()) {
Picasso.get()
.load(imageURL)
.resize(1280, 0)
.onlyScaleDown()
.into(this)
} else {
Picasso.get().load(R.color.black).into(this)
}
}
}
fun List<DownloadRepository.Status>.toListString() : List<String>{
return this.map { it.toString() }
}
fun List<String>.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) }
class CustomInterpolator : Interpolator {

View file

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

View file

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

View file

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

View file

@ -267,7 +267,7 @@ class DownloadWorker(
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished(
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
downloadItem.id, downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
)
if (wasQuickDownloaded && createResultItem){
@ -326,7 +326,9 @@ class DownloadWorker(
}
notificationUtil.createDownloadErrored(
downloadItem.title.ifEmpty { downloadItem.url }, it.message,
downloadItem.id,
downloadItem.title.ifEmpty { downloadItem.url },
it.message,
downloadItem.logID,
resources
)

View file

@ -65,22 +65,20 @@ class ObserveSourceWorker(
}
}
.filter { result ->
val history = historyRepo.getAllByURL(result.url)
if (!item.retryMissingDownloads){
//all items that are not present in history
history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } }
}else{
//all items that are not already processed
if(item.alreadyProcessedLinks.isEmpty()){
!history.map { it.url }.contains(result.url) && !item.alreadyProcessedLinks.contains(result.url)
val history = historyRepo.getAllByURL(result.url)
if (item.retryMissingDownloads){
//all items that are not present in history
history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } }
}else{
!item.alreadyProcessedLinks.contains(result.url)
//all items that are not already processed
if(item.alreadyProcessedLinks.isEmpty()){
!history.map { it.url }.contains(result.url)
}else{
!item.alreadyProcessedLinks.contains(result.url)
}
}
}
}
val items = mutableListOf<DownloadItem>()
res.forEach {
@ -208,47 +206,18 @@ class ObserveSourceWorker(
repo.update(item)
}
scheduleForNextTime(item)
return Result.success()
}
private fun scheduleForNextTime(item: ObserveSourcesItem){
//schedule for next time
val alarmManager = context.getSystemService(AlarmManager::class.java)
val intent = Intent(context, ObserveAlarmReceiver::class.java)
intent.putExtra("id", item.id)
val c = Calendar.getInstance()
val hourMin = Calendar.getInstance()
hourMin.timeInMillis = item.everyTime
c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY))
c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
when(item.everyCategory){
ObserveSourcesRepository.EveryCategory.DAY -> {
c.add(Calendar.DAY_OF_MONTH, item.everyNr)
}
ObserveSourcesRepository.EveryCategory.WEEK -> {
if(item.everyWeekDay.isEmpty()){
c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
}else{
val weekDayID = c.get(Calendar.DAY_OF_WEEK).toString()
val followingWeekDay = (item.everyWeekDay.firstOrNull { it.toInt() > weekDayID.toInt() } ?: item.everyWeekDay.minBy { it.toInt() }).toInt()
c.set(Calendar.DAY_OF_WEEK, followingWeekDay)
if (item.everyNr > 1){
c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
}
}
}
ObserveSourcesRepository.EveryCategory.MONTH -> {
c.add(Calendar.MONTH, item.everyNr)
c.set(Calendar.DAY_OF_MONTH, item.everyMonthDay)
}
}
alarmManager.setExact(
AlarmManager.RTC,
c.timeInMillis,
AlarmManager.RTC_WAKEUP,
repo.calculateNextTime(item),
PendingIntent.getBroadcast(context, item.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
)
return Result.success()
}
}

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"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/pause"
android:layoutAnimation="@anim/popup_enter"
app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp"

View file

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

View file

@ -500,6 +500,13 @@
android:checked="false"
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>

View file

@ -3,6 +3,13 @@
xmlns:tools="http://schemas.android.com/tools"
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
android:id="@+id/delete_results"
android:title="@string/remove_results"
@ -25,5 +32,11 @@
android:title="@string/invert_selected"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:icon="@drawable/ic_delete_all"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -3,6 +3,13 @@
xmlns:tools="http://schemas.android.com/tools"
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
android:id="@+id/delete_results"
android:title="@string/remove_results"
@ -32,5 +39,11 @@
android:title="@string/invert_selected"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:icon="@drawable/ic_delete_all"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -3,6 +3,14 @@
xmlns:tools="http://schemas.android.com/tools"
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
android:id="@+id/delete_results"
android:title="@string/remove_results"
@ -25,5 +33,11 @@
android:title="@string/invert_selected"
app:showAsAction="never" />
<item
android:id="@+id/copy_urls"
android:icon="@drawable/ic_delete_all"
android:title="@string/copy_urls"
app:showAsAction="never" />
</menu>

View file

@ -372,4 +372,7 @@
<string name="text_size">Text Size</string>
<string name="add">Add</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>

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