fixed webview ok button
slightly improved listing of items if u have alot of items fixed app not enabling ok button in the cut screen if you change the TO text box only added force keyframes at cuts switch in the cut screen Added 3 dots in multiple download card. It will have the configure chips in a separate card. Each will have a callback that will update all items in multiple download fixed start end textboxes showing truncanted text in playlist select card add ability to start a download now and put the rest of the queue behind it Scrollbar handle shrinked depending on size. Made it same size Fixed Tapping the notification of the errored download leading to the running tab instead of errored Set different icon for terminal in share menu ignored extra command preference in backups of command templates made preference titles be multiline if they are too long if the preferred download type is command, the app will not bother fetching data beforehand fixed app not showing the entire interface when being in landscape hid format details in command type logs because they are useless fixed cookies import not working from external sources other than the app fixed app using continue button for a split second when there are no paused downloads added state in home screen so that it wont populate trending videos while its searching for an item implemented batch downloads in a single worker to avoid bogging down the system with many works fixed instagram status that have multiple videos just using the first video fixed monochrome icon not showing made yt-dlp auto update a choice in the settings fixed bilibili not working in normal mode fixed app overwriting files instead of adding (1) in the filename instead
|
|
@ -1,4 +1,4 @@
|
|||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="corretto-17" project-jdk-type="JavaSDK" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
|
|
@ -2,15 +2,15 @@ plugins {
|
|||
id 'com.android.application'
|
||||
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-kapt'
|
||||
id 'com.google.devtools.ksp'
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10"
|
||||
}
|
||||
|
||||
def properties = new Properties()
|
||||
def versionMajor = 1
|
||||
def versionMinor = 6
|
||||
def versionPatch = 3
|
||||
def versionBuild = 17 // bump for dogfood builds, public betas, etc.
|
||||
def versionPatch = 5
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
def versionExt = ""
|
||||
|
||||
if (versionBuild > 0){
|
||||
|
|
@ -49,10 +49,8 @@ android {
|
|||
useSupportLibrary true
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -76,6 +74,8 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
debuggable false
|
||||
signingConfig signingConfigs.debug
|
||||
applicationIdSuffix '.beta'
|
||||
versionNameSuffix '-Beta'
|
||||
}
|
||||
|
||||
debug {
|
||||
|
|
@ -98,7 +98,7 @@ android {
|
|||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
android.applicationVariants.configureEach { variant ->
|
||||
variant.outputs.each { output ->
|
||||
def baseAbiVersionCode = project.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI))
|
||||
if (baseAbiVersionCode != null) {
|
||||
|
|
@ -149,7 +149,7 @@ dependencies {
|
|||
implementation 'com.google.android.material:material:1.9.0-rc01'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$navVer"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
|
|
@ -174,16 +174,18 @@ dependencies {
|
|||
|
||||
implementation "androidx.room:room-runtime:2.5.2"
|
||||
implementation "androidx.room:room-ktx:2.5.2"
|
||||
kapt "androidx.room:room-compiler:2.5.2"
|
||||
ksp "androidx.room:room-compiler:2.5.2"
|
||||
implementation 'androidx.paging:paging-runtime-ktx:3.2.0'
|
||||
implementation "androidx.room:room-paging:2.5.2"
|
||||
androidTestImplementation "androidx.room:room-testing:2.5.2"
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||
implementation "androidx.compose.runtime:runtime:$composeVer"
|
||||
androidTestImplementation "com.google.truth:truth:1.1.3"
|
||||
androidTestImplementation 'com.google.truth:truth:1.1.5'
|
||||
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVer"
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVer"
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.9.1'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||
|
|
@ -200,7 +202,7 @@ dependencies {
|
|||
implementation("androidx.media3:media3-ui:$media3_version")
|
||||
// For RTSP playback support with ExoPlayer
|
||||
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
|
||||
implementation("androidx.media3:media3-datasource-cronet:$media3_version")
|
||||
|
||||
implementation "com.anggrayudi:storage:1.5.5"
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,17 @@
|
|||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="audio/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".receiver.ShareFileActivity"
|
||||
|
|
@ -96,6 +107,8 @@
|
|||
android:name=".terminalShareAlias"
|
||||
android:targetActivity=".ui.more.TerminalActivity"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/terminal_icon"
|
||||
android:roundIcon="@mipmap/terminal_icon_round"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
|
@ -204,7 +217,7 @@
|
|||
</service>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.deniscerri.ytdl.fileprovider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:enabled="true"
|
||||
android:grantUriPermissions="true">
|
||||
|
|
@ -212,18 +225,6 @@
|
|||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
|
||||
<!-- If you are using androidx.startup to initialize other components -->
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -9,14 +9,11 @@ import androidx.work.Configuration
|
|||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkQuery
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
import com.deniscerri.ytdlnis.work.UpdateYTDLWorker
|
||||
import com.google.android.gms.common.internal.Constants
|
||||
import com.yausername.aria2c.Aria2c
|
||||
import com.yausername.ffmpeg.FFmpeg
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
|
@ -50,21 +47,27 @@ class App : Application() {
|
|||
}
|
||||
}
|
||||
|
||||
//init yt-dlp auto update with work request
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build()
|
||||
if (sharedPreferences.getBoolean("auto_update_ytdlp", true)){
|
||||
//init yt-dlp auto update with work request
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build()
|
||||
|
||||
val periodicWorkRequest = PeriodicWorkRequestBuilder<UpdateYTDLWorker>(1, TimeUnit.DAYS)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(this@App).enqueueUniquePeriodicWork(
|
||||
"ytdlp-Updater",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
periodicWorkRequest
|
||||
)
|
||||
}else{
|
||||
WorkManager.getInstance(this@App).cancelUniqueWork("ytdlp-Updater")
|
||||
}
|
||||
|
||||
val periodicWorkRequest = PeriodicWorkRequestBuilder<UpdateYTDLWorker>(1, TimeUnit.DAYS)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(this@App).enqueueUniquePeriodicWork(
|
||||
"ytdlp-Updater",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
periodicWorkRequest
|
||||
)
|
||||
|
||||
}catch (e: Exception){
|
||||
Looper.prepare().runCatching {
|
||||
|
|
@ -73,14 +76,6 @@ class App : Application() {
|
|||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
WorkManager.initialize(
|
||||
this@App,
|
||||
Configuration.Builder()
|
||||
.setExecutor(Executors.newFixedThreadPool(
|
||||
sharedPreferences.getInt("concurrent_downloads", 1)))
|
||||
.build())
|
||||
|
||||
}
|
||||
@Throws(YoutubeDLException::class)
|
||||
private fun initLibraries() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.SharedPreferences
|
|||
import android.graphics.Color
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -106,14 +107,14 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
}
|
||||
|
||||
val formatDetailsChip = card.findViewById<Chip>(R.id.format_note)
|
||||
val formatDetailsText = StringBuilder(item.format.format_note.uppercase())
|
||||
|
||||
formatDetailsText.append(" \t •\t ${item.container.uppercase().ifEmpty { item.format.container.uppercase() }}")
|
||||
val sideDetails = mutableListOf<String>()
|
||||
sideDetails.add(item.format.format_note.uppercase())
|
||||
sideDetails.add(item.container.uppercase().ifEmpty { item.format.container.uppercase() })
|
||||
|
||||
val fileSize = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSize != "?") formatDetailsText.append(" \t •\t $fileSize")
|
||||
|
||||
formatDetailsChip.text = formatDetailsText
|
||||
if (fileSize != "?") sideDetails.add(fileSize)
|
||||
formatDetailsChip.text = sideDetails.joinToString(" · ")
|
||||
|
||||
//OUTPUT
|
||||
val output = card.findViewById<TextView>(R.id.output)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.adapter
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Color
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -11,10 +10,10 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
|
|
@ -24,16 +23,18 @@ import com.google.android.material.button.MaterialButton
|
|||
import com.google.android.material.card.MaterialCardView
|
||||
import com.squareup.picasso.Picasso
|
||||
|
||||
class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, GenericDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||
class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter<DownloadItem, GenericDownloadAdapter.ViewHolder>(DIFF_CALLBACK) {
|
||||
private val onItemClickListener: OnItemClickListener
|
||||
private val activity: Activity
|
||||
private val checkedItems: ArrayList<Long>
|
||||
val checkedItems: ArrayList<Long>
|
||||
var inverted: Boolean
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
checkedItems = ArrayList()
|
||||
this.onItemClickListener = onItemClickListener
|
||||
this.activity = activity
|
||||
this.inverted = false
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
}
|
||||
|
||||
|
|
@ -53,13 +54,16 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.cardView
|
||||
card.isVisible = item != null
|
||||
if (item == null) return
|
||||
card.tag = item.id.toString()
|
||||
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||
|
||||
// THUMBNAIL ----------------------------------
|
||||
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
|
||||
val imageURL = item!!.thumb
|
||||
val imageURL = item.thumb
|
||||
if (imageURL.isNotEmpty()) {
|
||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||
} else {
|
||||
|
|
@ -127,7 +131,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
actionButton.setOnClickListener {
|
||||
onItemClickListener.onActionButtonClick(item.id)
|
||||
}
|
||||
if (checkedItems.contains(item.id)) {
|
||||
if ((checkedItems.contains(item.id) && !inverted) || (!checkedItems.contains(item.id) && inverted)) {
|
||||
card.isChecked = true
|
||||
card.strokeWidth = 5
|
||||
} else {
|
||||
|
|
@ -135,67 +139,69 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
card.strokeWidth = 0
|
||||
}
|
||||
card.setOnClickListener {
|
||||
if (checkedItems.size > 0) {
|
||||
checkCard(card, item.id)
|
||||
if (checkedItems.size > 0 || inverted) {
|
||||
checkCard(card, item.id, position)
|
||||
} else {
|
||||
onItemClickListener.onCardClick(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
card.setOnLongClickListener {
|
||||
checkCard(card, item.id)
|
||||
checkCard(card, item.id, position)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clearCheckeditems() {
|
||||
for (i in 0 until itemCount){
|
||||
val item = getItem(i)
|
||||
if (checkedItems.find { it == item?.id } != null){
|
||||
checkedItems.remove(item?.id)
|
||||
notifyItemChanged(i)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCheckedItems() {
|
||||
inverted = false
|
||||
checkedItems.clear()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkAll(items: List<DownloadItem?>?){
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(items!!.map { it!!.id })
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun invertSelected(items: List<DownloadItem?>?){
|
||||
val invertedList = mutableListOf<Long>()
|
||||
items?.forEach {
|
||||
if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
|
||||
}
|
||||
fun checkAll() {
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(invertedList)
|
||||
inverted = true
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun checkCard(card: MaterialCardView, itemID: Long) {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun invertSelected() {
|
||||
inverted = !inverted
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun getSelectedObjectsCount(totalSize: Int) : Int{
|
||||
return if (inverted){
|
||||
totalSize - checkedItems.size
|
||||
}else{
|
||||
checkedItems.size
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun checkCard(card: MaterialCardView, itemID: Long, position: Int) {
|
||||
if (card.isChecked) {
|
||||
card.strokeWidth = 0
|
||||
checkedItems.remove(itemID)
|
||||
if (inverted) checkedItems.add(itemID)
|
||||
else checkedItems.remove(itemID)
|
||||
} else {
|
||||
card.strokeWidth = 5
|
||||
checkedItems.add(itemID)
|
||||
if (inverted) checkedItems.remove(itemID)
|
||||
else checkedItems.add(itemID)
|
||||
}
|
||||
|
||||
card.isChecked = !card.isChecked
|
||||
onItemClickListener.onCardSelect(itemID, card.isChecked)
|
||||
onItemClickListener.onCardSelect(card.isChecked, position)
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onActionButtonClick(itemID: Long)
|
||||
fun onCardClick(itemID: Long)
|
||||
fun onCardSelect(itemID: Long, isChecked: Boolean)
|
||||
fun onCardSelect(isChecked: Boolean, position: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.squareup.picasso.Picasso
|
||||
import org.w3c.dom.Text
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
@ -70,17 +71,9 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
|||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||
}
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
var title = item!!.title
|
||||
if (title.length > 100) {
|
||||
title = title.substring(0, 40) + "..."
|
||||
}
|
||||
itemTitle.text = title
|
||||
|
||||
|
||||
val duration = card.findViewById<TextView>(R.id.duration)
|
||||
duration.text = item.duration
|
||||
card.findViewById<TextView>(R.id.title).text = item!!.title
|
||||
card.findViewById<TextView>(R.id.author).text = item.author
|
||||
card.findViewById<TextView>(R.id.duration).text = item.duration
|
||||
|
||||
// CHECKBOX ----------------------------------
|
||||
val check = card.findViewById<CheckBox>(R.id.checkBox)
|
||||
|
|
|
|||
|
|
@ -13,16 +13,9 @@ import java.lang.reflect.Type
|
|||
|
||||
class Converters {
|
||||
@TypeConverter
|
||||
fun stringToListOfFormats(value: String?): ArrayList<Format> {
|
||||
val listType: Type = object : TypeToken<ArrayList<Format?>?>() {}.type
|
||||
return Gson().fromJson(value, listType)
|
||||
}
|
||||
|
||||
fun stringToListOfFormats(value: String) = Gson().fromJson(value, Array<Format>::class.java).toMutableList()
|
||||
@TypeConverter
|
||||
fun listOfFormatsToString(list: ArrayList<Format?>?): String {
|
||||
val gson = Gson()
|
||||
return gson.toJson(list)
|
||||
}
|
||||
fun listOfFormatsToString(list: List<Format?>?) = Gson().toJson(list).toString()
|
||||
|
||||
@TypeConverter
|
||||
fun formatToString(format: Format): String = Gson().toJson(format)
|
||||
|
|
@ -53,13 +46,10 @@ class Converters {
|
|||
fun stringToVideoPreferences(string: String): VideoPreferences = Gson().fromJson(string, VideoPreferences::class.java)
|
||||
|
||||
@TypeConverter
|
||||
fun stringToListOfChapters(value: String?): ArrayList<ChapterItem>? {
|
||||
val listType: Type = object : TypeToken<ArrayList<ChapterItem?>?>() {}.type
|
||||
return Gson().fromJson(value, listType)
|
||||
}
|
||||
fun stringToListOfChapters(value: String?) = Gson().fromJson(value, Array<ChapterItem>::class.java).toMutableList()
|
||||
|
||||
@TypeConverter
|
||||
fun listOfChaptersToString(list: ArrayList<ChapterItem?>?): String {
|
||||
fun listOfChaptersToString(list: List<ChapterItem?>?): String {
|
||||
val gson = Gson()
|
||||
return gson.toJson(list)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
package com.deniscerri.ytdlnis.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.dao.*
|
||||
import com.deniscerri.ytdlnis.database.models.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@TypeConverters(Converters::class)
|
||||
@Database(
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface DownloadDao {
|
||||
|
||||
@Query("SELECT * FROM downloads ORDER BY status")
|
||||
fun getAllDownloads() : Flow<List<DownloadItem>>
|
||||
fun getAllDownloads() : PagingSource<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
|
||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status='Active' or status='Paused'")
|
||||
fun getActiveDownloadsCount() : Flow<Int>
|
||||
fun getActiveDownloadsCountFlow() : Flow<Int>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||
fun getDownloadsCountByStatus(status : List<String>) : Flow<Int>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
|
||||
fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
|
@ -22,26 +28,41 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||
fun getActiveAndQueuedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' ORDER BY downloadStartTime, id")
|
||||
fun getQueuedDownloads() : Flow<List<DownloadItem>>
|
||||
fun getQueuedDownloads() : PagingSource<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT format FROM downloads WHERE status='Queued' or status='QueuedPaused'")
|
||||
fun getSelectedFormatFromQueued() : List<Format>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE downloadStartTime <= :currentTime and status='Queued' ORDER BY downloadStartTime, id LIMIT 20")
|
||||
fun getQueuedDownloadsThatAreNotScheduledChunked(currentTime: Long) : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' or status='Paused' ORDER BY downloadStartTime, id")
|
||||
fun getQueuedAndPausedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' ORDER BY downloadStartTime, id")
|
||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||
fun getCancelledDownloads() : Flow<List<DownloadItem>>
|
||||
fun getCancelledDownloads() : PagingSource<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||
fun getCancelledDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status LIKE '%Paused%'")
|
||||
fun getPausedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloads() : Flow<List<DownloadItem>>
|
||||
fun getErroredDownloads() : PagingSource<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
|
||||
fun getSavedDownloads() : Flow<List<DownloadItem>>
|
||||
fun getSavedDownloads() : PagingSource<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||
fun getDownloadById(id: Long) : DownloadItem
|
||||
|
|
@ -52,6 +73,22 @@ interface DownloadDao {
|
|||
@Query("SELECT status FROM downloads WHERE id=:id")
|
||||
fun checkStatus(id: Long) : String
|
||||
|
||||
@Query("UPDATE downloads " +
|
||||
"SET status = CASE " +
|
||||
" WHEN status = 'Active' THEN 'Paused' " +
|
||||
" WHEN status = 'Queued' THEN 'QueuedPaused' " +
|
||||
" ELSE status " +
|
||||
" END;")
|
||||
fun pauseActiveAndQueued()
|
||||
|
||||
@Query("UPDATE downloads " +
|
||||
"SET status = CASE " +
|
||||
" WHEN status = 'Paused' THEN 'Active' " +
|
||||
" WHEN status = 'QueuedPaused' THEN 'Queued' " +
|
||||
" ELSE status " +
|
||||
" END;")
|
||||
fun unPauseActiveAndQueued()
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(item: DownloadItem) : Long
|
||||
|
||||
|
|
@ -73,7 +110,7 @@ interface DownloadDao {
|
|||
@Query("DELETE FROM downloads WHERE status='Saved'")
|
||||
suspend fun deleteSaved()
|
||||
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued'")
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued' or status='QueuedPaused'")
|
||||
suspend fun cancelQueued()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
||||
|
|
@ -82,6 +119,9 @@ interface DownloadDao {
|
|||
@Upsert
|
||||
suspend fun update(item: DownloadItem)
|
||||
|
||||
@Upsert
|
||||
fun updateMultiple(items :List<DownloadItem>)
|
||||
|
||||
@Query("SELECT * FROM downloads ORDER BY id DESC LIMIT 1")
|
||||
fun getLatest() : DownloadItem
|
||||
|
||||
|
|
@ -94,4 +134,38 @@ interface DownloadDao {
|
|||
|
||||
@Query("SELECT * FROM downloads WHERE url=:url AND format=:format AND (status='Error' OR status='Cancelled') LIMIT 1")
|
||||
fun getUnfinishedByURLAndFormat(url: String, format: String) : DownloadItem
|
||||
|
||||
|
||||
@Query("SELECT COUNT(downloadStartTime) = 0 from downloads WHERE id in (:items) AND " +
|
||||
"CASE :inverted " +
|
||||
"WHEN :inverted = 'false' THEN downloadStartTime > :currentStartTime " +
|
||||
"WHEN :inverted = 'true' THEN downloadStartTime < :currentStartTime ELSE downloadStartTime < -1 END")
|
||||
fun checkAllQueuedItemsAreScheduledAfterNow(items: List<Long>, inverted: String, currentStartTime: Long) : Boolean
|
||||
|
||||
|
||||
@Query("Select id from downloads where id not in (:list) and status in (:status)")
|
||||
fun getDownloadIDsNotPresentInList(list: List<Long>, status: List<String>) : List<Long>
|
||||
|
||||
@Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)")
|
||||
fun resetScheduleTimeForItems(list: List<Long>)
|
||||
|
||||
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
|
||||
fun reQueueDownloadItems(list: List<Long>)
|
||||
|
||||
@Transaction
|
||||
fun putAtTopOfTheQueue(id: Long){
|
||||
val downloads = getQueuedDownloadsList()
|
||||
val newID = downloads.first().id
|
||||
|
||||
updateDownloadID(id, -id)
|
||||
resetScheduleTimeForItems(listOf(-id))
|
||||
|
||||
downloads.reversed().dropWhile { it.id == id }.forEach {
|
||||
updateDownloadID(it.id, it.id + 1)
|
||||
}
|
||||
updateDownloadID(-id, newID)
|
||||
}
|
||||
|
||||
@Query("Update downloads set id=:newId where id=:id")
|
||||
fun updateDownloadID(id: Long, newId: Long)
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ interface SearchHistoryDao {
|
|||
fun getAllByKeyword(keyword: String) : List<SearchHistoryItem>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insert(new: SearchHistoryItem)
|
||||
suspend fun insert(newItem: SearchHistoryItem)
|
||||
|
||||
@Query("DELETE FROM searchHistory")
|
||||
suspend fun deleteAll()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ data class DownloadItem(
|
|||
var container: String,
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var downloadSections: String,
|
||||
val allFormats: ArrayList<Format>,
|
||||
val allFormats: MutableList<Format>,
|
||||
var downloadPath: String,
|
||||
var website: String,
|
||||
val downloadSize: String,
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ data class ResultItem(
|
|||
val thumb: String,
|
||||
val website: String,
|
||||
var playlistTitle: String,
|
||||
var formats: ArrayList<Format>,
|
||||
var formats: MutableList<Format>,
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var urls: String,
|
||||
var chapters: ArrayList<ChapterItem>?,
|
||||
var chapters: MutableList<ChapterItem>?,
|
||||
var creationTime: Long = System.currentTimeMillis() / 1000,
|
||||
)
|
||||
|
|
@ -1,19 +1,36 @@
|
|||
package com.deniscerri.ytdlnis.database.repository
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.deniscerri.ytdlnis.database.Converters
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||
val allDownloads : Flow<List<DownloadItem>> = downloadDao.getAllDownloads()
|
||||
val allDownloads : Pager<Int, DownloadItem> = Pager(
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
pagingSourceFactory = {downloadDao.getAllDownloads()}
|
||||
)
|
||||
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads()
|
||||
val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCount()
|
||||
val queuedDownloads : Flow<List<DownloadItem>> = downloadDao.getQueuedDownloads()
|
||||
val cancelledDownloads : Flow<List<DownloadItem>> = downloadDao.getCancelledDownloads()
|
||||
val erroredDownloads : Flow<List<DownloadItem>> = downloadDao.getErroredDownloads()
|
||||
val savedDownloads : Flow<List<DownloadItem>> = downloadDao.getSavedDownloads()
|
||||
val queuedDownloads : Pager<Int, DownloadItem> = Pager(
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
pagingSourceFactory = {downloadDao.getQueuedDownloads()}
|
||||
)
|
||||
val cancelledDownloads : Pager<Int, DownloadItem> = Pager(
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
pagingSourceFactory = {downloadDao.getCancelledDownloads()}
|
||||
)
|
||||
val erroredDownloads : Pager<Int, DownloadItem> = Pager(
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
pagingSourceFactory = {downloadDao.getErroredDownloads()}
|
||||
)
|
||||
val savedDownloads : Pager<Int, DownloadItem> = Pager(
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
pagingSourceFactory = {downloadDao.getSavedDownloads()}
|
||||
)
|
||||
|
||||
val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCountFlow()
|
||||
|
||||
enum class Status {
|
||||
Active, Paused, Queued, QueuedPaused, Error, Cancelled, Saved
|
||||
|
|
@ -27,8 +44,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.insertAll(items)
|
||||
}
|
||||
|
||||
suspend fun delete(item: DownloadItem){
|
||||
downloadDao.delete(item.id)
|
||||
suspend fun delete(id: Long){
|
||||
downloadDao.delete(id)
|
||||
}
|
||||
|
||||
suspend fun update(item: DownloadItem){
|
||||
|
|
@ -61,6 +78,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getCancelledDownloadsList()
|
||||
}
|
||||
|
||||
fun getPausedDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getPausedDownloadsList()
|
||||
}
|
||||
|
||||
fun getErroredDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getErroredDownloadsList()
|
||||
}
|
||||
|
|
@ -81,20 +102,12 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
downloadDao.cancelQueued()
|
||||
}
|
||||
|
||||
fun getLastDownloadId() : Long {
|
||||
return downloadDao.getLastDownloadId()
|
||||
fun pauseDownloads(){
|
||||
downloadDao.pauseActiveAndQueued()
|
||||
}
|
||||
|
||||
|
||||
fun checkIfReDownloadingErroredOrCancelled(item: DownloadItem) : Long {
|
||||
val converters = Converters()
|
||||
val format = converters.formatToString(item.format)
|
||||
return try {
|
||||
val i = downloadDao.getUnfinishedByURLAndFormat(item.url, format)
|
||||
i.id
|
||||
}catch (e: Exception){
|
||||
0L
|
||||
}
|
||||
fun unPauseDownloads(){
|
||||
downloadDao.unPauseActiveAndQueued()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,23 +12,24 @@ import androidx.lifecycle.AndroidViewModel
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anggrayudi.storage.file.dataDirectory
|
||||
import com.deniscerri.ytdlnis.BuildConfig
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
||||
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
|
||||
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||
private val repository: CookieRepository
|
||||
val items: LiveData<List<CookieItem>>
|
||||
private val jsonFormat = Json { prettyPrint = true }
|
||||
|
||||
private val cookieHeader = "# Netscape HTTP Cookie File\n" +
|
||||
"# WebView Generated by the YTDLnis app\n"
|
||||
private val cookieHeader =
|
||||
"# Netscape HTTP Cookie File\n" +
|
||||
"# WebView Generated by the YTDLnis app\n" +
|
||||
"# This is a generated file! Do not edit."
|
||||
|
||||
init {
|
||||
val dao = DBManager.getInstance(application).cookieDao
|
||||
|
|
@ -72,8 +73,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
if (!hasCookies()) throw Exception("There is no cookies in the database!")
|
||||
flush()
|
||||
}
|
||||
|
||||
val dbPath = File("/data/data/com.deniscerri.ytdl/").walkTopDown().find { it.name == "Cookies" }
|
||||
val dbPath = File("/data/data/${BuildConfig.APPLICATION_ID}/").walkTopDown().find { it.name == "Cookies" }
|
||||
?: throw Exception("Cookies File not found!")
|
||||
|
||||
SQLiteDatabase.openDatabase(
|
||||
|
|
@ -139,11 +139,12 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
var clip = clipboard.primaryClip!!.getItemAt(0).text
|
||||
Log.e("Aaa", clip.toString())
|
||||
if (clip.startsWith(cookieHeader)){
|
||||
|
||||
if (clip.startsWith("# Netscape HTTP Cookie File")){
|
||||
clip = clip.removePrefix(cookieHeader)
|
||||
val cookie = CookieItem(
|
||||
0,
|
||||
"Cookie Import [${System.currentTimeMillis()}]",
|
||||
"Cookie Import at [${Date()}]",
|
||||
clip.toString()
|
||||
)
|
||||
insert(cookie)
|
||||
|
|
|
|||
|
|
@ -8,22 +8,20 @@ import android.content.res.Resources
|
|||
import android.net.ConnectivityManager
|
||||
import android.os.Looper
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkRequest
|
||||
import androidx.work.workDataOf
|
||||
import androidx.work.await
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
|
|
@ -44,18 +42,12 @@ import com.deniscerri.ytdlnis.work.DownloadWorker
|
|||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextInt
|
||||
|
||||
|
||||
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
|
@ -64,13 +56,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
private val sharedPreferences: SharedPreferences
|
||||
private val commandTemplateDao: CommandTemplateDao
|
||||
private val infoUtil : InfoUtil
|
||||
val allDownloads : LiveData<List<DownloadItem>>
|
||||
val queuedDownloads : LiveData<List<DownloadItem>>
|
||||
val allDownloads : Flow<PagingData<DownloadItem>>
|
||||
val queuedDownloads : Flow<PagingData<DownloadItem>>
|
||||
val activeDownloads : LiveData<List<DownloadItem>>
|
||||
val activeDownloadsCount : LiveData<Int>
|
||||
val cancelledDownloads : LiveData<List<DownloadItem>>
|
||||
val erroredDownloads : LiveData<List<DownloadItem>>
|
||||
val savedDownloads : LiveData<List<DownloadItem>>
|
||||
val cancelledDownloads : Flow<PagingData<DownloadItem>>
|
||||
val erroredDownloads : Flow<PagingData<DownloadItem>>
|
||||
val savedDownloads : Flow<PagingData<DownloadItem>>
|
||||
|
||||
private var bestVideoFormat : Format
|
||||
private var bestAudioFormat : Format
|
||||
|
|
@ -81,6 +73,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
private val audioFormatIDPreference: List<String>
|
||||
private val resources : Resources
|
||||
private var extraCommands: String = ""
|
||||
|
||||
private var audioContainer: String?
|
||||
private var videoContainer: String?
|
||||
private var videoCodec: String?
|
||||
|
||||
enum class Type {
|
||||
audio, video, command
|
||||
}
|
||||
|
|
@ -93,13 +90,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||
infoUtil = InfoUtil(application)
|
||||
|
||||
allDownloads = repository.allDownloads.asLiveData()
|
||||
queuedDownloads = repository.queuedDownloads.asLiveData()
|
||||
allDownloads = repository.allDownloads.flow
|
||||
queuedDownloads = repository.queuedDownloads.flow
|
||||
activeDownloads = repository.activeDownloads.asLiveData()
|
||||
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
|
||||
savedDownloads = repository.savedDownloads.asLiveData()
|
||||
cancelledDownloads = repository.cancelledDownloads.asLiveData()
|
||||
erroredDownloads = repository.erroredDownloads.asLiveData()
|
||||
savedDownloads = repository.savedDownloads.flow
|
||||
cancelledDownloads = repository.cancelledDownloads.flow
|
||||
erroredDownloads = repository.erroredDownloads.flow
|
||||
viewModelScope.launch(Dispatchers.IO){
|
||||
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||
extraCommands = commandTemplateDao.getAllTemplatesAsExtraCommands().joinToString(" ")
|
||||
|
|
@ -107,8 +104,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
}
|
||||
|
||||
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",")
|
||||
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",")
|
||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
|
||||
val confTmp = Configuration(application.resources.configuration)
|
||||
confTmp.locale = Locale(sharedPreferences.getString("app_language", "en")!!)
|
||||
|
|
@ -117,7 +114,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
|
||||
|
||||
val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
|
||||
val videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||
videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||
|
||||
defaultVideoFormats = mutableListOf()
|
||||
videoFormatValues.forEach {
|
||||
|
|
@ -132,29 +129,55 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
)
|
||||
defaultVideoFormats.add(tmp)
|
||||
}
|
||||
formatIDPreference.reversed().forEach {
|
||||
val tmp = Format(
|
||||
it,
|
||||
videoContainer!!,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
it
|
||||
)
|
||||
defaultVideoFormats.add(tmp)
|
||||
}
|
||||
|
||||
bestVideoFormat = defaultVideoFormats.last()
|
||||
|
||||
val audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||
bestAudioFormat = Format(
|
||||
"best",
|
||||
audioContainer!!,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
"best"
|
||||
)
|
||||
audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||
bestAudioFormat = if (audioFormatIDPreference.isEmpty()){
|
||||
Format(
|
||||
"best",
|
||||
audioContainer!!,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
"best"
|
||||
)
|
||||
}else{
|
||||
Format(
|
||||
audioFormatIDPreference.first(),
|
||||
audioContainer!!,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
audioFormatIDPreference.first()
|
||||
)
|
||||
}
|
||||
|
||||
videoCodec = sharedPreferences.getString("video_codec", "")
|
||||
}
|
||||
|
||||
fun deleteDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.delete(item)
|
||||
fun deleteDownload(id: Long) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.delete(id)
|
||||
}
|
||||
|
||||
suspend fun updateDownload(item: DownloadItem){
|
||||
if (sharedPreferences.getBoolean("incognito", false)){
|
||||
if (item.status == DownloadRepository.Status.Cancelled.toString() || item.status == DownloadRepository.Status.Error.toString()){
|
||||
repository.delete(item)
|
||||
repository.delete(item.id)
|
||||
}
|
||||
}else{
|
||||
repository.update(item)
|
||||
|
|
@ -345,7 +368,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
return cloneFormat (
|
||||
try {
|
||||
try{
|
||||
formats.first { it.format_note.contains("audio", ignoreCase = true) && audioFormatIDPreference.contains(it.format_id)}
|
||||
formats.filter { it.format_note.contains("audio", ignoreCase = true)}.first {
|
||||
audioFormatIDPreference.contains(it.format_id) ||
|
||||
it.container == audioContainer
|
||||
}
|
||||
}catch (e: Exception){
|
||||
formats.last { it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
|
|
@ -358,24 +384,19 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
Type.video -> {
|
||||
return cloneFormat(
|
||||
try {
|
||||
val theFormats = formats.ifEmpty { defaultVideoFormats }
|
||||
try {
|
||||
formats.first { !it.format_note.contains("audio", ignoreCase = true) && formatIDPreference.contains(it.format_id)}
|
||||
}catch (e: Exception){
|
||||
when (videoQualityPreference) {
|
||||
"worst" -> {
|
||||
theFormats.first {!it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
"best" -> {
|
||||
theFormats.last {!it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
else -> {
|
||||
try{
|
||||
theFormats.last {it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
|
||||
}catch (e: Exception){
|
||||
theFormats.last { !it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
val theFormats = formats.filter { !it.format_note.contains("audio", true) }.ifEmpty { defaultVideoFormats }
|
||||
when (videoQualityPreference) {
|
||||
"worst" -> {
|
||||
theFormats.first()
|
||||
}
|
||||
else /*best*/ -> {
|
||||
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
|
||||
requirements.add { it: Format -> formatIDPreference.contains(it.format_id) }
|
||||
requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
|
||||
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
|
||||
requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) }
|
||||
|
||||
theFormats.reversed().maxByOrNull { f -> requirements.count{ req -> req(f)} } ?: throw Exception()
|
||||
}
|
||||
}
|
||||
}catch (e: Exception){
|
||||
|
|
@ -406,6 +427,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("audio_format", "")
|
||||
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||
return formats
|
||||
}
|
||||
|
|
@ -414,6 +436,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("video_format", "")
|
||||
formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
|
||||
videoFormats.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
|
||||
return formats
|
||||
}
|
||||
|
|
@ -436,6 +459,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
repository.insert(item)
|
||||
}
|
||||
|
||||
fun pauseDownloads() = viewModelScope.launch(Dispatchers.IO){
|
||||
repository.pauseDownloads()
|
||||
}
|
||||
|
||||
fun unPauseDownloads() = viewModelScope.launch(Dispatchers.IO){
|
||||
repository.unPauseDownloads()
|
||||
}
|
||||
|
||||
fun insertAll(items: List<DownloadItem>)= viewModelScope.launch(Dispatchers.IO){
|
||||
items.forEach{
|
||||
repository.insert(it)
|
||||
|
|
@ -466,6 +497,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
return repository.getCancelledDownloads()
|
||||
}
|
||||
|
||||
fun getPausedDownloads() : List<DownloadItem> {
|
||||
return repository.getPausedDownloads()
|
||||
}
|
||||
|
||||
fun getErrored() : List<DownloadItem> {
|
||||
return repository.getErroredDownloads()
|
||||
}
|
||||
|
|
@ -483,10 +518,24 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
return repository.getActiveAndQueuedDownloads()
|
||||
}
|
||||
|
||||
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
||||
startDownloadWorker(emptyList())
|
||||
}
|
||||
|
||||
suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{
|
||||
dbManager.downloadDao.putAtTopOfTheQueue(id)
|
||||
startDownloadWorker(emptyList())
|
||||
}
|
||||
|
||||
suspend fun reQueueDownloadItems(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
dbManager.downloadDao.reQueueDownloadItems(items)
|
||||
startDownloadWorker(emptyList())
|
||||
}
|
||||
|
||||
suspend fun queueDownloads(items: List<DownloadItem>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
val context = App.instance
|
||||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
val queuedItems = mutableListOf<DownloadItem>()
|
||||
var exists = false
|
||||
|
||||
|
|
@ -518,33 +567,50 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
}
|
||||
}
|
||||
|
||||
if (queuedItems.isNotEmpty()){
|
||||
startDownloadWorker(queuedItems)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startDownloadWorker(queuedItems: List<DownloadItem>) {
|
||||
val context = App.instance
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
|
||||
queuedItems.forEach {
|
||||
val currentWork = workManager.getWorkInfosByTag("download").await()
|
||||
if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
var delay = if (it.downloadStartTime != 0L){
|
||||
it.downloadStartTime - currentTime
|
||||
} else 0
|
||||
if (delay < 0L) delay = 1L
|
||||
var delay = 1000L
|
||||
if (queuedItems.isNotEmpty()){
|
||||
delay = if (queuedItems[0].downloadStartTime != 0L){
|
||||
queuedItems[0].downloadStartTime.minus(currentTime)
|
||||
} else 0
|
||||
if (delay <= 0L) delay = 1000L
|
||||
}
|
||||
|
||||
|
||||
val workConstraints = Constraints.Builder()
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.setInputData(Data.Builder().putLong("id", it.id).build())
|
||||
.addTag(it.id.toString())
|
||||
.addTag("download")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(delay, TimeUnit.SECONDS)
|
||||
.build()
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
|
||||
if (delay > 1000L){
|
||||
queuedItems.forEach {
|
||||
workRequest.addTag(it.id.toString())
|
||||
}
|
||||
}
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
it.id.toString(),
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
||||
|
|
@ -553,12 +619,29 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
}
|
||||
}
|
||||
|
||||
items.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }?.forEach {
|
||||
try{
|
||||
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
||||
}catch (ignored: Exception){}
|
||||
}
|
||||
}
|
||||
|
||||
fun getQueuedCollectedFileSize() : Long {
|
||||
return dbManager.downloadDao.getSelectedFormatFromQueued().sumOf { it.filesize }
|
||||
}
|
||||
|
||||
fun getTotalSize(status: List<DownloadRepository.Status>) : LiveData<Int> {
|
||||
return dbManager.downloadDao.getDownloadsCountByStatus(status.map { it.toString() }).asLiveData()
|
||||
}
|
||||
|
||||
fun checkAllQueuedItemsAreScheduledAfterNow(items: List<Long>, inverted: Boolean, currentStartTime: Long) : Boolean {
|
||||
return dbManager.downloadDao.checkAllQueuedItemsAreScheduledAfterNow(items, inverted.toString(), currentStartTime)
|
||||
}
|
||||
|
||||
fun getItemIDsNotPresentIn(items: List<Long>, status: List<DownloadRepository.Status>) : List<Long> {
|
||||
return dbManager.downloadDao.getDownloadIDsNotPresentInList(items.ifEmpty { listOf(-1L) }, status.map { it.toString() })
|
||||
}
|
||||
|
||||
private fun updateDownloadItem(
|
||||
downloadItem: DownloadItem,
|
||||
infoUtil: InfoUtil,
|
||||
|
|
|
|||
|
|
@ -28,10 +28,13 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
val items : LiveData<List<ResultItem>>
|
||||
val loadingItems = MutableLiveData<Boolean>()
|
||||
private val sharedPreferences: SharedPreferences
|
||||
var state: ResultsState = ResultsState.IDLE
|
||||
enum class ResultsState {
|
||||
PROCESSING, IDLE
|
||||
}
|
||||
|
||||
init {
|
||||
val dao = DBManager.getInstance(application).resultDao
|
||||
val commandDao = DBManager.getInstance(application).commandTemplateDao
|
||||
repository = ResultRepository(dao, getApplication<Application>().applicationContext)
|
||||
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
|
||||
items = repository.allResults.asLiveData()
|
||||
|
|
@ -61,6 +64,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
}
|
||||
}
|
||||
suspend fun parseQueries(inputQueries: List<String>){
|
||||
state = ResultsState.PROCESSING
|
||||
if (inputQueries.size == 1){
|
||||
parseQuery(inputQueries[0], true)
|
||||
}else {
|
||||
|
|
@ -69,6 +73,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
inputQueries.forEach {
|
||||
parseQuery(it, false)
|
||||
}
|
||||
state = ResultsState.IDLE
|
||||
loadingItems.postValue(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -97,7 +102,10 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
} catch (e: Exception) {
|
||||
Log.e(tag, e.toString())
|
||||
}
|
||||
if (resetResults) loadingItems.postValue(false)
|
||||
if (resetResults) {
|
||||
state = ResultsState.IDLE
|
||||
loadingItems.postValue(false)
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +115,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
val m = p.matcher(inputQuery)
|
||||
if (m.find()) {
|
||||
type = "YT_Video"
|
||||
if (inputQuery.contains("playlist?list=")) {
|
||||
if (inputQuery.contains("playlist?list=") && !inputQuery.contains("music.youtu")) {
|
||||
type = "YT_Playlist"
|
||||
}
|
||||
} else if (inputQuery.contains("http")) {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,8 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() {
|
|||
if (message != null) {
|
||||
runCatching {
|
||||
val notificationUtil = NotificationUtil(c)
|
||||
WorkManager.getInstance(c).cancelUniqueWork(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
val dbManager = DBManager.getInstance(c)
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
runCatching {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ package com.deniscerri.ytdlnis.receiver
|
|||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(c: Context, intent: Intent) {
|
||||
|
|
@ -16,6 +21,14 @@ class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
|||
notificationUtil.cancelDownloadNotification(id)
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.createResumeDownload(id, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
val dbManager = DBManager.getInstance(c)
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
runCatching {
|
||||
val item = dbManager.downloadDao.getDownloadById(id.toLong())
|
||||
item.status = DownloadRepository.Status.Paused.toString()
|
||||
dbManager.downloadDao.update(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
|||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.SelectPlaylistItemsBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.SelectPlaylistItemsDialog
|
||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
@ -95,10 +95,9 @@ class ShareActivity : BaseActivity() {
|
|||
askPermissions()
|
||||
|
||||
val action = intent.action
|
||||
val type = intent.clipData
|
||||
Log.e("aa", intent.toString())
|
||||
if (Intent.ACTION_SEND == action && type != null) {
|
||||
if (intent.getStringExtra(Intent.EXTRA_TEXT) == null){
|
||||
if (Intent.ACTION_SEND == action || Intent.ACTION_VIEW == action) {
|
||||
if (intent.getStringExtra(Intent.EXTRA_TEXT) == null && Intent.ACTION_SEND == action){
|
||||
intent.setClass(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
finishAffinity()
|
||||
|
|
@ -107,7 +106,7 @@ class ShareActivity : BaseActivity() {
|
|||
|
||||
runCatching { supportFragmentManager.popBackStack() }
|
||||
|
||||
quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false))
|
||||
quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false) || sharedPreferences.getString("preferred_download_type", "video") == "command")
|
||||
|
||||
val loadingBottomSheet = BottomSheetDialog(this)
|
||||
loadingBottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
|
@ -118,7 +117,11 @@ class ShareActivity : BaseActivity() {
|
|||
this.finish()
|
||||
}
|
||||
|
||||
val inputQuery = intent.getStringExtra(Intent.EXTRA_TEXT)!!
|
||||
val inputQuery = when(action){
|
||||
Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)!!
|
||||
else -> intent.dataString!!
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = resultViewModel.getItemByURL(inputQuery)
|
||||
|
|
@ -166,7 +169,7 @@ class ShareActivity : BaseActivity() {
|
|||
|
||||
private fun showSelectPlaylistItems(it: List<ResultItem?>){
|
||||
if (sharedPreferences.getBoolean("download_card", true)){
|
||||
val bottomSheet = SelectPlaylistItemsBottomSheetDialog(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
|
||||
val bottomSheet = SelectPlaylistItemsDialog(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
|
||||
bottomSheet.show(supportFragmentManager, "downloadPlaylistSheet")
|
||||
}else{
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import android.content.*
|
|||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.OvalShape
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
|
@ -40,6 +42,8 @@ import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog
|
|||
import com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.devbrackets.android.exomedia.core.video.mp.NativeVideoDelegate
|
||||
import com.facebook.shimmer.ShimmerFrameLayout
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
|
@ -55,6 +59,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
@ -146,6 +151,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
|||
recyclerView = view.findViewById(R.id.recyclerViewHome)
|
||||
recyclerView?.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
recyclerView?.adapter = homeAdapter
|
||||
recyclerView?.enableFastScroll()
|
||||
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
resultViewModel.items.observe(viewLifecycleOwner) {
|
||||
|
|
@ -251,12 +257,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
if(arguments?.getString("url") == null){
|
||||
resultViewModel.checkTrending()
|
||||
if (resultViewModel.state == ResultViewModel.ResultsState.IDLE){
|
||||
resultViewModel.checkTrending()
|
||||
}else{
|
||||
resultViewModel.loadingItems.postValue(true)
|
||||
}
|
||||
}else{
|
||||
arguments
|
||||
arguments?.remove("url")
|
||||
arguments?.remove("showDownloadsWithUpdatedFormats")
|
||||
}
|
||||
arguments?.remove("showDownloadsWithUpdatedFormats")
|
||||
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
|
||||
lifecycleScope.launch {
|
||||
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
|
||||
|
|
@ -533,7 +542,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
|||
resultViewModel.deleteAll()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
Thread.sleep(300)
|
||||
if(sharedPreferences!!.getBoolean("quick_download", false)){
|
||||
if(sharedPreferences!!.getBoolean("quick_download", false) || sharedPreferences!!.getString("preferred_download_type", "video") == "command"){
|
||||
if (queryList.size == 1 && queryList.first().matches("(https?://(?:www\\.|(?!www))[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|www\\.[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|https?://(?:www\\.|(?!www))[a-zA-Z\\d]+\\.\\S{2,}|www\\.[a-zA-Z\\d]+\\.\\S{2,})".toRegex())){
|
||||
val resultItem = downloadViewModel.createEmptyResultItem(queryList.first())
|
||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import java.util.*
|
|||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
class AddExtraCommandsDialog(private val item: DownloadItem, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {
|
||||
class AddExtraCommandsDialog(private val item: DownloadItem?, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||
|
|
@ -86,9 +86,14 @@ class AddExtraCommandsDialog(private val item: DownloadItem, private val callbac
|
|||
val text = view.findViewById<EditText>(R.id.command)
|
||||
val templates = view.findViewById<Button>(R.id.commands)
|
||||
val shortcuts = view.findViewById<Button>(R.id.shortcuts)
|
||||
val currentCommand = infoUtil.parseYTDLRequestString(infoUtil.buildYoutubeDLRequest(item))
|
||||
val currentText = view.findViewById<TextView>(R.id.currentText)
|
||||
currentText?.text = currentCommand
|
||||
|
||||
if (item != null){
|
||||
val currentCommand = infoUtil.parseYTDLRequestString(infoUtil.buildYoutubeDLRequest(item))
|
||||
currentText?.text = currentCommand
|
||||
}else{
|
||||
view.findViewById<View>(R.id.current).visibility = View.GONE
|
||||
}
|
||||
|
||||
|
||||
//init syntax highlighter
|
||||
|
|
@ -111,7 +116,10 @@ class AddExtraCommandsDialog(private val item: DownloadItem, private val callbac
|
|||
highlight.setSpan(currentText)
|
||||
currentText.addTextChangedListener(highlightWatcher)
|
||||
|
||||
text?.setText(item.extraCommands)
|
||||
highlight.setSpan(text)
|
||||
text.addTextChangedListener(highlightWatcher)
|
||||
|
||||
text?.setText(item?.extraCommands)
|
||||
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
text!!.postDelayed({
|
||||
text.setSelection(text.length())
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
|
|
@ -15,6 +16,7 @@ import android.view.View
|
|||
import android.widget.*
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.MediaItem.fromUri
|
||||
import androidx.media3.common.Player
|
||||
|
|
@ -23,6 +25,7 @@ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||
import androidx.media3.ui.PlayerView
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
|
|
@ -37,6 +40,7 @@ import com.google.android.material.chip.Chip
|
|||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.gson.Gson
|
||||
|
|
@ -68,6 +72,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
private lateinit var toTextInput : TextInputLayout
|
||||
private lateinit var cancelBtn : Button
|
||||
private lateinit var okBtn : Button
|
||||
private lateinit var forceKeyframes: MaterialSwitch
|
||||
private lateinit var suggestedChips: ChipGroup
|
||||
private lateinit var suggestedChapters : LinearLayout
|
||||
|
||||
|
|
@ -129,6 +134,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
newCutBtn = view.findViewById(R.id.new_cut)
|
||||
resetBtn = view.findViewById(R.id.reset_all)
|
||||
chipGroup = view.findViewById(R.id.cut_list_chip_group)
|
||||
forceKeyframes = view.findViewById(R.id.force_keyframes)
|
||||
|
||||
|
||||
selectedCuts = if (chipGroup.childCount == 0){
|
||||
|
|
@ -152,7 +158,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
if (urls.isNullOrEmpty()) {
|
||||
infoUtil.getStreamingUrlAndChapters(item.url)
|
||||
}else {
|
||||
urls!!.split("\n").toMutableList()
|
||||
urls.split("\n").toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +252,15 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
}catch (ignored: Exception) {}
|
||||
}
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val editor = prefs.edit()
|
||||
|
||||
forceKeyframes.isChecked = prefs.getBoolean("force_keyframes", false)
|
||||
forceKeyframes.setOnCheckedChangeListener { compoundButton, b ->
|
||||
editor.putBoolean("force_keyframes", forceKeyframes.isChecked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
|
|
@ -253,31 +268,25 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
fromTextInput.editText!!.setTextAndRecalculateWidth("0:00")
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(item.duration)
|
||||
|
||||
rangeSlider.addOnSliderTouchListener(object : RangeSlider.OnSliderTouchListener {
|
||||
override fun onStartTrackingTouch(slider: RangeSlider) {
|
||||
rangeSlider.setOnTouchListener { v, event -> // Handle touch events here
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
updateFromSlider()
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
updateFromSlider()
|
||||
}
|
||||
}
|
||||
// Return 'false' to allow the event to continue propagating or 'true' to consume it
|
||||
false
|
||||
}
|
||||
rangeSlider.performClick()
|
||||
|
||||
override fun onStopTrackingTouch(slider: RangeSlider) {
|
||||
val values = rangeSlider.values
|
||||
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
|
||||
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
|
||||
|
||||
val startTimestampString = infoUtil.formatIntegerDuration(startTimestamp, Locale.US)
|
||||
val endTimestampString = infoUtil.formatIntegerDuration(endTimestamp, Locale.US)
|
||||
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestampString)
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString)
|
||||
|
||||
|
||||
okBtn.isEnabled = values[0] != 0F && values[1] != 100F
|
||||
|
||||
try {
|
||||
player.seekTo((startTimestamp * 1000).toLong())
|
||||
player.play()
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
})
|
||||
rangeSlider.setOnDragListener { view, dragEvent ->
|
||||
updateFromSlider()
|
||||
false
|
||||
}
|
||||
|
||||
fromTextInput.editText!!.setOnKeyListener(object : View.OnKeyListener {
|
||||
|
||||
|
|
@ -304,8 +313,10 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
startValue = 0F
|
||||
}
|
||||
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(fromTextInput.editText!!.text.toString())
|
||||
|
||||
rangeSlider.setValues(startValue, endValue)
|
||||
okBtn.isEnabled = startValue != 0F && endValue != 100F
|
||||
okBtn.isEnabled = startValue != 0F || endValue != 100F
|
||||
try {
|
||||
player.seekTo(seconds.toLong() * 1000)
|
||||
player.play()
|
||||
|
|
@ -341,12 +352,14 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
|
||||
if (seconds == 0) {
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
||||
}else if (endValue > 100 || endValue <= rangeSlider.valueFrom.toInt()){
|
||||
}else if (endValue <= rangeSlider.valueFrom.toInt()){
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
||||
}
|
||||
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(toTextInput.editText!!.text.toString())
|
||||
|
||||
rangeSlider.setValues(startValue, endValue)
|
||||
okBtn.isEnabled = startValue != 0F && endValue != 100F
|
||||
okBtn.isEnabled = startValue != 0F || endValue != 100F
|
||||
try {
|
||||
player.seekTo(startSeconds.toLong() * 1000)
|
||||
player.play()
|
||||
|
|
@ -370,6 +383,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
|
||||
okBtn.isEnabled = false
|
||||
okBtn.setOnClickListener {
|
||||
forceKeyframes.isVisible = true
|
||||
val chip = createChip("${fromTextInput.editText!!.text}-${toTextInput.editText!!.text}")
|
||||
chip.performClick()
|
||||
cutSection.visibility = View.GONE
|
||||
|
|
@ -379,6 +393,26 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
populateSuggestedChapters()
|
||||
}
|
||||
|
||||
private fun updateFromSlider(){
|
||||
val values = rangeSlider.values
|
||||
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
|
||||
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
|
||||
|
||||
val startTimestampString = infoUtil.formatIntegerDuration(startTimestamp, Locale.US)
|
||||
val endTimestampString = infoUtil.formatIntegerDuration(endTimestamp, Locale.US)
|
||||
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestampString)
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString)
|
||||
|
||||
|
||||
okBtn.isEnabled = values[0] != 0F || values[1] != 100F
|
||||
|
||||
try {
|
||||
player.seekTo((startTimestamp * 1000).toLong())
|
||||
player.play()
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
|
||||
private fun populateSuggestedChapters(){
|
||||
if (chapters!!.isEmpty()) suggestedChapters.visibility = View.GONE
|
||||
else {
|
||||
|
|
@ -391,6 +425,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
chip.isCheckedIconVisible = false
|
||||
suggestedChips.addView(chip)
|
||||
chip.setOnClickListener { c ->
|
||||
forceKeyframes.isVisible = true
|
||||
val createdChip = createChapterChip(it, null)
|
||||
createdChip.performClick()
|
||||
cutSection.visibility = View.GONE
|
||||
|
|
@ -424,6 +459,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
}
|
||||
|
||||
if (item.downloadSections.isNotBlank()){
|
||||
forceKeyframes.isVisible = true
|
||||
chipGroup.removeAllViews()
|
||||
item.downloadSections.split(";").forEachIndexed { _, it ->
|
||||
if (it.isBlank()) return
|
||||
|
|
|
|||
|
|
@ -212,143 +212,34 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
if (containers[index] == getString(R.string.defaultValue)) downloadItem.container = ""
|
||||
}
|
||||
|
||||
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
|
||||
embedThumb!!.isChecked = downloadItem.audioPreferences.embedThumb
|
||||
embedThumb.setOnClickListener {
|
||||
downloadItem.audioPreferences.embedThumb = embedThumb.isChecked
|
||||
}
|
||||
|
||||
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
|
||||
if (downloadItem.downloadSections.isNotBlank()){
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
}else{
|
||||
splitByChapters!!.isChecked = downloadItem.audioPreferences.splitByChapters
|
||||
}
|
||||
|
||||
splitByChapters.setOnClickListener {
|
||||
downloadItem.audioPreferences.splitByChapters = splitByChapters.isChecked
|
||||
}
|
||||
|
||||
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
||||
filenameTemplate.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.file_name_template))
|
||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = getString(R.string.file_name_template)
|
||||
editText.setText(downloadItem.customFileNameTemplate)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.ok)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
downloadItem.customFileNameTemplate = editText.text.toString()
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
editText.doOnTextChanged { text, start, before, count ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
}
|
||||
dialog.show()
|
||||
val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
val sponsorblock = view.findViewById<Chip>(R.id.sponsorblock_filters)
|
||||
sponsorblock!!.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.select_sponsorblock_filtering))
|
||||
val values = resources.getStringArray(R.array.sponsorblock_settings_values)
|
||||
val entries = resources.getStringArray(R.array.sponsorblock_settings_entries)
|
||||
val checkedItems : ArrayList<Boolean> = arrayListOf()
|
||||
values.forEach {
|
||||
if (downloadItem.audioPreferences.sponsorBlockFilters.contains(it)) {
|
||||
checkedItems.add(true)
|
||||
}else{
|
||||
checkedItems.add(false)
|
||||
}
|
||||
}
|
||||
|
||||
builder.setMultiChoiceItems(
|
||||
entries,
|
||||
checkedItems.toBooleanArray()
|
||||
) { _, which, isChecked ->
|
||||
checkedItems[which] = isChecked
|
||||
}
|
||||
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
UiUtil.configureAudio(
|
||||
view,
|
||||
requireActivity(),
|
||||
listOf(downloadItem),
|
||||
embedThumbClicked = {
|
||||
downloadItem.audioPreferences.embedThumb = it
|
||||
},
|
||||
splitByChaptersClicked = {
|
||||
downloadItem.audioPreferences.splitByChapters = it
|
||||
},
|
||||
filenameTemplateSet = {
|
||||
downloadItem.customFileNameTemplate = it
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
downloadItem.audioPreferences.sponsorBlockFilters.clear()
|
||||
for (i in 0 until checkedItems.size) {
|
||||
if (checkedItems[i]) {
|
||||
downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
val cut = view.findViewById<Chip>(R.id.cut)
|
||||
if (downloadItem.duration.isNotEmpty()){
|
||||
cut.isEnabled = true
|
||||
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
|
||||
val cutVideoListener = object : VideoCutListener {
|
||||
override fun onChangeCut(list: List<String>) {
|
||||
if (list.isEmpty()){
|
||||
downloadItem.downloadSections = ""
|
||||
cut.text = getString(R.string.cut)
|
||||
|
||||
splitByChapters.isEnabled = true
|
||||
splitByChapters.isChecked = downloadItem.audioPreferences.splitByChapters
|
||||
}else{
|
||||
var value = ""
|
||||
list.forEach {
|
||||
value += "$it;"
|
||||
}
|
||||
downloadItem.downloadSections = value
|
||||
cut.text = value.dropLast(1)
|
||||
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
cut.setOnClickListener {
|
||||
},
|
||||
cutClicked = {cutVideoListener ->
|
||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
cut.isEnabled = false
|
||||
}
|
||||
|
||||
|
||||
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
||||
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||
extraCommands.visibility = View.VISIBLE
|
||||
extraCommands.setOnClickListener {
|
||||
},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
downloadItem.extraCommands = c
|
||||
|
|
@ -358,9 +249,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
}
|
||||
}else{
|
||||
extraCommands.visibility = View.GONE
|
||||
}
|
||||
)
|
||||
}catch (e : Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
|
|
@ -73,8 +74,9 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
if(resources.getBoolean(R.bool.isTablet)){
|
||||
if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = displayMetrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -151,43 +151,43 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace
|
||||
))
|
||||
|
||||
val newTemplate : Chip = view.findViewById(R.id.newTemplate)
|
||||
newTemplate.setOnClickListener {
|
||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||
templates.add(it)
|
||||
chosenCommandView.editText!!.setText(it.content)
|
||||
downloadItem.format = Format(
|
||||
it.title,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
it.content
|
||||
)
|
||||
populateCommandTemplates(templates, autoCompleteTextView)
|
||||
UiUtil.configureCommand(
|
||||
view,
|
||||
1,
|
||||
newTemplateClicked = {
|
||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||
templates.add(it)
|
||||
chosenCommandView.editText!!.setText(it.content)
|
||||
downloadItem.format = Format(
|
||||
it.title,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
it.content
|
||||
)
|
||||
populateCommandTemplates(templates, autoCompleteTextView)
|
||||
}
|
||||
},
|
||||
editSelectedClicked = {
|
||||
var current = templates.find { it.title == autoCompleteTextView.text.toString() }
|
||||
if (current == null) current = CommandTemplate(0, "", chosenCommandView.editText!!.text.toString(), false)
|
||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||
templates.add(it)
|
||||
chosenCommandView.editText!!.setText(it.content)
|
||||
downloadItem.format = Format(
|
||||
it.title,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
it.content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val editSelected : Chip = view.findViewById(R.id.editSelected)
|
||||
editSelected.setOnClickListener {
|
||||
var current = templates.find { it.title == autoCompleteTextView.text.toString() }
|
||||
if (current == null) current = CommandTemplate(0, "", chosenCommandView.editText!!.text.toString(), false)
|
||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
|
||||
templates.add(it)
|
||||
chosenCommandView.editText!!.setText(it.content)
|
||||
downloadItem.format = Format(
|
||||
it.title,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
it.content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.app.Dialog
|
|||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
|
|
@ -39,6 +40,7 @@ import androidx.work.OneTimeWorkRequestBuilder
|
|||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
|
|
@ -48,6 +50,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
|||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
|
|
@ -63,6 +66,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.reflect.Type
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -98,8 +102,9 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
if(resources.getBoolean(R.bool.isTablet)){
|
||||
if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = displayMetrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,8 +117,11 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
recyclerView.adapter = listAdapter
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
recyclerView.enableFastScroll()
|
||||
if(sharedPreferences.getBoolean("swipe_gestures", true)){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
listAdapter.submitList(items.toList())
|
||||
|
||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||
|
|
@ -227,7 +235,9 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
if (items[0].type == DownloadViewModel.Type.command){
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
}
|
||||
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when (m.itemId) {
|
||||
R.id.folder -> {
|
||||
|
|
@ -264,6 +274,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
listAdapter.notifyDataSetChanged()
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
|
||||
|
|
@ -273,6 +284,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
listAdapter.notifyDataSetChanged()
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +297,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
listAdapter.notifyDataSetChanged()
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -299,52 +312,6 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
)
|
||||
}
|
||||
|
||||
}
|
||||
R.id.filename_template -> {
|
||||
if (items.count { it.type == DownloadViewModel.Type.command } != 0){
|
||||
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint_2), Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.file_name_template))
|
||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = getString(R.string.file_name_template)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.ok)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
|
||||
lifecycleScope.launch {
|
||||
items.forEach {
|
||||
it.customFileNameTemplate = editText.text.toString()
|
||||
}
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
|
||||
val filenameTemplateDialog = builder.create()
|
||||
editText.doOnTextChanged { text, start, before, count ->
|
||||
filenameTemplateDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
}
|
||||
filenameTemplateDialog.show()
|
||||
val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
filenameTemplateDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
filenameTemplateDialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
R.id.format -> {
|
||||
if (! items.all { it.type == items[0].type }){
|
||||
|
|
@ -383,6 +350,136 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
}
|
||||
|
||||
}
|
||||
R.id.more -> {
|
||||
if (! items.all { it.type == items[0].type }) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getString(R.string.format_filtering_hint),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}else{
|
||||
val scale = resources.displayMetrics.density
|
||||
val padding = (40*scale*0.5f).toInt()
|
||||
|
||||
when(items[0].type){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.adjust_audio)
|
||||
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
UiUtil.configureAudio(
|
||||
sheetView,
|
||||
requireActivity(),
|
||||
items,
|
||||
embedThumbClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.embedThumb = enabled
|
||||
}
|
||||
},
|
||||
splitByChaptersClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.splitByChapters = enabled
|
||||
}
|
||||
},
|
||||
filenameTemplateSet = {template ->
|
||||
items.forEach {
|
||||
it.customFileNameTemplate = template
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
items.forEach { it.audioPreferences.sponsorBlockFilters.clear() }
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
items.forEach { it.audioPreferences.sponsorBlockFilters.add(values[i]) }
|
||||
}
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
items.forEach { it.extraCommands = c }
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
val bottomSheetDialog = AddExtraCommandsDialog(null, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
}
|
||||
)
|
||||
bottomSheet.show()
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.adjust_video)
|
||||
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
UiUtil.configureVideo(
|
||||
sheetView,
|
||||
requireActivity(),
|
||||
items,
|
||||
embedSubsClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.embedSubs = checked }
|
||||
},
|
||||
addChaptersClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.addChapters = checked }
|
||||
},
|
||||
splitByChaptersClicked = { checked ->
|
||||
items.forEach { it.videoPreferences.splitByChapters = checked }
|
||||
},
|
||||
saveThumbnailClicked = {checked ->
|
||||
items.forEach { it.SaveThumb = checked }
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
items.forEach { it.videoPreferences.sponsorBlockFilters.clear() }
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
items.forEach { it.videoPreferences.sponsorBlockFilters.add(values[i]) }
|
||||
}
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
filenameTemplateSet = { checked ->
|
||||
items.forEach { it.customFileNameTemplate = checked }
|
||||
},
|
||||
saveSubtitlesClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.writeSubs = checked }
|
||||
},
|
||||
subtitleLanguagesClicked = {
|
||||
UiUtil.showSubtitleLanguagesDialog(requireActivity(), ""){ value ->
|
||||
items.forEach { it.videoPreferences.subsLanguages = value }
|
||||
}
|
||||
},
|
||||
removeAudioClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.removeAudio = checked }
|
||||
},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
items.forEach { it.extraCommands = c }
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
val bottomSheetDialog = AddExtraCommandsDialog(null, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
}
|
||||
)
|
||||
|
||||
bottomSheet.show()
|
||||
}
|
||||
DownloadViewModel.Type.command -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -507,8 +604,12 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
items[i] = item
|
||||
if (! items.all { it.type == items[0].type }){
|
||||
bottomAppBar.menu[1].icon?.alpha = 30
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
}else{
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
if (items[0].type != DownloadViewModel.Type.command){
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
}
|
||||
}
|
||||
|
||||
if (items.count { it.type == DownloadViewModel.Type.command } == 0){
|
||||
|
|
@ -581,7 +682,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder!!,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
|
|
|
|||
|
|
@ -221,193 +221,51 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
}
|
||||
|
||||
|
||||
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
||||
embedSubs!!.isChecked = downloadItem.videoPreferences.embedSubs
|
||||
embedSubs.setOnClickListener {
|
||||
downloadItem.videoPreferences.embedSubs = embedSubs.isChecked
|
||||
}
|
||||
|
||||
val addChapters = view.findViewById<Chip>(R.id.add_chapters)
|
||||
addChapters!!.isChecked = downloadItem.videoPreferences.addChapters
|
||||
addChapters.setOnClickListener{
|
||||
downloadItem.videoPreferences.addChapters = addChapters.isChecked
|
||||
}
|
||||
|
||||
|
||||
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
|
||||
if(downloadItem.downloadSections.isNotBlank()){
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
}else{
|
||||
splitByChapters!!.isChecked = downloadItem.audioPreferences.splitByChapters
|
||||
}
|
||||
splitByChapters.setOnClickListener {
|
||||
if (splitByChapters.isChecked){
|
||||
addChapters.isEnabled = false
|
||||
addChapters.isChecked = false
|
||||
downloadItem.videoPreferences.addChapters = false
|
||||
}else{
|
||||
addChapters.isEnabled = true
|
||||
}
|
||||
downloadItem.videoPreferences.splitByChapters = splitByChapters.isChecked
|
||||
}
|
||||
|
||||
val saveThumbnail = view.findViewById<Chip>(R.id.save_thumbnail)
|
||||
saveThumbnail!!.isChecked = downloadItem.SaveThumb
|
||||
saveThumbnail.setOnClickListener {
|
||||
downloadItem.SaveThumb = saveThumbnail.isChecked
|
||||
}
|
||||
|
||||
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
|
||||
sponsorBlock!!.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.select_sponsorblock_filtering))
|
||||
val values = resources.getStringArray(R.array.sponsorblock_settings_values)
|
||||
val entries = resources.getStringArray(R.array.sponsorblock_settings_entries)
|
||||
val checkedItems : ArrayList<Boolean> = arrayListOf()
|
||||
values.forEach {
|
||||
if (downloadItem.videoPreferences.sponsorBlockFilters.contains(it)) {
|
||||
checkedItems.add(true)
|
||||
}else{
|
||||
checkedItems.add(false)
|
||||
}
|
||||
}
|
||||
|
||||
builder.setMultiChoiceItems(
|
||||
entries,
|
||||
checkedItems.toBooleanArray()
|
||||
) { _, which, isChecked ->
|
||||
checkedItems[which] = isChecked
|
||||
}
|
||||
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
UiUtil.configureVideo(
|
||||
view,
|
||||
requireActivity(),
|
||||
listOf(downloadItem),
|
||||
embedSubsClicked = {
|
||||
downloadItem.videoPreferences.embedSubs = it
|
||||
},
|
||||
addChaptersClicked = {
|
||||
downloadItem.videoPreferences.addChapters = it
|
||||
},
|
||||
splitByChaptersClicked = {
|
||||
downloadItem.videoPreferences.splitByChapters = it
|
||||
},
|
||||
saveThumbnailClicked = {
|
||||
downloadItem.SaveThumb = it
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
downloadItem.videoPreferences.sponsorBlockFilters.clear()
|
||||
for (i in 0 until checkedItems.size) {
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
downloadItem.videoPreferences.sponsorBlockFilters.add(values[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
val cut = view.findViewById<Chip>(R.id.cut)
|
||||
if(downloadItem.duration.isNotEmpty()){
|
||||
cut.isEnabled = true
|
||||
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
|
||||
val cutVideoListener = object : VideoCutListener {
|
||||
|
||||
override fun onChangeCut(list: List<String>) {
|
||||
if (list.isEmpty()){
|
||||
downloadItem.downloadSections = ""
|
||||
cut.text = getString(R.string.cut)
|
||||
|
||||
splitByChapters.isEnabled = true
|
||||
splitByChapters.isChecked = downloadItem.videoPreferences.splitByChapters
|
||||
if (splitByChapters.isChecked){
|
||||
addChapters.isEnabled = false
|
||||
addChapters.isChecked = false
|
||||
}else{
|
||||
addChapters.isEnabled = true
|
||||
}
|
||||
}else{
|
||||
var value = ""
|
||||
list.forEach {
|
||||
value += "$it;"
|
||||
}
|
||||
downloadItem.downloadSections = value
|
||||
cut.text = value.dropLast(1)
|
||||
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
addChapters.isEnabled = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
cut.setOnClickListener {
|
||||
},
|
||||
cutClicked = { cutVideoListener ->
|
||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||
}
|
||||
}
|
||||
}else{
|
||||
cut.isEnabled = false
|
||||
}
|
||||
|
||||
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
||||
filenameTemplate.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.file_name_template))
|
||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = getString(R.string.file_name_template)
|
||||
editText.setText(downloadItem.customFileNameTemplate)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
downloadItem.customFileNameTemplate = editText.text.toString()
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
editText.doOnTextChanged { _, _, _, _ ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
}
|
||||
dialog.show()
|
||||
val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
|
||||
val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages)
|
||||
downloadItem.videoPreferences.subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
|
||||
if (downloadItem.videoPreferences.writeSubs) {
|
||||
saveSubtitles.isChecked = true
|
||||
subtitleLanguages.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
saveSubtitles.setOnCheckedChangeListener { _, _ ->
|
||||
if (saveSubtitles.isChecked) subtitleLanguages.visibility = View.VISIBLE
|
||||
else subtitleLanguages.visibility = View.GONE
|
||||
downloadItem.videoPreferences.writeSubs = saveSubtitles.isChecked
|
||||
}
|
||||
|
||||
subtitleLanguages.setOnClickListener {
|
||||
UiUtil.showSubtitleLanguagesDialog(requireActivity(), downloadItem.videoPreferences.subsLanguages){
|
||||
downloadItem.videoPreferences.subsLanguages = it
|
||||
}
|
||||
}
|
||||
|
||||
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
|
||||
removeAudio.setOnCheckedChangeListener { _, _ ->
|
||||
downloadItem.videoPreferences.removeAudio = removeAudio.isChecked
|
||||
}
|
||||
|
||||
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
||||
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||
extraCommands.visibility = View.VISIBLE
|
||||
extraCommands.setOnClickListener {
|
||||
},
|
||||
filenameTemplateSet = {
|
||||
downloadItem.customFileNameTemplate = it
|
||||
},
|
||||
saveSubtitlesClicked = {
|
||||
downloadItem.videoPreferences.writeSubs = it
|
||||
},
|
||||
subtitleLanguagesClicked = {
|
||||
UiUtil.showSubtitleLanguagesDialog(requireActivity(), downloadItem.videoPreferences.subsLanguages){
|
||||
downloadItem.videoPreferences.subsLanguages = it
|
||||
}
|
||||
},
|
||||
removeAudioClicked = {
|
||||
downloadItem.videoPreferences.removeAudio = it
|
||||
},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
downloadItem.extraCommands = c
|
||||
|
|
@ -417,9 +275,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
}
|
||||
}else{
|
||||
extraCommands.visibility = View.GONE
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
private lateinit var view: View
|
||||
|
||||
enum class FormatSorting {
|
||||
filesize, container, id
|
||||
filesize, container, codec, id
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -127,7 +127,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
refreshBtn.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
if (items.size > 10){
|
||||
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_INDEFINITE)
|
||||
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
|
||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||
listener.onContinueOnBackground(items)
|
||||
this@FormatSelectionBottomSheetDialog.dismiss()
|
||||
|
|
@ -158,7 +158,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
|
||||
formats = listOf(res)
|
||||
|
||||
//playlist format filtering
|
||||
//list format filtering
|
||||
}else{
|
||||
var progress = "0/${items.size}"
|
||||
formatCollection.clear()
|
||||
|
|
@ -168,8 +168,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
lifecycleScope.launch(Dispatchers.Main){
|
||||
progress = "${formatCollection.size}/${items.size}"
|
||||
refreshBtn.text = progress
|
||||
formatCollection.add(it)
|
||||
}
|
||||
formatCollection.add(it)
|
||||
}
|
||||
}
|
||||
formats = formatCollection
|
||||
|
|
@ -242,6 +242,14 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
val finalFormats: List<Format> = when(sortBy){
|
||||
FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value }
|
||||
FormatSorting.id -> chosenFormats.sortedBy { it.format_id }
|
||||
FormatSorting.codec -> {
|
||||
val codecOrder = resources.getStringArray(R.array.video_codec_values).toMutableList()
|
||||
codecOrder.removeFirst()
|
||||
chosenFormats.groupBy { format -> codecOrder.indexOfFirst { format.vcodec.startsWith(it) } }
|
||||
.flatMap {
|
||||
it.value.sortedBy { l -> l.filesize }
|
||||
}
|
||||
}
|
||||
FormatSorting.filesize -> chosenFormats
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.app.DownloadManager
|
|||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
|
|
@ -28,6 +29,7 @@ import androidx.media3.exoplayer.source.MediaSource
|
|||
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||
import androidx.media3.ui.PlayerView
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.paging.filter
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
|
|
@ -37,7 +39,6 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter
|
||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
|
|
@ -47,6 +48,7 @@ import com.deniscerri.ytdlnis.util.InfoUtil
|
|||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
|
@ -58,10 +60,11 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.FieldPosition
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -73,15 +76,14 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
|
||||
private lateinit var activeDownloads: ActiveDownloadMinifiedAdapter
|
||||
private lateinit var queuedDownloads: GenericDownloadAdapter
|
||||
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
|
||||
private lateinit var queuedAdapter: GenericDownloadAdapter
|
||||
private var activeCount: Int = 0
|
||||
|
||||
private lateinit var queuedItems : List<DownloadItem>
|
||||
private lateinit var activeItems : List<DownloadItem>
|
||||
private lateinit var downloadManager: DownloadManager
|
||||
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private lateinit var dialogView : View
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -89,8 +91,6 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
notificationUtil = NotificationUtil(requireActivity().applicationContext)
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
queuedItems = listOf()
|
||||
activeItems = listOf()
|
||||
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
}
|
||||
|
|
@ -101,7 +101,8 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
// Inflate the layout to use as dialog or embedded fragment
|
||||
return inflater.inflate(R.layout.result_card_details, container, false)
|
||||
dialogView = inflater.inflate(R.layout.result_card_details, container, false)
|
||||
return dialogView
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
|
@ -116,14 +117,27 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.result_card_details, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val behavior = BottomSheetBehavior.from(dialogView.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = displayMetrics.heightPixels
|
||||
}
|
||||
if (!resources.getBoolean(R.bool.isTablet) && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
|
||||
behavior.maxWidth = displayMetrics.widthPixels
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
activeDownloads = ActiveDownloadMinifiedAdapter(this,requireActivity())
|
||||
queuedDownloads = GenericDownloadAdapter(this,requireActivity())
|
||||
activeAdapter = ActiveDownloadMinifiedAdapter(this,requireActivity())
|
||||
queuedAdapter = GenericDownloadAdapter(this,requireActivity())
|
||||
|
||||
val bottomSheetLink = view.findViewById<MaterialButton>(R.id.bottom_sheet_link)
|
||||
val downloadThumb = view.findViewById<MaterialButton>(R.id.download_thumb)
|
||||
|
|
@ -137,8 +151,8 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
val queuedRecycler = view.findViewById<RecyclerView>(R.id.queued_recycler)
|
||||
val queued = view.findViewById<TextView>(R.id.queued)
|
||||
|
||||
runningRecycler.adapter = activeDownloads
|
||||
runningRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
runningRecycler.adapter = activeAdapter
|
||||
runningRecycler.layoutManager = GridLayoutManager(context, 1)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getBoolean("swipe_gestures", true)){
|
||||
|
|
@ -146,56 +160,57 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
itemTouchHelper.attachToRecyclerView(queuedRecycler)
|
||||
}
|
||||
|
||||
queuedRecycler.adapter = queuedDownloads
|
||||
queuedRecycler.adapter = queuedAdapter
|
||||
queuedRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
downloadViewModel.activeDownloads.map { it.filter { d -> d.url == item.url } }.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty()) {
|
||||
activeAdapter.submitList(it)
|
||||
if (it.isEmpty()){
|
||||
running.visibility = View.GONE
|
||||
runningRecycler.visibility = View.GONE
|
||||
}else{
|
||||
running.visibility = View.VISIBLE
|
||||
runningRecycler.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
it.forEach{item ->
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosForUniqueWorkLiveData(item.id.toString())
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@observe
|
||||
val id = work.progress.getLong("id", 0L)
|
||||
if(id == 0L) return@observe
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData("download")
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@forEach
|
||||
val id = work.progress.getLong("id", 0L)
|
||||
if(id == 0L) return@forEach
|
||||
|
||||
val progress = work.progress.getInt("progress", 0)
|
||||
|
||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
try {
|
||||
progressBar?.setProgressCompat(progress, true)
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
val progress = work.progress.getInt("progress", 0)
|
||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||
requireActivity().runOnUiThread {
|
||||
try {
|
||||
progressBar?.setProgressCompat(progress, true)
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeItems = it
|
||||
activeDownloads.submitList(it)
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.queuedDownloads.map { it.filter { d -> d.url == item.url } }.collectLatest {
|
||||
queuedAdapter.submitData(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
downloadViewModel.queuedDownloads.map { it.filter { d -> d.url == item.url } }.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty()) {
|
||||
queued.visibility = View.GONE
|
||||
queuedRecycler.visibility = View.GONE
|
||||
}else{
|
||||
queued.visibility = View.VISIBLE
|
||||
queuedRecycler.visibility = View.VISIBLE
|
||||
queuedAdapter.addLoadStateListener { loadState ->
|
||||
lifecycleScope.launch {
|
||||
if (loadState.append.endOfPaginationReached )
|
||||
{
|
||||
if (queuedAdapter.itemCount < 1){
|
||||
queued.visibility = View.GONE
|
||||
queuedRecycler.visibility = View.GONE
|
||||
}else{
|
||||
queued.visibility = View.VISIBLE
|
||||
queuedRecycler.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queuedItems = it
|
||||
queuedDownloads.submitList(it)
|
||||
}
|
||||
|
||||
downloadViewModel.activeDownloadsCount.observe(viewLifecycleOwner){
|
||||
|
|
@ -238,23 +253,9 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
|
||||
|
||||
videoView = view.findViewById(R.id.video_view)
|
||||
val pause = view.findViewById<MaterialButton>(R.id.pause)
|
||||
|
||||
val player = VideoPlayerUtil.buildPlayer(requireContext())
|
||||
videoView.player = player
|
||||
|
||||
videoView.setOnClickListener {
|
||||
if (player.isPlaying){
|
||||
player.pause()
|
||||
pause.visibility = View.VISIBLE
|
||||
}
|
||||
else {
|
||||
player.play()
|
||||
pause.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val data : MutableList<String?> = withContext(Dispatchers.IO){
|
||||
|
|
@ -324,25 +325,29 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
}
|
||||
|
||||
private fun removeQueuedItem(id: Long){
|
||||
val item = queuedItems.find { it.id == id } ?: return
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(id)
|
||||
}
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
|
||||
Snackbar.make(requireView().rootView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.deleteDownload(item)
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}.show()
|
||||
Snackbar.make(requireView().rootView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
|
|
@ -353,12 +358,16 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = queuedItems[position]
|
||||
queuedDownloads.notifyItemChanged(position)
|
||||
removeQueuedItem(deletedItem.id)
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
queuedAdapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
|
||||
removeQueuedItem(deletedItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -396,7 +405,7 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder!!,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
|
|
@ -410,133 +419,135 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
}
|
||||
|
||||
override fun onCardClick(itemID: Long) {
|
||||
val item = queuedItems.find { it.id == itemID } ?: return
|
||||
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
||||
when (item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
||||
when (item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||
}
|
||||
else -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||
|
||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) time!!.visibility = View.GONE
|
||||
else {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = item.downloadStartTime
|
||||
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
||||
|
||||
time.setOnClickListener {
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
bottomSheet.dismiss()
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
item.downloadStartTime = it.timeInMillis
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note
|
||||
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) time!!.visibility = View.GONE
|
||||
else {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = item.downloadStartTime
|
||||
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||
else container!!.visibility = View.GONE
|
||||
|
||||
time.setOnClickListener {
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec!!.visibility = View.GONE
|
||||
}else{
|
||||
codec!!.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||
else fileSize!!.text = fileSizeReadable
|
||||
|
||||
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val url = item.url
|
||||
link!!.text = url
|
||||
link.tag = itemID
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||
true
|
||||
}
|
||||
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||
remove!!.tag = itemID
|
||||
remove.setOnClickListener{
|
||||
bottomSheet.hide()
|
||||
removeQueuedItem(itemID)
|
||||
}
|
||||
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||
openFile!!.visibility = View.GONE
|
||||
|
||||
|
||||
|
||||
val downloadNow = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) downloadNow!!.visibility = View.GONE
|
||||
else{
|
||||
downloadNow!!.text = getString(R.string.download_now)
|
||||
downloadNow.setOnClickListener {
|
||||
bottomSheet.dismiss()
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(item)
|
||||
item.downloadStartTime = it.timeInMillis
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
item.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note
|
||||
|
||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||
else container!!.visibility = View.GONE
|
||||
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec!!.visibility = View.GONE
|
||||
}else{
|
||||
codec!!.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||
else fileSize!!.text = fileSizeReadable
|
||||
|
||||
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val url = item.url
|
||||
link!!.text = url
|
||||
link.tag = itemID
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||
true
|
||||
}
|
||||
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||
remove!!.tag = itemID
|
||||
remove.setOnClickListener{
|
||||
bottomSheet.hide()
|
||||
removeQueuedItem(itemID)
|
||||
}
|
||||
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||
openFile!!.visibility = View.GONE
|
||||
|
||||
|
||||
|
||||
val downloadNow = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) downloadNow!!.visibility = View.GONE
|
||||
else{
|
||||
downloadNow!!.text = getString(R.string.download_now)
|
||||
downloadNow.setOnClickListener {
|
||||
bottomSheet.dismiss()
|
||||
downloadViewModel.deleteDownload(item)
|
||||
item.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
|
||||
}
|
||||
override fun onCardSelect(isChecked: Boolean, position: Int) {}
|
||||
|
||||
override fun onCancelClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
|
|
@ -557,41 +568,45 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
cancelActiveDownload(itemID)
|
||||
}
|
||||
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
|
||||
val item = activeItems.find { it.id == itemID } ?: return
|
||||
when(action){
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
item.status = DownloadRepository.Status.Paused.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeDownloads.notifyItemChanged(position)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
||||
lifecycleScope.launch {
|
||||
item.status = DownloadRepository.Status.Active.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
when(action){
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
item.status = DownloadRepository.Status.Paused.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeAdapter.notifyItemChanged(position)
|
||||
}
|
||||
activeDownloads.notifyItemChanged(position)
|
||||
}
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
||||
lifecycleScope.launch {
|
||||
item.status = DownloadRepository.Status.Active.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeAdapter.notifyItemChanged(position)
|
||||
|
||||
val queue = if (activeCount > 1) listOf(item)
|
||||
else withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
list.add(0, item)
|
||||
list
|
||||
}
|
||||
val queue = if (activeCount > 1) listOf(item)
|
||||
else withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
list.add(0, item)
|
||||
list
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
override fun onCardClick() {
|
||||
this.dismiss()
|
||||
|
|
@ -607,15 +622,19 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
}
|
||||
|
||||
private fun cancelActiveDownload(itemID: Long){
|
||||
val id = itemID.toInt()
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
lifecycleScope.launch {
|
||||
val id = itemID.toInt()
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
|
||||
activeItems.find { it.id == itemID }?.let {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(it)
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}.let {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActionBar.LayoutParams
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
|
|
@ -8,9 +9,12 @@ import android.util.DisplayMetrics
|
|||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
|
@ -22,44 +26,56 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
|
|||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>, private val type: DownloadViewModel.Type) : BottomSheetDialogFragment(), PlaylistAdapter.OnItemClickListener {
|
||||
class SelectPlaylistItemsDialog(private val items: List<ResultItem?>, private val type: DownloadViewModel.Type) : DialogFragment(), PlaylistAdapter.OnItemClickListener {
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var listAdapter : PlaylistAdapter
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var selectedText: TextView
|
||||
private lateinit var ok: MenuItem
|
||||
private lateinit var toolbar: MaterialToolbar
|
||||
private lateinit var fromTextInput: TextInputLayout
|
||||
private lateinit var toTextInput: TextInputLayout
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun setupDialog(dialog: Dialog, style: Int) {
|
||||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.select_playlist_items, null)
|
||||
dialog.setContentView(view)
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.select_playlist_items, container, false)
|
||||
}
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
return dialog
|
||||
}
|
||||
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
dialog?.window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
|
||||
toolbar = view.findViewById<MaterialToolbar>(R.id.toolbar)
|
||||
|
||||
listAdapter =
|
||||
PlaylistAdapter(
|
||||
|
|
@ -70,10 +86,10 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
|||
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
recyclerView.adapter = listAdapter
|
||||
recyclerView.enableFastScroll()
|
||||
listAdapter.submitList(items)
|
||||
|
||||
selectedText = view.findViewById<Button>(R.id.selected)
|
||||
selectedText.text = "0 ${resources.getString(R.string.selected)}"
|
||||
toolbar.title = "0 ${resources.getString(R.string.selected)}"
|
||||
|
||||
fromTextInput = view.findViewById(R.id.from_textinput)
|
||||
toTextInput = view.findViewById(R.id.to_textinput)
|
||||
|
|
@ -93,14 +109,14 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
|||
if (startNr <= 0) startNr = 0
|
||||
if (endNr > items.size) endNr = items.size - 1
|
||||
listAdapter.checkRange(startNr, endNr)
|
||||
selectedText.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
ok.isEnabled = true
|
||||
toolbar.title = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toTextInput.editText!!.doAfterTextChanged { _text ->
|
||||
reset()
|
||||
|
||||
val start = fromTextInput.editText!!.text.toString()
|
||||
val end = _text.toString()
|
||||
|
||||
|
|
@ -113,7 +129,8 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
|||
if (startNr <= 0) startNr = 0
|
||||
if (endNr > items.size) endNr = items.size -1
|
||||
listAdapter.checkRange(startNr, endNr)
|
||||
selectedText.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
ok.isEnabled = true
|
||||
toolbar.title = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,19 +143,22 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
|||
listAdapter.checkAll()
|
||||
fromTextInput.isEnabled = true
|
||||
toTextInput.isEnabled = true
|
||||
selectedText.text = resources.getString(R.string.all_items_selected)
|
||||
ok.isEnabled = true
|
||||
toolbar.title = resources.getString(R.string.all_items_selected)
|
||||
}else{
|
||||
reset()
|
||||
fromTextInput.isEnabled = true
|
||||
toTextInput.isEnabled = true
|
||||
ok.isEnabled = false
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth("")
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth("")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val ok = view.findViewById<Button>(R.id.bottomsheet_ok)
|
||||
ok!!.setOnClickListener {
|
||||
ok = toolbar.menu.getItem(0)
|
||||
ok.isEnabled = false
|
||||
ok.setOnMenuItemClickListener {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val checkedItems = listAdapter.getCheckedItems()
|
||||
val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) }
|
||||
|
|
@ -164,6 +184,7 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
|||
dismiss()
|
||||
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
view.findViewById<BottomAppBar>(R.id.bottomAppBar).setOnMenuItemClickListener { m: MenuItem ->
|
||||
|
|
@ -172,17 +193,22 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
|||
listAdapter.invertSelected(items)
|
||||
val checkedItems = listAdapter.getCheckedItems()
|
||||
if (checkedItems.size == items.size){
|
||||
selectedText.text = resources.getString(R.string.all_items_selected)
|
||||
toolbar.title= resources.getString(R.string.all_items_selected)
|
||||
}else{
|
||||
selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
toolbar.title = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
if(checkedItems.isNotEmpty() && checkedItems.size < items.size){
|
||||
fromTextInput.isEnabled = false
|
||||
toTextInput.isEnabled = false
|
||||
}
|
||||
ok.isEnabled = checkedItems.isNotEmpty()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
requireActivity().finishAffinity()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
|
|
@ -209,21 +235,24 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
|
|||
}
|
||||
|
||||
private fun reset(){
|
||||
ok.isEnabled = false
|
||||
listAdapter.clearCheckeditems()
|
||||
selectedText.text = "0 ${resources.getString(R.string.selected)}"
|
||||
toolbar.title = "0 ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
|
||||
|
||||
override fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>) {
|
||||
if (checkedItems.size == items.size){
|
||||
selectedText.text = resources.getString(R.string.all_items_selected)
|
||||
toolbar.title = resources.getString(R.string.all_items_selected)
|
||||
}else{
|
||||
selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
toolbar.title = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
if (checkedItems.isEmpty()){
|
||||
ok.isEnabled = false
|
||||
fromTextInput.isEnabled = true
|
||||
toTextInput.isEnabled = true
|
||||
}else{
|
||||
ok.isEnabled = true
|
||||
fromTextInput.isEnabled = false
|
||||
toTextInput.isEnabled = false
|
||||
}
|
||||
|
|
@ -10,9 +10,11 @@ import android.widget.TextView
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.map
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
|
||||
|
|
@ -39,7 +41,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
private lateinit var activeDownloads : ActiveDownloadAdapter
|
||||
lateinit var downloadItem: DownloadItem
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var list: List<DownloadItem>
|
||||
private lateinit var pauseResume: MaterialButton
|
||||
private lateinit var workManager: WorkManager
|
||||
|
||||
|
|
@ -53,7 +54,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
activity = getActivity()
|
||||
notificationUtil = NotificationUtil(requireContext())
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
list = listOf()
|
||||
workManager = WorkManager.getInstance(requireContext())
|
||||
return fragmentView
|
||||
}
|
||||
|
|
@ -77,6 +77,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
if (pauseResume.text == requireContext().getString(R.string.pause)){
|
||||
|
||||
lifecycleScope.launch {
|
||||
pauseResume.isEnabled = false
|
||||
val queued = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
|
|
@ -92,18 +93,28 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
workManager.cancelAllWorkByTag(it.id.toString())
|
||||
}
|
||||
|
||||
list.forEach {
|
||||
val active = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}
|
||||
|
||||
active.forEach {
|
||||
cancelItem(it.id.toInt())
|
||||
it.status = DownloadRepository.Status.Paused.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
|
||||
pauseResume.isEnabled = true
|
||||
}
|
||||
}else{
|
||||
lifecycleScope.launch {
|
||||
val toQueue = list.filter { it.status == DownloadRepository.Status.Paused.toString() }.toMutableList()
|
||||
pauseResume.isEnabled = false
|
||||
|
||||
val active = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}
|
||||
|
||||
val toQueue = active.filter { it.status == DownloadRepository.Status.Paused.toString() }.toMutableList()
|
||||
toQueue.forEach {
|
||||
it.status = DownloadRepository.Status.Active.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
|
|
@ -116,17 +127,40 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
runBlocking {
|
||||
downloadViewModel.queueDownloads(toQueue)
|
||||
}
|
||||
pauseResume.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData("download")
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@forEach
|
||||
val id = work.progress.getLong("id", 0L)
|
||||
if(id == 0L) return@forEach
|
||||
|
||||
val progress = work.progress.getInt("progress", 0)
|
||||
val output = work.progress.getString("output")
|
||||
|
||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||
val outputText = view.findViewWithTag<TextView>("$id##output")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
try {
|
||||
progressBar?.setProgressCompat(progress, true)
|
||||
outputText?.text = output
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadViewModel.activeDownloads.observe(viewLifecycleOwner) {
|
||||
list = it
|
||||
activeDownloads.submitList(it)
|
||||
|
||||
if (it.size > 1){
|
||||
pauseResume.visibility = View.VISIBLE
|
||||
if (list.all { l -> l.status == DownloadRepository.Status.Paused.toString() }){
|
||||
if (it.all { l -> l.status == DownloadRepository.Status.Paused.toString() }){
|
||||
pauseResume.text = requireContext().getString(R.string.resume)
|
||||
}else{
|
||||
pauseResume.text = requireContext().getString(R.string.pause)
|
||||
|
|
@ -135,37 +169,16 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
activeDownloads.notifyDataSetChanged()
|
||||
pauseResume.visibility = View.GONE
|
||||
}
|
||||
|
||||
it.forEach{item ->
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosForUniqueWorkLiveData(item.id.toString())
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@observe
|
||||
val id = work.progress.getLong("id", 0L)
|
||||
if(id == 0L) return@observe
|
||||
|
||||
val progress = work.progress.getInt("progress", 0)
|
||||
val output = work.progress.getString("output")
|
||||
|
||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||
val outputText = view.findViewWithTag<TextView>("$id##output")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
try {
|
||||
progressBar?.setProgressCompat(progress, true)
|
||||
outputText?.text = output
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
if (list.size == 1){
|
||||
val count = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}.count()
|
||||
|
||||
if (count == 1){
|
||||
val queue = withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
|
|
@ -183,41 +196,50 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
|
||||
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
|
||||
val item = list.find { it.id == itemID } ?: return
|
||||
when(action){
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
item.status = DownloadRepository.Status.Paused.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeDownloads.notifyItemChanged(position)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
||||
lifecycleScope.launch {
|
||||
item.status = DownloadRepository.Status.Active.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeDownloads.notifyItemChanged(position)
|
||||
|
||||
val queue = if (list.size > 1) listOf(item)
|
||||
else withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
list.add(0, item)
|
||||
list
|
||||
when(action){
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
item.status = DownloadRepository.Status.Paused.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeDownloads.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
||||
lifecycleScope.launch {
|
||||
item.status = DownloadRepository.Status.Active.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeDownloads.notifyItemChanged(position)
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
val count = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}.count()
|
||||
|
||||
val queue = if (count > 1) listOf(item)
|
||||
else withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
list.add(0, item)
|
||||
list
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOutputClick(item: DownloadItem) {
|
||||
|
|
@ -238,17 +260,19 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
|
||||
private fun cancelDownload(itemID: Long){
|
||||
cancelItem(itemID.toInt())
|
||||
list.find { it.id == itemID }?.let {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(it)
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}.let {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onClick(p0: View?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,21 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloads
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
|
|
@ -30,20 +23,22 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||
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.databinding.FragmentHomeBinding
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||
|
|
@ -51,10 +46,9 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
private lateinit var cancelledRecyclerView : RecyclerView
|
||||
private lateinit var cancelledDownloads : GenericDownloadAdapter
|
||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
||||
private lateinit var adapter : GenericDownloadAdapter
|
||||
private var actionMode : ActionMode? = null
|
||||
private lateinit var items : MutableList<DownloadItem>
|
||||
private var totalSize = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -63,16 +57,14 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
): View? {
|
||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||
activity = getActivity()
|
||||
selectedObjects = arrayListOf()
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
items = mutableListOf()
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
cancelledDownloads =
|
||||
adapter =
|
||||
GenericDownloadAdapter(
|
||||
this,
|
||||
requireActivity()
|
||||
|
|
@ -80,7 +72,8 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
|
||||
cancelledRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
cancelledRecyclerView.forceFastScrollMode()
|
||||
cancelledRecyclerView.adapter = cancelledDownloads
|
||||
cancelledRecyclerView.adapter = adapter
|
||||
cancelledRecyclerView.enableFastScroll()
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getBoolean("swipe_gestures", true)){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
|
|
@ -89,146 +82,74 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
cancelledRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
|
||||
downloadViewModel.cancelledDownloads.observe(viewLifecycleOwner) {
|
||||
items = it.toMutableList()
|
||||
cancelledDownloads.submitList(it)
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.cancelledDownloads.collectLatest {
|
||||
adapter.submitData(it)
|
||||
}
|
||||
}
|
||||
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Cancelled)).observe(viewLifecycleOwner){
|
||||
totalSize = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActionButtonClick(itemID: Long) {
|
||||
val item = items.find { it.id == itemID }
|
||||
if (item != null){
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
lifecycleScope.launch {
|
||||
runCatching {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}.onFailure {
|
||||
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}else{
|
||||
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCardClick(itemID: Long) {
|
||||
val item = items.find { it.id == itemID } ?: return
|
||||
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
||||
when (item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||
}
|
||||
else -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
removeItem(it, sheet)
|
||||
},
|
||||
downloadItem = {
|
||||
runBlocking{
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||
|
||||
time!!.visibility = View.GONE
|
||||
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note
|
||||
|
||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||
else container!!.visibility = View.GONE
|
||||
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec!!.visibility = View.GONE
|
||||
}else{
|
||||
codec!!.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||
else fileSize!!.text = fileSizeReadable
|
||||
|
||||
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val url = item.url
|
||||
link!!.text = url
|
||||
link.tag = itemID
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||
true
|
||||
}
|
||||
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||
remove!!.tag = itemID
|
||||
remove.setOnClickListener{
|
||||
removeItem(item, bottomSheet)
|
||||
}
|
||||
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||
openFile!!.visibility = View.GONE
|
||||
|
||||
val redownload = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||
redownload!!.tag = itemID
|
||||
redownload.setOnClickListener{
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
|
||||
redownload.setOnLongClickListener {
|
||||
bottomSheet.cancel()
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(item), item.type, item, false)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
true
|
||||
}
|
||||
|
||||
openFile.visibility = View.GONE
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = items.find { it.id == itemID }
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
|
||||
}else{
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
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 {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
actionMode?.finish()
|
||||
else {
|
||||
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0){
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -240,7 +161,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
downloadViewModel.deleteDownload(item)
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
|
|
@ -249,7 +170,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.cancelled_downloads_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${adapter.getSelectedObjectsCount(totalSize)} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -270,39 +191,56 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
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 ->
|
||||
for (obj in selectedObjects!!){
|
||||
downloadViewModel.deleteDownload(obj)
|
||||
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()
|
||||
}
|
||||
|
||||
adapter.clearCheckedItems()
|
||||
for (id in selectedObjects){
|
||||
downloadViewModel.deleteDownload(id)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
clearCheckedItems()
|
||||
actionMode?.finish()
|
||||
}
|
||||
deleteDialog.show()
|
||||
true
|
||||
}
|
||||
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()
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.reQueueDownloadItems(selectedObjects)
|
||||
}
|
||||
adapter.clearCheckedItems()
|
||||
}
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(selectedObjects!!.toMutableList())
|
||||
actionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
cancelledDownloads.checkAll(items)
|
||||
selectedObjects?.clear()
|
||||
items.forEach { selectedObjects?.add(it) }
|
||||
adapter.checkAll()
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
cancelledDownloads.invertSelected(items)
|
||||
val invertedList = arrayListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
adapter.invertSelected()
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
|
@ -311,15 +249,10 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
actionMode = null
|
||||
clearCheckedItems()
|
||||
adapter.clearCheckedItems()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearCheckedItems(){
|
||||
cancelledDownloads.clearCheckeditems()
|
||||
selectedObjects?.clear()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||
|
|
@ -328,18 +261,22 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = items[position]
|
||||
downloadViewModel.deleteDownload(deletedItem)
|
||||
Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
downloadViewModel.deleteDownload(deletedItem.id)
|
||||
Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
ItemTouchHelper.RIGHT -> {
|
||||
onActionButtonClick(items[position].id)
|
||||
onActionButtonClick(itemID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,10 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
initMenu()
|
||||
|
||||
if (arguments?.getString("tab") != null){
|
||||
tabLayout.selectTab(tabLayout.getTabAt(3))
|
||||
tabLayout.getTabAt(3)!!.select()
|
||||
viewPager2.postDelayed( {
|
||||
viewPager2.setCurrentItem(3, false)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,22 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloads
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.AdapterView
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
|
|
@ -31,19 +25,22 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||
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.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener, OnItemClickListener {
|
||||
|
|
@ -51,10 +48,9 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
private lateinit var erroredRecyclerView : RecyclerView
|
||||
private lateinit var erroredDownloads : GenericDownloadAdapter
|
||||
private lateinit var items : MutableList<DownloadItem>
|
||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
||||
private lateinit var adapter : GenericDownloadAdapter
|
||||
private var actionMode : ActionMode? = null
|
||||
private var totalSize: Int = 0
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
@ -63,15 +59,13 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||
activity = getActivity()
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
items = mutableListOf()
|
||||
selectedObjects = arrayListOf()
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
erroredDownloads =
|
||||
adapter =
|
||||
GenericDownloadAdapter(
|
||||
this,
|
||||
requireActivity()
|
||||
|
|
@ -79,7 +73,8 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
|
||||
erroredRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
erroredRecyclerView.forceFastScrollMode()
|
||||
erroredRecyclerView.adapter = erroredDownloads
|
||||
erroredRecyclerView.adapter = adapter
|
||||
erroredRecyclerView.enableFastScroll()
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getBoolean("swipe_gestures", true)){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
|
|
@ -87,147 +82,77 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
}
|
||||
erroredRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
downloadViewModel.erroredDownloads.observe(viewLifecycleOwner) {
|
||||
items = it.toMutableList()
|
||||
erroredDownloads.submitList(it)
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.erroredDownloads.collectLatest {
|
||||
adapter.submitData(it)
|
||||
}
|
||||
}
|
||||
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Error)).observe(viewLifecycleOwner){
|
||||
totalSize = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActionButtonClick(itemID: Long) {
|
||||
val item = items.find { it.id == itemID } ?: return
|
||||
if (item.logID != null) {
|
||||
val bundle = Bundle()
|
||||
bundle.putLong("logID", item.logID!!)
|
||||
findNavController().navigate(
|
||||
R.id.downloadLogFragment,
|
||||
bundle
|
||||
)
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
if (item.logID != null) {
|
||||
val bundle = Bundle()
|
||||
bundle.putLong("logID", item.logID!!)
|
||||
findNavController().navigate(
|
||||
R.id.downloadLogFragment,
|
||||
bundle
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCardClick(itemID: Long) {
|
||||
val item = items.find { it.id == itemID } ?: return
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
removeItem(it, sheet)
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
||||
when (item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||
}
|
||||
else -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||
}
|
||||
}
|
||||
|
||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||
val formatNote = bottomSheet.findViewById<TextView>(R.id.format_note)
|
||||
val container = bottomSheet.findViewById<TextView>(R.id.container_chip)
|
||||
val codec = bottomSheet.findViewById<TextView>(R.id.codec)
|
||||
val fileSize = bottomSheet.findViewById<TextView>(R.id.file_size)
|
||||
|
||||
time!!.visibility = View.GONE
|
||||
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note
|
||||
|
||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||
else container!!.visibility = View.GONE
|
||||
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec!!.visibility = View.GONE
|
||||
}else{
|
||||
codec!!.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||
else fileSize!!.text = fileSizeReadable
|
||||
|
||||
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val url = item.url
|
||||
link!!.text = url
|
||||
link.tag = itemID
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||
true
|
||||
}
|
||||
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||
remove!!.tag = itemID
|
||||
remove.setOnClickListener{
|
||||
removeItem(item, bottomSheet)
|
||||
}
|
||||
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||
openFile!!.visibility = View.GONE
|
||||
|
||||
val redownload = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||
redownload!!.tag = itemID
|
||||
redownload.setOnClickListener{
|
||||
runBlocking{
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
|
||||
redownload.setOnLongClickListener {
|
||||
bottomSheet.cancel()
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(item), item.type, item, false)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
true
|
||||
}
|
||||
|
||||
openFile.visibility = View.GONE
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
},
|
||||
downloadItem = {
|
||||
runBlocking{
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = items.find { it.id == itemID }
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
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!!.size} ${getString(R.string.selected)}"
|
||||
}else{
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
actionMode?.finish()
|
||||
else {
|
||||
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0){
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -238,7 +163,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
downloadViewModel.deleteDownload(item)
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
|
|
@ -252,7 +177,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.cancelled_downloads_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${adapter.getSelectedObjectsCount(totalSize)} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -273,39 +198,53 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
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 ->
|
||||
for (obj in selectedObjects!!){
|
||||
downloadViewModel.deleteDownload(obj)
|
||||
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()
|
||||
}
|
||||
adapter.clearCheckedItems()
|
||||
for (id in selectedObjects){
|
||||
downloadViewModel.deleteDownload(id)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
clearCheckedItems()
|
||||
actionMode?.finish()
|
||||
}
|
||||
deleteDialog.show()
|
||||
true
|
||||
}
|
||||
R.id.redownload -> {
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(selectedObjects!!.toMutableList())
|
||||
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()
|
||||
}
|
||||
adapter.clearCheckedItems()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.reQueueDownloadItems(selectedObjects)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
erroredDownloads.checkAll(items)
|
||||
selectedObjects?.clear()
|
||||
items.forEach { selectedObjects?.add(it) }
|
||||
adapter.checkAll()
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
erroredDownloads.invertSelected(items)
|
||||
val invertedList = arrayListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
adapter.invertSelected()
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
|
@ -314,15 +253,10 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
actionMode = null
|
||||
clearCheckedItems()
|
||||
adapter.clearCheckedItems()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearCheckedItems(){
|
||||
erroredDownloads.clearCheckeditems()
|
||||
selectedObjects?.clear()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||
|
|
@ -331,20 +265,25 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
when (direction) {
|
||||
ItemTouchHelper.RIGHT -> {
|
||||
runBlocking{
|
||||
downloadViewModel.queueDownloads(listOf(items[position]))
|
||||
downloadViewModel.reQueueDownloadItems(listOf(itemID))
|
||||
}
|
||||
}
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = items[position]
|
||||
downloadViewModel.deleteDownload(deletedItem)
|
||||
Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
downloadViewModel.deleteDownload(deletedItem.id)
|
||||
Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -383,7 +322,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder!!,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
|
|||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
|
|
@ -127,6 +128,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
)
|
||||
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
|
||||
recyclerView?.adapter = historyAdapter
|
||||
recyclerView?.enableFastScroll()
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getBoolean("swipe_gestures", true)){
|
||||
|
|
|
|||
|
|
@ -6,23 +6,15 @@ import android.content.DialogInterface
|
|||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -36,26 +28,23 @@ import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
|||
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.databinding.FragmentHomeBinding
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||
|
|
@ -63,12 +52,11 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
private lateinit var queuedRecyclerView : RecyclerView
|
||||
private lateinit var queuedDownloads : GenericDownloadAdapter
|
||||
private lateinit var adapter : GenericDownloadAdapter
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var fileSize: TextView
|
||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
||||
private var totalSize: Int = 0
|
||||
private var actionMode : ActionMode? = null
|
||||
private lateinit var items : MutableList<DownloadItem>
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -79,8 +67,6 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
activity = getActivity()
|
||||
notificationUtil = NotificationUtil(requireContext())
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
items = mutableListOf()
|
||||
selectedObjects = arrayListOf()
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +74,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
fileSize = view.findViewById(R.id.filesize)
|
||||
queuedDownloads =
|
||||
adapter =
|
||||
GenericDownloadAdapter(
|
||||
this,
|
||||
requireActivity()
|
||||
|
|
@ -96,7 +82,9 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
|
||||
queuedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
queuedRecyclerView.forceFastScrollMode()
|
||||
queuedRecyclerView.adapter = queuedDownloads
|
||||
queuedRecyclerView.adapter = adapter
|
||||
queuedRecyclerView.enableFastScroll()
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getBoolean("swipe_gestures", true)){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
|
|
@ -104,18 +92,34 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
}
|
||||
queuedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) {
|
||||
items = it.toMutableList()
|
||||
if (it.isEmpty()) fileSize.visibility = View.GONE
|
||||
else{
|
||||
val size = FileUtil.convertFileSize(it.sumOf { i -> i.format.filesize })
|
||||
if (size == "?") fileSize.visibility = View.GONE
|
||||
else {
|
||||
fileSize.visibility = View.VISIBLE
|
||||
fileSize.text = "${getString(R.string.file_size)}: ~ $size"
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.queuedDownloads.collectLatest {
|
||||
adapter.submitData(it)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.addLoadStateListener { loadState ->
|
||||
lifecycleScope.launch {
|
||||
if (loadState.append.endOfPaginationReached )
|
||||
{
|
||||
if ( adapter.itemCount < 1){
|
||||
fileSize.visibility = View.GONE
|
||||
}else{
|
||||
val size = withContext(Dispatchers.IO){
|
||||
FileUtil.convertFileSize(downloadViewModel.getQueuedCollectedFileSize())
|
||||
}
|
||||
if (size == "?") fileSize.visibility = View.GONE
|
||||
else {
|
||||
fileSize.visibility = View.VISIBLE
|
||||
fileSize.text = "${getString(R.string.file_size)}: ~ $size"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
queuedDownloads.submitList(it)
|
||||
}
|
||||
}
|
||||
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)).observe(viewLifecycleOwner){
|
||||
totalSize = it
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,180 +128,104 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
}
|
||||
|
||||
override fun onCardClick(itemID: Long) {
|
||||
val item = items.find { it.id == itemID } ?: return
|
||||
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
||||
when (item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||
}
|
||||
else -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||
}
|
||||
}
|
||||
|
||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) time!!.visibility = View.GONE
|
||||
else {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = item.downloadStartTime
|
||||
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
||||
|
||||
time.setOnClickListener {
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
bottomSheet.dismiss()
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(item)
|
||||
item.downloadStartTime = it.timeInMillis
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
sheet.hide()
|
||||
removeItem(it.id)
|
||||
},
|
||||
downloadItem = {
|
||||
downloadViewModel.deleteDownload(it.id)
|
||||
it.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(it.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {downloadItem ->
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(downloadItem.id)
|
||||
downloadItem.downloadStartTime = it.timeInMillis
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(downloadItem.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note
|
||||
|
||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||
else container!!.visibility = View.GONE
|
||||
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec!!.visibility = View.GONE
|
||||
}else{
|
||||
codec!!.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||
else fileSize!!.text = fileSizeReadable
|
||||
|
||||
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val url = item.url
|
||||
link!!.text = url
|
||||
link.tag = itemID
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||
true
|
||||
}
|
||||
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||
remove!!.tag = itemID
|
||||
remove.setOnClickListener{
|
||||
bottomSheet.hide()
|
||||
removeItem(itemID)
|
||||
}
|
||||
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||
openFile!!.visibility = View.GONE
|
||||
|
||||
|
||||
|
||||
val downloadNow = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) downloadNow!!.visibility = View.GONE
|
||||
else{
|
||||
downloadNow!!.text = getString(R.string.download_now)
|
||||
downloadNow.setOnClickListener {
|
||||
bottomSheet.dismiss()
|
||||
downloadViewModel.deleteDownload(item)
|
||||
item.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(item.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = items.find { it.id == itemID }
|
||||
val now = System.currentTimeMillis()
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
override fun onCardSelect(isChecked: Boolean, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
val now = System.currentTimeMillis()
|
||||
if (isChecked) {
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
}else{
|
||||
|
||||
}else{
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
actionMode?.finish()
|
||||
else {
|
||||
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0){
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
if (actionMode != null){
|
||||
actionMode!!.menu.getItem(2).isVisible = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems, adapter.inverted, now)
|
||||
}
|
||||
|
||||
actionMode!!.menu.getItem(1).isVisible = selectedObjects == 1 && position > 0
|
||||
}
|
||||
}
|
||||
if (actionMode != null){
|
||||
actionMode!!.menu.getItem(1).isVisible = selectedObjects!!.all { it.downloadStartTime > now }
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeItem(id: Long){
|
||||
val item = items.find { it.id == id } ?: return
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(id)
|
||||
}
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
|
||||
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.deleteDownload(item)
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}.show()
|
||||
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
|
||||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.queued_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${adapter.getSelectedObjectsCount(totalSize)} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -318,46 +246,81 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
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 ->
|
||||
for (obj in selectedObjects!!){
|
||||
val id = obj.id.toInt()
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
downloadViewModel.deleteDownload(obj)
|
||||
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()
|
||||
}
|
||||
adapter.clearCheckedItems()
|
||||
for (id in selectedObjects){
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toInt().toString())
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString())
|
||||
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||
downloadViewModel.deleteDownload(id)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
clearCheckedItems()
|
||||
actionMode?.finish()
|
||||
|
||||
}
|
||||
deleteDialog.show()
|
||||
true
|
||||
}
|
||||
R.id.download -> {
|
||||
for (obj in selectedObjects!!){
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(obj.id.toInt().toString())
|
||||
}
|
||||
selectedObjects!!.forEach { it.downloadStartTime = 0L }
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.queueDownloads(selectedObjects!!)
|
||||
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()
|
||||
}
|
||||
adapter.clearCheckedItems()
|
||||
for (id in selectedObjects){
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString())
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.resetScheduleTimeForItemsAndStartDownload(selectedObjects)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
queuedDownloads.checkAll(items)
|
||||
selectedObjects?.clear()
|
||||
items.forEach { selectedObjects?.add(it) }
|
||||
adapter.checkAll()
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
queuedDownloads.invertSelected(items)
|
||||
val invertedList = arrayListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||
adapter.invertSelected()
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
R.id.up -> {
|
||||
lifecycleScope.launch {
|
||||
if (adapter.getSelectedObjectsCount(totalSize) == 1){
|
||||
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()
|
||||
}
|
||||
|
||||
adapter.clearCheckedItems()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.putAtTopOfQueue(selectedObjects[0])
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
|
@ -366,14 +329,10 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
actionMode = null
|
||||
clearCheckedItems()
|
||||
adapter.clearCheckedItems()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearCheckedItems(){
|
||||
queuedDownloads.clearCheckeditems()
|
||||
selectedObjects?.clear()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
||||
|
|
@ -383,12 +342,16 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = items[position]
|
||||
queuedDownloads.notifyItemChanged(position)
|
||||
removeItem(deletedItem.id)
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
|
||||
removeItem(deletedItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -426,7 +389,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder!!,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
|
|
|
|||
|
|
@ -1,27 +1,21 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloads
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
|
|
@ -29,19 +23,22 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||
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.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||
|
|
@ -49,11 +46,9 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
private lateinit var savedRecyclerView: RecyclerView
|
||||
private lateinit var savedDownloads: GenericDownloadAdapter
|
||||
private var selectedObjects: ArrayList<DownloadItem>? = null
|
||||
private lateinit var adapter: GenericDownloadAdapter
|
||||
private var actionMode : ActionMode? = null
|
||||
private lateinit var items : MutableList<DownloadItem>
|
||||
|
||||
private var totalSize : Int = 0
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
@ -61,16 +56,14 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
): View? {
|
||||
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||
activity = getActivity()
|
||||
selectedObjects = arrayListOf()
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
items = mutableListOf()
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
savedDownloads =
|
||||
adapter =
|
||||
GenericDownloadAdapter(
|
||||
this,
|
||||
requireActivity()
|
||||
|
|
@ -78,7 +71,8 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
|
||||
savedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
savedRecyclerView.forceFastScrollMode()
|
||||
savedRecyclerView.adapter = savedDownloads
|
||||
savedRecyclerView.enableFastScroll()
|
||||
savedRecyclerView.adapter = adapter
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getBoolean("swipe_gestures", true)){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
|
|
@ -87,148 +81,76 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
savedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
|
||||
downloadViewModel.savedDownloads.observe(viewLifecycleOwner) {
|
||||
items = it.toMutableList()
|
||||
savedDownloads.submitList(it)
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.savedDownloads.collectLatest {
|
||||
adapter.submitData(it)
|
||||
}
|
||||
}
|
||||
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Saved)).observe(viewLifecycleOwner){
|
||||
totalSize = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActionButtonClick(itemID: Long) {
|
||||
val item = items.find { it.id == itemID }
|
||||
if (item != null){
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
if (item != null){
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}else{
|
||||
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}else{
|
||||
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCardClick(itemID: Long) {
|
||||
val item = items.find { it.id == itemID } ?: return
|
||||
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
||||
when (item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||
}
|
||||
else -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
removeItem(it, sheet)
|
||||
},
|
||||
downloadItem = {it: DownloadItem ->
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = { it: DownloadItem ->
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||
|
||||
time!!.visibility = View.GONE
|
||||
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note
|
||||
|
||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||
else container!!.visibility = View.GONE
|
||||
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec!!.visibility = View.GONE
|
||||
}else{
|
||||
codec!!.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||
else fileSize!!.text = fileSizeReadable
|
||||
|
||||
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val url = item.url
|
||||
link!!.text = url
|
||||
link.tag = itemID
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||
true
|
||||
}
|
||||
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||
remove!!.tag = itemID
|
||||
remove.setOnClickListener{
|
||||
removeItem(item, bottomSheet)
|
||||
}
|
||||
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||
openFile!!.visibility = View.GONE
|
||||
|
||||
val download = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||
download!!.text = getString(R.string.download)
|
||||
download.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_downloads, 0, 0, 0);
|
||||
download.tag = itemID
|
||||
download.setOnClickListener{
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
|
||||
download.setOnLongClickListener {
|
||||
bottomSheet.cancel()
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(item), item.type, item, false)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
true
|
||||
}
|
||||
|
||||
openFile.visibility = View.GONE
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = items.find { it.id == itemID }
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
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!!.size} ${getString(R.string.selected)}"
|
||||
}else{
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
actionMode?.finish()
|
||||
else {
|
||||
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0){
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -240,7 +162,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
downloadViewModel.deleteDownload(item)
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
|
|
@ -249,7 +171,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.saved_downloads_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${adapter.getSelectedObjectsCount(totalSize)} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -270,39 +192,54 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
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 ->
|
||||
for (obj in selectedObjects!!){
|
||||
downloadViewModel.deleteDownload(obj)
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = if (adapter.inverted){
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
|
||||
DownloadRepository.Status.Saved))
|
||||
}
|
||||
}else{
|
||||
adapter.checkedItems.toList()
|
||||
}
|
||||
adapter.clearCheckedItems()
|
||||
for (id in selectedObjects){
|
||||
downloadViewModel.deleteDownload(id)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
clearCheckedItems()
|
||||
actionMode?.finish()
|
||||
}
|
||||
deleteDialog.show()
|
||||
true
|
||||
}
|
||||
R.id.download -> {
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(selectedObjects!!.toMutableList())
|
||||
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()
|
||||
}
|
||||
adapter.clearCheckedItems()
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.reQueueDownloadItems(selectedObjects)
|
||||
}
|
||||
actionMode?.finish()
|
||||
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
savedDownloads.checkAll(items)
|
||||
selectedObjects?.clear()
|
||||
items.forEach { selectedObjects?.add(it) }
|
||||
adapter.checkAll()
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
savedDownloads.invertSelected(items)
|
||||
val invertedList = arrayListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
adapter.invertSelected()
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
|
@ -311,14 +248,10 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
actionMode = null
|
||||
clearCheckedItems()
|
||||
adapter.clearCheckedItems()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearCheckedItems(){
|
||||
savedDownloads.clearCheckeditems()
|
||||
selectedObjects?.clear()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
|
|
@ -328,18 +261,22 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = items[position]
|
||||
downloadViewModel.deleteDownload(deletedItem)
|
||||
Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
downloadViewModel.deleteDownload(deletedItem.id)
|
||||
Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
ItemTouchHelper.RIGHT -> {
|
||||
onActionButtonClick(items[position].id)
|
||||
onActionButtonClick(itemID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -377,7 +314,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder!!,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.adapter.CookieAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.more
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
|
|
@ -68,6 +69,7 @@ class WebViewActivity : BaseActivity() {
|
|||
|
||||
generateBtn.setOnClickListener {
|
||||
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
||||
Log.e("asas", it)
|
||||
kotlin.runCatching {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
|
|
|
|||
|
|
@ -31,28 +31,6 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
|
|||
ignoreBatteryOptimization!!.isVisible = false
|
||||
}
|
||||
|
||||
concurrentDownloads = findPreference("concurrent_downloads")
|
||||
concurrentDownloads!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(resources.getString(R.string.warning))
|
||||
.setMessage(resources.getString(R.string.workmanager_updated))
|
||||
.setNegativeButton(resources.getString(R.string.cancel)) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.setPositiveButton(resources.getString(R.string.restart)) { _, _ ->
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
requireContext().startActivity(intent)
|
||||
if (context is Activity) {
|
||||
(context as Activity).finish()
|
||||
}
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
.show()
|
||||
|
||||
true
|
||||
}
|
||||
ignoreBatteryOptimization = findPreference("ignore_battery")
|
||||
ignoreBatteryOptimization!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
|
|
|
|||
|
|
@ -113,14 +113,14 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||
audioFilenameTemplate?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||
|
||||
var cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
var cacheSize = File(FileUtil.getCachePath()).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
clearCache!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
if (activeDownloadCount == 0){
|
||||
File(requireContext().cacheDir.absolutePath + "/downloads").deleteRecursively()
|
||||
File(FileUtil.getCachePath()).deleteRecursively()
|
||||
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
|
||||
cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
cacheSize = File(FileUtil.getCachePath()).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
}else{
|
||||
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
version = findPreference("version")
|
||||
val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir
|
||||
|
||||
version!!.summary = "${BuildConfig.VERSION_NAME} [${nativeLibraryDir?.split("/lib/")?.get(1)}] ${BuildConfig.BUILD_TYPE}"
|
||||
version!!.summary = "${BuildConfig.VERSION_NAME} [${nativeLibraryDir?.split("/lib/")?.get(1)}]"
|
||||
|
||||
version!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
|
|
@ -314,6 +314,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
val arr = JsonArray()
|
||||
items.forEach {
|
||||
it.useAsExtraCommand = false
|
||||
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
|
||||
}
|
||||
return arr
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.os.storage.StorageVolume
|
|||
import android.provider.DocumentsContract
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
|
@ -20,6 +21,7 @@ import com.anggrayudi.storage.file.copyFolderTo
|
|||
import com.anggrayudi.storage.file.getAbsolutePath
|
||||
import com.anggrayudi.storage.file.getBasePath
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.BuildConfig
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -27,11 +29,13 @@ import okhttp3.internal.closeQuietly
|
|||
import java.io.File
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.CopyOption
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.pow
|
||||
|
||||
|
|
@ -87,7 +91,7 @@ object FileUtil {
|
|||
if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
|
||||
var destFile: DocumentFile
|
||||
try {
|
||||
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)".toRegex())){
|
||||
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)|(live_chat)".toRegex())){
|
||||
it.delete()
|
||||
return@forEach
|
||||
}
|
||||
|
|
@ -182,8 +186,15 @@ object FileUtil {
|
|||
super.onFailed(errorCode)
|
||||
}
|
||||
|
||||
override fun onConflict(
|
||||
destinationFile: DocumentFile,
|
||||
action: FileConflictAction
|
||||
) {
|
||||
action.confirmResolution(ConflictResolution.CREATE_NEW)
|
||||
}
|
||||
|
||||
override fun onStart(file: Any, workerThread: Thread): Long {
|
||||
return 1000 // update progress every 1 second
|
||||
return 500 // update progress every 1 second
|
||||
}
|
||||
|
||||
override fun onReport(report: Report) {
|
||||
|
|
@ -204,17 +215,37 @@ object FileUtil {
|
|||
val files = it.listFiles()?.filter { fil -> !fil.isDirectory }?.toTypedArray() ?: arrayOf(it)
|
||||
for (ff in files){
|
||||
val newFile = File(dir.absolutePath + "/${ff.absolutePath.removePrefix(originDir.absolutePath)}")
|
||||
newFile.mkdirs()
|
||||
if (Build.VERSION.SDK_INT >= 26 ) {
|
||||
Files.move(
|
||||
ff.toPath(),
|
||||
newFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}else{
|
||||
ff.renameTo(newFile)
|
||||
runCatching {
|
||||
newFile.parentFile?.mkdirs()
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 26 ) {
|
||||
var newFileName = newFile.absolutePath
|
||||
var counter = 1
|
||||
while (Files.exists(File(newFileName).toPath())) {
|
||||
// If the file already exists in the destination directory, add a number to differentiate it
|
||||
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
||||
counter++
|
||||
}
|
||||
|
||||
fileList.add(Files.move(
|
||||
ff.toPath(),
|
||||
File(newFileName).toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
).absolutePathString())
|
||||
ff.delete()
|
||||
fileList.add(newFileName)
|
||||
}else{
|
||||
var newFileName = newFile.absolutePath
|
||||
var counter = 1
|
||||
while (File(newFileName).exists()) {
|
||||
// If the file already exists in the destination directory, add a number to differentiate it
|
||||
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
||||
counter++
|
||||
}
|
||||
|
||||
ff.renameTo(File(newFileName))
|
||||
fileList.add(newFileName)
|
||||
}
|
||||
fileList.add(newFile.absolutePath)
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
|
|
@ -238,17 +269,25 @@ object FileUtil {
|
|||
return listOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
|
||||
fun getCachePath() : String {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/.CACHE"
|
||||
}
|
||||
|
||||
fun getDefaultAudioPath() : String{
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Audio"
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Audio"
|
||||
}
|
||||
|
||||
fun getDefaultVideoPath() : String{
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Video"
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Video"
|
||||
}
|
||||
|
||||
fun getDefaultCommandPath() : String {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis/Command"
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Command"
|
||||
}
|
||||
|
||||
fun getCookieFile(context : Context, path: (path: String) -> Unit){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()) path(cookiesFile.absolutePath)
|
||||
}
|
||||
|
||||
fun convertFileSize(s: Long): String{
|
||||
|
|
|
|||
|
|
@ -448,14 +448,14 @@ class InfoUtil(private val context: Context) {
|
|||
try {
|
||||
val request = YoutubeDLRequest(url)
|
||||
request.addOption("--print", "%(formats)s")
|
||||
request.addOption("--print", "%(duration)s")
|
||||
request.addOption("--skip-download")
|
||||
request.addOption("-R", "1")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()) {
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -476,10 +476,10 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
return parseYTDLFormats(jsonArray)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Looper.prepare().run {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
e.printStackTrace()
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
|
@ -507,9 +507,8 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -569,9 +568,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -611,7 +609,13 @@ class InfoUtil(private val context: Context) {
|
|||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
||||
}
|
||||
}
|
||||
val url = jsonObject.getString("webpage_url")
|
||||
|
||||
val url = if (jsonObject.has("url")){
|
||||
jsonObject.getString("url")
|
||||
}else{
|
||||
jsonObject.getString("webpage_url")
|
||||
}
|
||||
|
||||
var thumb: String? = ""
|
||||
if (jsonObject.has("thumbnail")) {
|
||||
thumb = jsonObject.getString("thumbnail")
|
||||
|
|
@ -722,9 +726,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -885,9 +888,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -927,7 +929,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||
val cacheDir = File(context.cacheDir.absolutePath + "/downloads/")
|
||||
val cacheDir = FileUtil.getCachePath()
|
||||
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
||||
tempFileDir.delete()
|
||||
tempFileDir.mkdirs()
|
||||
|
|
@ -972,26 +974,29 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
if (sponsorBlockFilters.isNotEmpty()) {
|
||||
val filters = java.lang.String.join(",", sponsorBlockFilters.filter { it.isNotBlank() })
|
||||
if (filters.isNotBlank()) {
|
||||
request.addOption("--sponsorblock-remove", filters)
|
||||
if (sharedPreferences.getBoolean("force_keyframes", false)){
|
||||
request.addOption("--force-keyframes-at-cuts")
|
||||
|
||||
if (sharedPreferences.getBoolean("use_sponsorblock", true)){
|
||||
if (sponsorBlockFilters.isNotEmpty()) {
|
||||
val filters = java.lang.String.join(",", sponsorBlockFilters.filter { it.isNotBlank() })
|
||||
if (filters.isNotBlank()) {
|
||||
request.addOption("--sponsorblock-remove", filters)
|
||||
if (sharedPreferences.getBoolean("force_keyframes", false)){
|
||||
request.addOption("--force-keyframes-at-cuts")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(downloadItem.title.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title.take(200)))
|
||||
}
|
||||
if (downloadItem.author.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
|
||||
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", downloadItem.author.take(25))
|
||||
}
|
||||
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
||||
|
||||
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader|${downloadItem.author})s")
|
||||
|
||||
if (downloadItem.downloadSections.isNotBlank()){
|
||||
downloadItem.downloadSections.split(";").forEach {
|
||||
if (it.isBlank()) return@forEach
|
||||
|
|
@ -1004,7 +1009,7 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--force-keyframes-at-cuts")
|
||||
}
|
||||
}
|
||||
downloadItem.customFileNameTemplate += " %(section_title)s %(autonumber)s"
|
||||
downloadItem.customFileNameTemplate += " %(section_title|)s %(autonumber)s"
|
||||
request.addOption("--output-na-placeholder", " ")
|
||||
}
|
||||
|
||||
|
|
@ -1013,7 +1018,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if (downloadItem.extraCommands.isNotBlank()){
|
||||
val conf = File(context.cacheDir.absolutePath + "/downloads/configExtraCommands${System.currentTimeMillis()}.txt")
|
||||
val conf = File(tempFileDir.absolutePath + "/${System.currentTimeMillis()}.txt")
|
||||
conf.createNewFile()
|
||||
conf.writeText(downloadItem.extraCommands)
|
||||
request.addOption(
|
||||
|
|
@ -1032,9 +1037,8 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1093,7 +1097,7 @@ class InfoUtil(private val context: Context) {
|
|||
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
|
||||
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
|
||||
try {
|
||||
val config = File(context.cacheDir.absolutePath + "/downloads/${downloadItem.id}/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
|
||||
val config = File(tempFileDir.absolutePath + "/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
|
||||
val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||
config.writeText(configData)
|
||||
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
||||
|
|
@ -1118,7 +1122,9 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if (downloadItem.videoPreferences.addChapters) {
|
||||
request.addOption("--sponsorblock-mark", "all")
|
||||
if (sharedPreferences.getBoolean("use_sponsorblock", true)){
|
||||
request.addOption("--sponsorblock-mark", "all")
|
||||
}
|
||||
request.addOption("--embed-chapters")
|
||||
}
|
||||
if (downloadItem.videoPreferences.embedSubs) {
|
||||
|
|
@ -1146,10 +1152,18 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
Log.e(DownloadWorker.TAG, formatArgument)
|
||||
request.addOption("-f", formatArgument)
|
||||
val outputFormat = downloadItem.container
|
||||
if(outputFormat.isNotEmpty() && outputFormat != "Default" && outputFormat != context.getString(R.string.defaultValue) && supportedContainers.contains(outputFormat)){
|
||||
request.addOption("--merge-output-format", outputFormat.lowercase())
|
||||
if (outputFormat != "webm") {
|
||||
|
||||
if (downloadItem.format.container.equals("mpeg_4", true)) downloadItem.format.container = "mp4"
|
||||
val outputContainer = downloadItem.container
|
||||
if(
|
||||
outputContainer.isNotEmpty() &&
|
||||
outputContainer != "Default" &&
|
||||
outputContainer != context.getString(R.string.defaultValue) &&
|
||||
supportedContainers.contains(outputContainer) &&
|
||||
!outputContainer.equals(downloadItem.format.container, true)
|
||||
){
|
||||
request.addOption("--merge-output-format", outputContainer.lowercase())
|
||||
if (outputContainer != "webm") {
|
||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||
if (embedThumb) {
|
||||
request.addOption("--embed-thumbnail")
|
||||
|
|
@ -1188,7 +1202,7 @@ class InfoUtil(private val context: Context) {
|
|||
DownloadViewModel.Type.command -> {
|
||||
request.addOption(
|
||||
"--config-locations",
|
||||
File(context.cacheDir.absolutePath + "/downloads/config${System.currentTimeMillis()}.txt").apply {
|
||||
File(FileUtil.getCachePath() + "/config[${downloadItem.id}].txt").apply {
|
||||
writeText(downloadItem.format.format_note)
|
||||
}.absolutePath
|
||||
)
|
||||
|
|
|
|||
|
|
@ -75,6 +75,25 @@ class NotificationUtil(var context: Context) {
|
|||
return downloadNotificationBuilder
|
||||
}
|
||||
|
||||
fun createDefaultWorkerNotification() : Notification {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(context.getString(R.string.downloading))
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.clearActions()
|
||||
.build()
|
||||
}
|
||||
|
||||
fun createDownloadServiceNotification(
|
||||
pendingIntent: PendingIntent?,
|
||||
|
|
@ -188,6 +207,7 @@ class NotificationUtil(var context: Context) {
|
|||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
.setContentText("")
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.clearActions()
|
||||
|
|
@ -277,6 +297,11 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
|
||||
fun notify(id: Int, notification: Notification){
|
||||
notificationManager.notify(id, notification)
|
||||
}
|
||||
|
||||
fun updateDownloadNotification(
|
||||
id: Int,
|
||||
desc: String,
|
||||
|
|
@ -344,6 +369,19 @@ class NotificationUtil(var context: Context) {
|
|||
fun createYTDLUpdateNotification() : Notification{
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
notificationBuilder.setContentTitle("Updating YT-DLP...")
|
||||
.setOngoing(true)
|
||||
.setCategory(Notification.CATEGORY_EVENT)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
context.resources,
|
||||
android.R.drawable.stat_sys_download
|
||||
)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.clearActions()
|
||||
return notificationBuilder.build()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ import android.content.ClipboardManager
|
|||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.OvalShape
|
||||
import android.net.Uri
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
|
|
@ -28,18 +31,26 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
|
|
@ -53,8 +64,11 @@ import com.google.android.material.timepicker.MaterialTimePicker
|
|||
import com.google.android.material.timepicker.TimeFormat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
object UiUtil {
|
||||
@SuppressLint("SetTextI18n")
|
||||
|
|
@ -280,7 +294,7 @@ object UiUtil {
|
|||
if (! file.exists()) return@forEach
|
||||
val uri = FileProvider.getUriForFile(
|
||||
fragmentContext,
|
||||
"com.deniscerri.ytdl.fileprovider",
|
||||
fragmentContext.packageName + ".fileprovider",
|
||||
file
|
||||
)
|
||||
uris.add(uri)
|
||||
|
|
@ -336,6 +350,150 @@ object UiUtil {
|
|||
datePicker.show(fragmentManager, "datepicker")
|
||||
}
|
||||
|
||||
fun showDownloadItemDetailsCard(
|
||||
item: DownloadItem,
|
||||
context: Activity,
|
||||
status: DownloadRepository.Status,
|
||||
removeItem : (DownloadItem, BottomSheetDialog) -> Unit,
|
||||
downloadItem: (DownloadItem) -> Unit,
|
||||
longClickDownloadButton: (DownloadItem) -> Unit,
|
||||
scheduleButtonClick: (DownloadItem) -> Unit?
|
||||
){
|
||||
val bottomSheet = BottomSheetDialog(context)
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||
title!!.text = item.title.ifEmpty { "`${context.getString(R.string.defaultValue)}`" }
|
||||
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||
author!!.text = item.author.ifEmpty { "`${context.getString(R.string.defaultValue)}`" }
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||
|
||||
when (item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(context, R.drawable.ic_music)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(context, R.drawable.ic_video)
|
||||
}
|
||||
else -> {
|
||||
btn!!.icon = ContextCompat.getDrawable(context, R.drawable.ic_terminal)
|
||||
}
|
||||
}
|
||||
|
||||
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||
|
||||
when(status){
|
||||
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused -> {
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) time!!.visibility = View.GONE
|
||||
else {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = item.downloadStartTime
|
||||
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
||||
|
||||
time.setOnClickListener {
|
||||
scheduleButtonClick(item)
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
time!!.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note
|
||||
|
||||
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||
else container!!.visibility = View.GONE
|
||||
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec!!.visibility = View.GONE
|
||||
}else{
|
||||
codec!!.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||
else fileSize!!.text = fileSizeReadable
|
||||
|
||||
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val url = item.url
|
||||
link!!.text = url
|
||||
link.tag = item.id
|
||||
link.setOnClickListener{
|
||||
openLinkIntent(context, item.url, bottomSheet)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
copyLinkToClipBoard(context, item.url, bottomSheet)
|
||||
true
|
||||
}
|
||||
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||
remove!!.tag = item.id
|
||||
remove.setOnClickListener{
|
||||
removeItem(item, bottomSheet)
|
||||
}
|
||||
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||
openFile!!.visibility = View.GONE
|
||||
|
||||
val download = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||
download?.tag = item.id
|
||||
when(status){
|
||||
DownloadRepository.Status.Cancelled, DownloadRepository.Status.Saved -> {
|
||||
download!!.text = context.getString(R.string.download)
|
||||
download.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_downloads, 0, 0, 0);
|
||||
download.setOnLongClickListener {
|
||||
longClickDownloadButton(item)
|
||||
bottomSheet.cancel()
|
||||
true
|
||||
}
|
||||
}
|
||||
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused -> {
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) download!!.visibility = View.GONE
|
||||
else{
|
||||
download!!.text = context.getString(R.string.download_now)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
download?.setOnLongClickListener {
|
||||
longClickDownloadButton(item)
|
||||
bottomSheet.cancel()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
download?.setOnClickListener {
|
||||
bottomSheet.dismiss()
|
||||
downloadItem(item)
|
||||
}
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
context.windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
fun showFormatDetails(format: Format, activity: Activity){
|
||||
val bottomSheet = BottomSheetDialog(activity)
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
|
@ -544,6 +702,395 @@ object UiUtil {
|
|||
)
|
||||
}
|
||||
|
||||
fun configureVideo(
|
||||
view: View,
|
||||
context: Activity,
|
||||
items: List<DownloadItem>,
|
||||
embedSubsClicked : (Boolean) -> Unit,
|
||||
addChaptersClicked: (Boolean) -> Unit,
|
||||
splitByChaptersClicked: (Boolean) -> Unit,
|
||||
saveThumbnailClicked: (Boolean) -> Unit,
|
||||
sponsorBlockItemsSet: (values: Array<String>, checkedItems: List<Boolean>) -> Unit,
|
||||
cutClicked: (VideoCutListener) -> Unit,
|
||||
filenameTemplateSet: (String) -> Unit,
|
||||
saveSubtitlesClicked: (Boolean) -> Unit,
|
||||
subtitleLanguagesClicked: () -> Unit,
|
||||
removeAudioClicked: (Boolean) -> Unit,
|
||||
extraCommandsClicked: () -> Unit
|
||||
){
|
||||
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
||||
embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs }
|
||||
embedSubs.setOnClickListener {
|
||||
embedSubsClicked(embedSubs.isChecked)
|
||||
}
|
||||
|
||||
val addChapters = view.findViewById<Chip>(R.id.add_chapters)
|
||||
addChapters!!.isChecked = items.all { it.videoPreferences.addChapters }
|
||||
addChapters.setOnClickListener{
|
||||
addChaptersClicked(addChapters.isChecked)
|
||||
}
|
||||
|
||||
|
||||
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
|
||||
if(items.size == 1 && items[0].downloadSections.isNotBlank()){
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
}else{
|
||||
splitByChapters!!.isChecked = items.all { it.videoPreferences.splitByChapters }
|
||||
}
|
||||
if (splitByChapters.isChecked){
|
||||
items.forEach { it.videoPreferences.addChapters = false }
|
||||
addChapters.isChecked = false
|
||||
}
|
||||
splitByChapters.setOnClickListener {
|
||||
if (splitByChapters.isChecked){
|
||||
addChapters.isEnabled = false
|
||||
addChapters.isChecked = false
|
||||
addChaptersClicked(false)
|
||||
}else{
|
||||
addChapters.isEnabled = true
|
||||
}
|
||||
splitByChaptersClicked(splitByChapters.isChecked)
|
||||
}
|
||||
|
||||
val saveThumbnail = view.findViewById<Chip>(R.id.save_thumbnail)
|
||||
saveThumbnail!!.isChecked = items.all { it.SaveThumb }
|
||||
saveThumbnail.setOnClickListener {
|
||||
saveThumbnailClicked(saveThumbnail.isChecked)
|
||||
}
|
||||
|
||||
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
|
||||
sponsorBlock!!.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(context.getString(R.string.select_sponsorblock_filtering))
|
||||
val values = context.resources.getStringArray(R.array.sponsorblock_settings_values)
|
||||
val entries = context.resources.getStringArray(R.array.sponsorblock_settings_entries)
|
||||
val checkedItems : ArrayList<Boolean> = arrayListOf()
|
||||
values.forEach {
|
||||
if (items[0].videoPreferences.sponsorBlockFilters.contains(it) && items.size == 1) {
|
||||
checkedItems.add(true)
|
||||
}else{
|
||||
checkedItems.add(false)
|
||||
}
|
||||
}
|
||||
|
||||
builder.setMultiChoiceItems(
|
||||
entries,
|
||||
checkedItems.toBooleanArray()
|
||||
) { _, which, isChecked ->
|
||||
checkedItems[which] = isChecked
|
||||
}
|
||||
|
||||
builder.setPositiveButton(
|
||||
context.getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
sponsorBlockItemsSet(values, checkedItems)
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
val cut = view.findViewById<Chip>(R.id.cut)
|
||||
if (items.size > 1) cut.isVisible = false
|
||||
else{
|
||||
if(items[0].duration.isNotEmpty()){
|
||||
val downloadItem = items[0]
|
||||
cut.isEnabled = true
|
||||
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
|
||||
val cutVideoListener = object : VideoCutListener {
|
||||
|
||||
override fun onChangeCut(list: List<String>) {
|
||||
if (list.isEmpty()){
|
||||
downloadItem.downloadSections = ""
|
||||
cut.text = context.getString(R.string.cut)
|
||||
|
||||
splitByChapters.isEnabled = true
|
||||
splitByChapters.isChecked = downloadItem.videoPreferences.splitByChapters
|
||||
if (splitByChapters.isChecked){
|
||||
addChapters.isEnabled = false
|
||||
addChapters.isChecked = false
|
||||
}else{
|
||||
addChapters.isEnabled = true
|
||||
}
|
||||
}else{
|
||||
var value = ""
|
||||
list.forEach {
|
||||
value += "$it;"
|
||||
}
|
||||
downloadItem.downloadSections = value
|
||||
cut.text = value.dropLast(1)
|
||||
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
addChapters.isEnabled = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
cut.setOnClickListener {
|
||||
cutClicked(cutVideoListener)
|
||||
}
|
||||
}else{
|
||||
cut.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
||||
filenameTemplate.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(context.getString(R.string.file_name_template))
|
||||
val inputLayout = context.layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = context.getString(R.string.file_name_template)
|
||||
if (items.size == 1){
|
||||
editText.setText(items[0].customFileNameTemplate)
|
||||
}
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setPositiveButton(
|
||||
context.getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
filenameTemplateSet(editText.text.toString())
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
editText.doOnTextChanged { _, _, _, _ ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
}
|
||||
dialog.show()
|
||||
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
|
||||
val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages)
|
||||
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
items.forEach { it.videoPreferences.subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!! }
|
||||
if (items.all { it.videoPreferences.writeSubs}) {
|
||||
saveSubtitles.isChecked = true
|
||||
subtitleLanguages.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
saveSubtitles.setOnCheckedChangeListener { _, _ ->
|
||||
if (saveSubtitles.isChecked) subtitleLanguages.visibility = View.VISIBLE
|
||||
else subtitleLanguages.visibility = View.GONE
|
||||
saveSubtitlesClicked(saveSubtitles.isChecked)
|
||||
}
|
||||
|
||||
subtitleLanguages.setOnClickListener {
|
||||
subtitleLanguagesClicked()
|
||||
}
|
||||
|
||||
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
|
||||
removeAudio.isChecked = items.all { it.videoPreferences.removeAudio }
|
||||
removeAudio.setOnCheckedChangeListener { _, _ ->
|
||||
removeAudioClicked(removeAudio.isChecked)
|
||||
}
|
||||
|
||||
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
||||
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||
extraCommands.visibility = View.VISIBLE
|
||||
extraCommands.setOnClickListener {
|
||||
extraCommandsClicked()
|
||||
}
|
||||
}else{
|
||||
extraCommands.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun configureAudio(
|
||||
view: View,
|
||||
context: Activity,
|
||||
items: List<DownloadItem>,
|
||||
embedThumbClicked: (Boolean) -> Unit,
|
||||
splitByChaptersClicked: (Boolean) -> Unit,
|
||||
filenameTemplateSet: (String) -> Unit,
|
||||
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
|
||||
cutClicked: (VideoCutListener) -> Unit,
|
||||
extraCommandsClicked: () -> Unit,
|
||||
|
||||
){
|
||||
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
|
||||
embedThumb!!.isChecked = items.all { it.audioPreferences.embedThumb }
|
||||
embedThumb.setOnClickListener {
|
||||
embedThumbClicked(embedThumb.isChecked)
|
||||
}
|
||||
|
||||
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
|
||||
if (items.size == 1 && items[0].downloadSections.isNotBlank()){
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
}else{
|
||||
splitByChapters!!.isChecked = items.all { it.audioPreferences.splitByChapters }
|
||||
}
|
||||
|
||||
splitByChapters.setOnClickListener {
|
||||
splitByChaptersClicked(splitByChapters.isChecked)
|
||||
}
|
||||
|
||||
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
|
||||
filenameTemplate.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(context.getString(R.string.file_name_template))
|
||||
val inputLayout = context.layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = context.getString(R.string.file_name_template)
|
||||
if (items.size == 1){
|
||||
editText.setText(items[0].customFileNameTemplate)
|
||||
}
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setPositiveButton(
|
||||
context.getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
filenameTemplateSet(editText.text.toString())
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
editText.doOnTextChanged { _, _, _, _ ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
}
|
||||
dialog.show()
|
||||
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
|
||||
sponsorBlock!!.setOnClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(context.getString(R.string.select_sponsorblock_filtering))
|
||||
val values = context.resources.getStringArray(R.array.sponsorblock_settings_values)
|
||||
val entries = context.resources.getStringArray(R.array.sponsorblock_settings_entries)
|
||||
val checkedItems : ArrayList<Boolean> = arrayListOf()
|
||||
values.forEach {
|
||||
if (items[0].audioPreferences.sponsorBlockFilters.contains(it) && items.size == 1) {
|
||||
checkedItems.add(true)
|
||||
}else{
|
||||
checkedItems.add(false)
|
||||
}
|
||||
}
|
||||
|
||||
builder.setMultiChoiceItems(
|
||||
entries,
|
||||
checkedItems.toBooleanArray()
|
||||
) { _, which, isChecked ->
|
||||
checkedItems[which] = isChecked
|
||||
}
|
||||
|
||||
builder.setPositiveButton(
|
||||
context.getString(R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
sponsorBlockItemsSet(values, checkedItems)
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
val cut = view.findViewById<Chip>(R.id.cut)
|
||||
if (items.size > 1) cut.isVisible = false
|
||||
else{
|
||||
val downloadItem = items[0]
|
||||
if (downloadItem.duration.isNotEmpty()){
|
||||
cut.isEnabled = true
|
||||
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
|
||||
val cutVideoListener = object : VideoCutListener {
|
||||
override fun onChangeCut(list: List<String>) {
|
||||
if (list.isEmpty()){
|
||||
downloadItem.downloadSections = ""
|
||||
cut.text = context.getString(R.string.cut)
|
||||
|
||||
splitByChapters.isEnabled = true
|
||||
splitByChapters.isChecked = downloadItem.audioPreferences.splitByChapters
|
||||
}else{
|
||||
var value = ""
|
||||
list.forEach {
|
||||
value += "$it;"
|
||||
}
|
||||
downloadItem.downloadSections = value
|
||||
cut.text = value.dropLast(1)
|
||||
|
||||
splitByChapters.isEnabled = false
|
||||
splitByChapters.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
cut.setOnClickListener {
|
||||
cutClicked(cutVideoListener)
|
||||
}
|
||||
|
||||
}else{
|
||||
cut.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
if (sharedPreferences.getBoolean("use_extra_commands", false)){
|
||||
extraCommands.visibility = View.VISIBLE
|
||||
extraCommands.setOnClickListener {
|
||||
extraCommandsClicked()
|
||||
}
|
||||
}else{
|
||||
extraCommands.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun configureCommand(
|
||||
view: View,
|
||||
size: Int,
|
||||
newTemplateClicked: () -> Unit,
|
||||
editSelectedClicked: () -> Unit,
|
||||
){
|
||||
val newTemplate : Chip = view.findViewById(R.id.newTemplate)
|
||||
newTemplate.setOnClickListener {
|
||||
newTemplateClicked()
|
||||
}
|
||||
|
||||
val editSelected : Chip = view.findViewById(R.id.editSelected)
|
||||
editSelected.isEnabled = size == 1
|
||||
editSelected.setOnClickListener {
|
||||
editSelectedClicked()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun RecyclerView.forceFastScrollMode()
|
||||
{
|
||||
|
|
@ -561,15 +1108,27 @@ object UiUtil {
|
|||
}
|
||||
}
|
||||
|
||||
fun RecyclerView.enableFastScroll(){
|
||||
val drawable = ShapeDrawable(OvalShape())
|
||||
drawable.paint.color = context.getColor(android.R.color.transparent)
|
||||
|
||||
FastScrollerBuilder(this)
|
||||
.setTrackDrawable(drawable)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fun EditText.setTextAndRecalculateWidth(t : String){
|
||||
val scale = context.resources.displayMetrics.density
|
||||
this.setText(t)
|
||||
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||
this.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
val requiredWidth: Int = this.measuredWidth
|
||||
if (t.isEmpty()){
|
||||
this.layoutParams.width = 70
|
||||
if (t.length < 5){
|
||||
this.layoutParams.width = (70 * scale + 0.5F).toInt()
|
||||
}else{
|
||||
this.layoutParams.width = requiredWidth
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,15 @@ package com.deniscerri.ytdlnis.util
|
|||
import android.content.Context
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.cronet.CronetDataSource
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import org.chromium.net.CronetEngine
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
object VideoPlayerUtil {
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
fun buildPlayer(context: Context) : ExoPlayer {
|
||||
val player: ExoPlayer
|
||||
|
||||
val cronetEngine: CronetEngine = CronetEngine.Builder(App.instance)
|
||||
.enableHttp2(true)
|
||||
.enableQuic(true)
|
||||
.enableBrotli(true)
|
||||
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 1024L * 1024L) // 1MiB
|
||||
.build()
|
||||
|
||||
val trackSelector = DefaultTrackSelector(context)
|
||||
val loadControl = DefaultLoadControl.Builder()
|
||||
|
|
@ -37,15 +25,8 @@ object VideoPlayerUtil {
|
|||
)
|
||||
.build()
|
||||
|
||||
val cronetDataSourceFactory = CronetDataSource.Factory(
|
||||
cronetEngine,
|
||||
Executors.newCachedThreadPool()
|
||||
)
|
||||
val dataSourceFactory = DefaultDataSource.Factory(context, cronetDataSourceFactory)
|
||||
|
||||
player = ExoPlayer.Builder(context)
|
||||
.setUsePlatformDiagnostics(false)
|
||||
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
|
||||
.setTrackSelector(trackSelector)
|
||||
.setLoadControl(loadControl)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import android.util.Log
|
|||
import android.widget.Toast
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Data
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.await
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
|
|
@ -21,215 +23,236 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
|
|||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
class DownloadWorker(
|
||||
private val context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : Worker(context, workerParams) {
|
||||
override fun doWork(): Result {
|
||||
itemId = inputData.getLong("id", 0)
|
||||
if (itemId == 0L || isStopped) return Result.success()
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
override suspend fun doWork(): Result {
|
||||
if (isStopped) return Result.success()
|
||||
|
||||
val notificationUtil = NotificationUtil(context)
|
||||
val infoUtil = InfoUtil(context)
|
||||
val dbManager = DBManager.getInstance(context)
|
||||
val dao = dbManager.downloadDao
|
||||
val repository = DownloadRepository(dao)
|
||||
val historyDao = dbManager.historyDao
|
||||
val resultDao = dbManager.resultDao
|
||||
val logRepo = LogRepository(dbManager.logDao)
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val time = System.currentTimeMillis() + 6000
|
||||
val queuedItems = dao.getQueuedDownloadsThatAreNotScheduledChunked(time)
|
||||
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
||||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
||||
|
||||
val downloadItem: DownloadItem?
|
||||
try {
|
||||
downloadItem = repository.getItemByID(itemId)
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
if (downloadItem.status != DownloadRepository.Status.Queued.toString() && downloadItem.status != DownloadRepository.Status.Paused.toString()) return Result.success()
|
||||
runningYTDLInstances.addAll(dao.getActiveDownloadsList().map { it.id })
|
||||
|
||||
val pendingIntent = NavDeepLinkBuilder(context)
|
||||
.setGraph(R.navigation.nav_graph)
|
||||
.setDestination(R.id.downloadQueueMainFragment)
|
||||
.createPendingIntent()
|
||||
|
||||
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
val foregroundInfo = ForegroundInfo(downloadItem.id.toInt(), notification)
|
||||
var notification = notificationUtil.createDefaultWorkerNotification()
|
||||
val foregroundInfo = ForegroundInfo(Random.nextInt(1000000000), notification)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
Log.e(TAG, downloadItem.toString())
|
||||
|
||||
runBlocking{
|
||||
YoutubeDL.getInstance().destroyProcessById(itemId.toString())
|
||||
repository.setDownloadStatus(downloadItem, DownloadRepository.Status.Active)
|
||||
}
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
//update item if its incomplete
|
||||
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
}
|
||||
|
||||
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
||||
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + downloadItem.id)
|
||||
tempFileDir.delete()
|
||||
tempFileDir.mkdirs()
|
||||
|
||||
val downloadLocation = downloadItem.downloadPath
|
||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||
|
||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||
|
||||
val logItem = LogItem(
|
||||
0,
|
||||
downloadItem.title.ifEmpty { downloadItem.url },
|
||||
"Downloading:\n" +
|
||||
"Title: ${downloadItem.title}\n" +
|
||||
"URL: ${downloadItem.url}\n" +
|
||||
"Type: ${downloadItem.type}\n" +
|
||||
"Format: ${downloadItem.format}\n\n" +
|
||||
"Command: ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
|
||||
downloadItem.format,
|
||||
downloadItem.type,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
|
||||
if (logDownloads){
|
||||
runBlocking {
|
||||
logItem.id = logRepo.insert(logItem)
|
||||
downloadItem.logID = logItem.id
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
}
|
||||
runCatching {
|
||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
|
||||
val title: String = downloadItem.title
|
||||
notificationUtil.updateDownloadNotification(
|
||||
downloadItem.id.toInt(),
|
||||
line, progress.toInt(), 0, title,
|
||||
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||
)
|
||||
if (logDownloads){
|
||||
queuedItems.collectLatest { items ->
|
||||
val running = ArrayList(runningYTDLInstances)
|
||||
if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
||||
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
|
||||
eligibleDownloads.forEach {downloadItem ->
|
||||
runningYTDLInstances.add(downloadItem.id)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
withContext(Dispatchers.Main){
|
||||
notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
notificationUtil.notify(downloadItem.id.toInt(), notification)
|
||||
}
|
||||
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
||||
downloadItem.status = DownloadRepository.Status.Active.toString()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
logRepo.update(line, logItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onSuccess {
|
||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
|
||||
runBlocking {
|
||||
var finalPaths : List<String>?
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
try {
|
||||
finalPaths = withContext(Dispatchers.IO){
|
||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
}
|
||||
//update item if its incomplete
|
||||
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
}
|
||||
|
||||
if (finalPaths.isNotEmpty()){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
|
||||
}else{
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
e.printStackTrace()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}, 1000)
|
||||
}
|
||||
val cacheDir = FileUtil.getCachePath()
|
||||
val tempFileDir = File(cacheDir, downloadItem.id.toString())
|
||||
tempFileDir.delete()
|
||||
tempFileDir.mkdirs()
|
||||
|
||||
val downloadLocation = downloadItem.downloadPath
|
||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||
|
||||
val logItem = LogItem(
|
||||
0,
|
||||
downloadItem.title.ifEmpty { downloadItem.url },
|
||||
"Downloading:\n" +
|
||||
"Title: ${downloadItem.title}\n" +
|
||||
"URL: ${downloadItem.url}\n" +
|
||||
"Type: ${downloadItem.type}\n" +
|
||||
if (downloadItem.type != DownloadViewModel.Type.command){
|
||||
"Format: ${downloadItem.format}\n\n"
|
||||
}
|
||||
else { "" } +
|
||||
"Command: ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
|
||||
downloadItem.format,
|
||||
downloadItem.type,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
|
||||
//put download in history
|
||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
if (!incognito) {
|
||||
val unixtime = System.currentTimeMillis() / 1000
|
||||
val file = File(finalPaths?.first()!!)
|
||||
downloadItem.format.filesize = file.length()
|
||||
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id)
|
||||
historyDao.insert(historyItem)
|
||||
}
|
||||
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
|
||||
if (wasQuickDownloaded){
|
||||
runCatching {
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id))
|
||||
if (logDownloads){
|
||||
runBlocking {
|
||||
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
|
||||
if (res != null) {
|
||||
resultDao.insert(res)
|
||||
logItem.id = logRepo.insert(logItem)
|
||||
downloadItem.logID = logItem.id
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
}
|
||||
runCatching {
|
||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
|
||||
val title: String = downloadItem.title
|
||||
notificationUtil.updateDownloadNotification(
|
||||
downloadItem.id.toInt(),
|
||||
line, progress.toInt(), 0, title,
|
||||
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||
)
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
logRepo.update(line, logItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onSuccess {
|
||||
runningYTDLInstances.remove(downloadItem.id)
|
||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
runBlocking {
|
||||
var finalPaths : List<String>?
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
try {
|
||||
finalPaths = withContext(Dispatchers.IO){
|
||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
}
|
||||
}
|
||||
|
||||
dao.delete(downloadItem.id)
|
||||
}
|
||||
if (finalPaths.isNotEmpty()){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
|
||||
}else{
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
e.printStackTrace()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
|
||||
}.onFailure {
|
||||
if (it is YoutubeDL.CanceledException) {
|
||||
return Result.success(
|
||||
Data.Builder().putString("output", "Download has been cancelled!").build()
|
||||
)
|
||||
}else{
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if(it.message != null){
|
||||
logRepo.update(it.message!!, logItem.id)
|
||||
//put download in history
|
||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
if (!incognito) {
|
||||
val unixtime = System.currentTimeMillis() / 1000
|
||||
val file = File(finalPaths?.first()!!)
|
||||
downloadItem.format.filesize = file.length()
|
||||
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id)
|
||||
historyDao.insert(historyItem)
|
||||
}
|
||||
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
|
||||
if (wasQuickDownloaded){
|
||||
runCatching {
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id))
|
||||
runBlocking {
|
||||
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
|
||||
if (res != null) {
|
||||
resultDao.insert(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dao.delete(downloadItem.id)
|
||||
|
||||
if (logDownloads){
|
||||
logRepo.update(it.out, logItem.id)
|
||||
}
|
||||
|
||||
if (items.size <= 1) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
}
|
||||
|
||||
}.onFailure {
|
||||
runningYTDLInstances.remove(downloadItem.id)
|
||||
withContext(Dispatchers.Main){
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
}
|
||||
if (it is YoutubeDL.CanceledException) {
|
||||
|
||||
}else{
|
||||
if (logDownloads){
|
||||
if(it.message != null){
|
||||
logRepo.update(it.message!!, logItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
tempFileDir.delete()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
|
||||
}, 1000)
|
||||
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
|
||||
downloadItem.status = DownloadRepository.Status.Error.toString()
|
||||
runBlocking {
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
notificationUtil.createDownloadErrored(
|
||||
downloadItem.title, it.message,
|
||||
if (logDownloads) logItem.id else null,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to it.toString(), "id" to downloadItem.id))
|
||||
}
|
||||
if (items.size <= 1) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
}
|
||||
}
|
||||
|
||||
tempFileDir.delete()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
|
||||
}, 1000)
|
||||
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
|
||||
downloadItem.status = DownloadRepository.Status.Error.toString()
|
||||
runBlocking {
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
notificationUtil.createDownloadErrored(
|
||||
downloadItem.title, it.message,
|
||||
if (logDownloads) logItem.id else null,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
|
||||
return Result.success(
|
||||
Data.Builder().putString("output", it.toString()).build()
|
||||
)
|
||||
}
|
||||
if (eligibleDownloads.isNotEmpty()){
|
||||
eligibleDownloads.forEach { it.status = DownloadRepository.Status.Active.toString() }
|
||||
dao.updateMultiple(eligibleDownloads)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
|
@ -242,7 +265,7 @@ class DownloadWorker(
|
|||
var wasQuickDownloaded = false
|
||||
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
||||
runCatching {
|
||||
if (isStopped) destroyYTDLProcess()
|
||||
if (isStopped) YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
|
||||
setProgressAsync(workDataOf("progress" to 0, "output" to context.getString(R.string.updating_download_data), "id" to downloadItem.id))
|
||||
val info = infoUtil.getMissingInfo(downloadItem.url)
|
||||
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
||||
|
|
@ -259,17 +282,10 @@ class DownloadWorker(
|
|||
return wasQuickDownloaded
|
||||
}
|
||||
|
||||
override fun onStopped() {
|
||||
destroyYTDLProcess()
|
||||
super.onStopped()
|
||||
}
|
||||
|
||||
private fun destroyYTDLProcess(){
|
||||
YoutubeDL.getInstance().destroyProcessById(itemId.toInt().toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
var itemId: Long = 0
|
||||
val runningYTDLInstances: MutableList<Long> = mutableListOf()
|
||||
const val TAG = "DownloadWorker"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import android.os.Looper
|
|||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
|
|
@ -21,6 +21,7 @@ import com.deniscerri.ytdlnis.database.repository.LogRepository
|
|||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
|
|
@ -29,17 +30,14 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.util.Calendar
|
||||
|
||||
|
||||
class TerminalDownloadWorker(
|
||||
private val context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : Worker(context, workerParams) {
|
||||
override fun doWork(): Result {
|
||||
val itemId = inputData.getInt("id", 0)
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
override suspend fun doWork(): Result {
|
||||
itemId = inputData.getInt("id", 0)
|
||||
val command = inputData.getString("command")
|
||||
if (itemId == 0) return Result.failure()
|
||||
if (command!!.isEmpty()) return Result.failure()
|
||||
|
|
@ -49,6 +47,7 @@ class TerminalDownloadWorker(
|
|||
|
||||
val notificationUtil = NotificationUtil(context)
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val infoUtil = InfoUtil(context)
|
||||
|
||||
val intent = Intent(context, TerminalActivity::class.java)
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
|
|
@ -59,27 +58,26 @@ class TerminalDownloadWorker(
|
|||
val request = YoutubeDLRequest(emptyList())
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
val downloadLocation = sharedPreferences.getString("command_path", context.getString(R.string.command_path))
|
||||
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + itemId)
|
||||
tempFileDir.delete()
|
||||
tempFileDir.mkdirs()
|
||||
val downloadLocation = sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
|
||||
|
||||
val outputFile = File(context.cacheDir.absolutePath + "/$itemId.txt")
|
||||
if (! outputFile.exists()) outputFile.createNewFile()
|
||||
|
||||
request.addOption(
|
||||
"--config-locations",
|
||||
File(context.cacheDir.absolutePath + "/downloads/config${System.currentTimeMillis()}.txt").apply {
|
||||
File(context.cacheDir.absolutePath + "/config${System.currentTimeMillis()}.txt").apply {
|
||||
writeText(command)
|
||||
}.absolutePath
|
||||
)
|
||||
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
request.addOption("-P", tempFileDir.absolutePath)
|
||||
request.addOption("-P", FileUtil.getDefaultCommandPath() + "/" + itemId)
|
||||
|
||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||
|
||||
|
|
@ -88,7 +86,7 @@ class TerminalDownloadWorker(
|
|||
"Terminal Download",
|
||||
"Downloading:\n" +
|
||||
"Terminal Download\n" +
|
||||
"Command: ${command}\n\n",
|
||||
"Command: ${infoUtil.parseYTDLRequestString(request)}\n\n",
|
||||
Format(),
|
||||
DownloadViewModel.Type.command,
|
||||
System.currentTimeMillis(),
|
||||
|
|
@ -119,7 +117,7 @@ class TerminalDownloadWorker(
|
|||
CoroutineScope(Dispatchers.IO).launch {
|
||||
//move file from internal to set download directory
|
||||
try {
|
||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation!!, false){ p ->
|
||||
FileUtil.moveFile(File(FileUtil.getDefaultCommandPath() + "/" + itemId),context, downloadLocation!!, false){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
|
|
@ -130,12 +128,10 @@ class TerminalDownloadWorker(
|
|||
}
|
||||
}
|
||||
|
||||
if (it.out.length > 200){
|
||||
outputFile.appendText("${it.out}\n")
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
logRepo.update(it.out, logItem.id)
|
||||
}
|
||||
outputFile.appendText("${it.out}\n")
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
logRepo.update(it.out, logItem.id)
|
||||
}
|
||||
}
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
|
|
@ -144,7 +140,7 @@ class TerminalDownloadWorker(
|
|||
if (it is YoutubeDL.CanceledException) {
|
||||
return Result.failure()
|
||||
}
|
||||
outputFile.appendText("${it.message}\n")
|
||||
outputFile.appendText("\n${it.message}\n")
|
||||
if (logDownloads){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (it.message != null){
|
||||
|
|
@ -152,7 +148,7 @@ class TerminalDownloadWorker(
|
|||
}
|
||||
}
|
||||
}
|
||||
tempFileDir.delete()
|
||||
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
|
||||
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
|
|
@ -163,12 +159,9 @@ class TerminalDownloadWorker(
|
|||
return Result.success()
|
||||
|
||||
}
|
||||
override fun onStopped() {
|
||||
YoutubeDL.getInstance().destroyProcessById(DownloadWorker.itemId.toInt().toString())
|
||||
super.onStopped()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var itemId : Int = 0
|
||||
const val TAG = "DownloadWorker"
|
||||
}
|
||||
|
||||
|
|
|
|||
5
app/src/main/res/drawable/baseline_close_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:textColorPrimary" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M23,16c0,1.1 -0.9,2 -2,2h-1l-2,-2h3L21,4L6,4L4,2h17c1.1,0 2,0.9 2,2v12zM17.5,18l-2,-2zM14.9,18l6,6 1.3,-1.3 -4.7,-4.7 -2,-2L1.2,1.8 0,3.1l1,1L1,16c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h0.9zM3,16L3,6.1l9.9,9.9L3,16z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M20,3H4C2.9,3 2,3.9 2,5v10c0,1.1 0.9,2 2,2h6v2H8v2h8v-2h-2v-2h6c1.1,0 2,-0.9 2,-2V5C22,3.9 21.1,3 20,3"/>
|
||||
</vector>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:textColorPrimary" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
||||
|
|
|
|||
10
app/src/main/res/drawable/terminal_icon_background.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="@color/icon_bg"
|
||||
android:pathData="M 0 0 H 192 V 192 H 0 V 0 Z" />
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/terminal_icon_foreground.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333">
|
||||
<group android:scaleX="0.58"
|
||||
android:scaleY="0.58"
|
||||
android:translateX="5.04"
|
||||
android:translateY="5.04">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z"/>
|
||||
</group>
|
||||
</vector>
|
||||
61
app/src/main/res/layout-land/activity_main.xml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/incognito_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/incognito"
|
||||
android:textStyle="bold"
|
||||
android:translationZ="20dp"
|
||||
android:visibility="gone"
|
||||
android:padding="5dp"
|
||||
android:background="?colorPrimaryContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/incognito_header">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/frame_layout"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/bottomNavigationView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<com.google.android.material.navigationrail.NavigationRailView
|
||||
android:id="@+id/bottomNavigationView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:labelVisibilityMode="selected"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:menu="@menu/bottom_nav_menu" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
312
app/src/main/res/layout-land/fragment_home.xml
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<com.google.android.material.search.SearchView
|
||||
android:id="@+id/search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_anchor="@id/search_bar">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/search_history_scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/providers"
|
||||
android:layout_margin="10dp"
|
||||
app:chipSpacingVertical="-10dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingStart="0dp"
|
||||
app:singleSelection="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="true" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
style="@style/Divider.Vertical"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/queries_constraint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/queries"
|
||||
android:layout_margin="10dp"
|
||||
app:chipSpacingVertical="-10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="false" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/init_search_query"
|
||||
style="?attr/materialIconButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
app:iconSize="20dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:icon="@drawable/ic_search"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<include
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="-10dp"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/link_you_copied"
|
||||
layout="@layout/search_suggestion_item"
|
||||
/>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/search_history_linear_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/search_suggestions_scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:id="@+id/search_suggestions_linear_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.search.SearchView>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/home_appbarlayout"
|
||||
app:liftOnScroll="true"
|
||||
android:background="@null"
|
||||
android:elevation="0dp"
|
||||
android:fitsSystemWindows="true"
|
||||
app:elevation="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
app:layout_scrollFlags="scroll"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/home_toolbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:title="@string/app_name" />
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/home_toolbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="2"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="250dp">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="250dp">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="250dp">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/home_fabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/download_all_coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:visibility="gone"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/download_all_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_down"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/download_selected_coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:visibility="gone"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/download_selected_fab"
|
||||
app:elevation="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
app:icon="@drawable/ic_down"
|
||||
android:text="@string/download"
|
||||
app:srcCompat="@drawable/ic_music"/>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
172
app/src/main/res/layout-land/result_card_details.xml
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:paddingBottom="20dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/frame_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintWidth_percent=".5"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:cardCornerRadius="0dp"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/info"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/video_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:resize_mode="fixed_height"
|
||||
app:useDefaultControls="true"
|
||||
app:use_controller="true" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/download_thumb"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_image"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:colorAccent"
|
||||
app:layout_constraintTop_toTopOf="@+id/frame_layout"
|
||||
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="17sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_music"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
android:singleLine="false"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_music"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/download_music"
|
||||
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
||||
android:layout_width="55dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_height="55dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
app:icon="@drawable/ic_music"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_video"
|
||||
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/download_video"
|
||||
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
||||
android:layout_width="55dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_height="55dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
app:icon="@drawable/ic_video"
|
||||
app:layout_constraintEnd_toEndOf="@id/frame_layout"
|
||||
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottom_sheet_link"
|
||||
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
android:textAlignment="textStart"
|
||||
android:textSize="15sp"
|
||||
app:icon="@drawable/ic_link"
|
||||
app:layout_constraintEnd_toStartOf="@+id/info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/download_music" />
|
||||
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/info"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintWidth_percent=".5"
|
||||
app:layout_constraintVertical_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/frame_layout"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/history_no_results"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/running"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:text="@string/running"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/running_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/queued"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:text="@string/download_queue"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/queued_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -157,11 +157,6 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="3"
|
||||
|
|
|
|||
|
|
@ -171,12 +171,6 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="2"
|
||||
|
|
|
|||
|
|
@ -156,11 +156,6 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="4"
|
||||
|
|
|
|||
126
app/src/main/res/layout/adjust_audio.xml
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<LinearLayout android:layout_width="match_parent"
|
||||
android:id="@+id/adjust"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:paddingTop="10dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/adjust_audio" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleLine="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/embed_thumb"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/embed_thumb"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/split_by_chapters"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/split_by_chapters"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/cut"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_cut"
|
||||
android:text="@string/cut"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleLine="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/sponsorblock_filters"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/sponsorblock"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_money"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filename_template"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/file_name_template"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_edit"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/extra_commands"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/use_extra_commands"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_terminal"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</LinearLayout>
|
||||
45
app/src/main/res/layout/adjust_command.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/adjust"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/adjust_templates" />
|
||||
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:selectionRequired="false"
|
||||
app:singleLine="false"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleSelection="false">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/newTemplate"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/new_template"
|
||||
app:chipIcon="@drawable/ic_plus" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/editSelected"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit_selected"
|
||||
app:chipIcon="@drawable/ic_edit" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
205
app/src/main/res/layout/adjust_video.xml
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/adjust"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="10dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:paddingTop="10dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="@string/adjust_video"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleLine="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/embed_subtitles"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/embed_subtitles" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/add_chapters"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/add_chapter" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/save_thumbnail"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/save_thumb" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/remove_audio"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/remove_audio"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/split_by_chapters"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/split_by_chapters"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/cut"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_cut"
|
||||
android:minWidth="30dp"
|
||||
android:text="@string/cut"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/sponsorblock_filters"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/sponsorblock"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_money"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filename_template"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/file_name_template"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_edit"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/save_subtitles"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/save_subs"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/subtitle_languages"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:checked="false"
|
||||
android:text="@string/subtitle_languages"/>
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/extra_commands"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/use_extra_commands"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_terminal"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</LinearLayout>
|
||||
|
|
@ -319,6 +319,16 @@
|
|||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/force_keyframes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:checked="true"
|
||||
android:textStyle="bold"
|
||||
android:textSize="15sp"
|
||||
android:text="@string/force_keyframes"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -183,7 +183,6 @@
|
|||
android:minHeight="0dp"
|
||||
android:padding="0dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:icon="@drawable/ic_video"
|
||||
app:iconTint="?attr/colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
|
|||
|
|
@ -92,11 +92,6 @@
|
|||
android:paddingTop="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
android:paddingBottom="70dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="14sp"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="2000" />
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@
|
|||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/format"
|
||||
android:textSize="14sp"
|
||||
android:textSize="17sp"
|
||||
android:layout_margin="20dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -22,22 +23,24 @@
|
|||
android:paddingHorizontal="10dp"
|
||||
android:weightSum="100"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
android:text="@string/format_id"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_id_value"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -51,25 +54,26 @@
|
|||
android:paddingHorizontal="10dp"
|
||||
android:weightSum="100"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
|
||||
android:text="@string/url"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_url_value"
|
||||
android:layout_width="0dp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:layout_weight="50"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_width="match_parent"
|
||||
android:gravity="end"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -83,22 +87,23 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:textSize="18sp"
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
|
||||
android:text="@string/container"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/container_value"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -112,22 +117,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:textSize="18sp"
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/codec"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codec_value"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -141,22 +146,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/file_size"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize_value"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -170,22 +175,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/quality"
|
||||
android:layout_weight="50"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_note_value"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -199,22 +204,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:text="FPS"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fps_value"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
@ -228,22 +233,22 @@
|
|||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/audio_samplerate"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginVertical="5dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/asr_value"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="50"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pause_resume"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -110,131 +110,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:paddingTop="10dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/adjust_audio" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleLine="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/embed_thumb"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/embed_thumb"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/split_by_chapters"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/split_by_chapters"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/cut"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_cut"
|
||||
android:text="@string/cut"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleLine="true"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/sponsorblock_filters"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/sponsorblock"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_money"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filename_template"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/file_name_template"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_edit"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/extra_commands"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/use_extra_commands"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_terminal"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/adjust_audio" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -69,50 +69,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/adjust_templates" />
|
||||
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:selectionRequired="false"
|
||||
app:singleLine="false"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleSelection="false">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/newTemplate"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/new_template"
|
||||
app:chipIcon="@drawable/ic_plus" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/editSelected"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit_selected"
|
||||
app:chipIcon="@drawable/ic_edit" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
<include layout="@layout/adjust_command" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -110,209 +110,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="10dp">
|
||||
|
||||
<TextView
|
||||
android:paddingTop="10dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="@string/adjust_video"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:chipSpacingVertical="0dp"
|
||||
app:singleLine="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/embed_subtitles"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/embed_subtitles" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/add_chapters"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/add_chapter" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/save_thumbnail"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/save_thumb" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/remove_audio"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/remove_audio"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/split_by_chapters"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/split_by_chapters"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/cut"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_cut"
|
||||
android:minWidth="30dp"
|
||||
android:text="@string/cut"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="true">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/sponsorblock_filters"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/sponsorblock"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_money"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filename_template"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/file_name_template"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_edit"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:scrollbars="none"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/save_subtitles"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/save_subs"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/subtitle_languages"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:checked="false"
|
||||
android:text="@string/subtitle_languages"/>
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/extra_commands"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/use_extra_commands"
|
||||
app:chipIconVisible="true"
|
||||
app:chipIcon="@drawable/ic_terminal"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</LinearLayout>
|
||||
<include layout="@layout/adjust_video" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -112,11 +112,6 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
|
||||
|
|
|
|||
|
|
@ -133,14 +133,8 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
|
|
@ -23,14 +24,7 @@
|
|||
android:id="@+id/download_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:fastScrollEnabled="true"
|
||||
android:scrollbars="none"
|
||||
android:paddingBottom="100dp"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -38,5 +32,5 @@
|
|||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
<FrameLayout
|
||||
android:id="@+id/download_card_view"
|
||||
android:layout_width="0dp"
|
||||
android:padding="10dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="3dp"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
|
|
@ -32,7 +33,7 @@
|
|||
android:clickable="false"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintHorizontal_weight="0.3"
|
||||
app:layout_constraintHorizontal_weight="0.4"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
@ -81,11 +82,9 @@
|
|||
android:maxLines="2"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:scrollbars="none"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -93,6 +92,22 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:scrollbars="none"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowRadius="2"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,22 +39,6 @@
|
|||
app:layout_constraintTop_toTopOf="@+id/frame_layout"
|
||||
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pause"
|
||||
android:visibility="gone"
|
||||
android:clickable="false"
|
||||
app:iconSize="50dp"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
app:iconTint="?android:colorAccent"
|
||||
app:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
android:layout_width="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:translationZ="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:orientation="vertical"
|
||||
|
|
@ -72,13 +56,13 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginVertical="10dp">
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
|
|
@ -93,14 +77,14 @@
|
|||
<TextView
|
||||
android:id="@+id/bottom_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
android:singleLine="false"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/nestedScrollView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_music"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -136,6 +120,7 @@
|
|||
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/nestedScrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
@ -150,12 +135,12 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/running"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:text="@string/running"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
|
@ -170,12 +155,12 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/queued"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="10dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:text="@string/download_queue"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/running_recycler" />
|
||||
|
||||
|
|
@ -189,13 +174,11 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/queued" />
|
||||
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottom_sheet_link"
|
||||
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
|
|
|
|||
|
|
@ -23,12 +23,37 @@
|
|||
app:menu="@menu/select_playlist_menu"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginHorizontal="30dp"
|
||||
android:layout_height="wrap_content" />
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/from_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:hint="@string/start">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/to_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/colon">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
</com.google.android.material.bottomappbar.BottomAppBar>
|
||||
|
|
@ -36,12 +61,10 @@
|
|||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/check_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/select_all"
|
||||
app:icon="@drawable/ic_select_all"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_anchor="@id/bottomAppBar"/>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
|
|
@ -56,72 +79,42 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="20dp">
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/select"
|
||||
android:textSize="20sp"
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottomsheet_ok"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:text="@string/ok"
|
||||
app:icon="@drawable/ic_check"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:elevation="0dp">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
app:menu="@menu/playlist_download_menu"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="6dp"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:navigationIcon="@drawable/baseline_close_24" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bottomsheet_ok"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appbar"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/from_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:hint="@string/start">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/to_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/colon">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
@ -136,11 +129,6 @@
|
|||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="80dp"
|
||||
app:fastScrollEnabled="true"
|
||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
|
||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
||||
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,6 @@
|
|||
android:icon="@drawable/ic_format"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/filename_template"
|
||||
android:title="@string/file_name_template"
|
||||
android:icon="@drawable/if_file_rename"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/folder"
|
||||
android:title="@string/directories"
|
||||
|
|
@ -29,5 +23,12 @@
|
|||
app:showAsAction="always"/>
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/more"
|
||||
android:title="@string/more"
|
||||
android:icon="@drawable/ic_more"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
|
||||
</menu>
|
||||
|
||||
|
|
|
|||
9
app/src/main/res/menu/playlist_download_menu.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:theme="@style/BaseTheme">
|
||||
|
||||
<item android:title="@string/download"
|
||||
app:showAsAction="always"
|
||||
android:id="@+id/downloadPlaylist"/>
|
||||
</menu>
|
||||
|
|
@ -9,12 +9,19 @@
|
|||
android:icon="@drawable/baseline_delete_24"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/up"
|
||||
android:icon="@drawable/ic_arrow_up"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="Move Up" />
|
||||
|
||||
<item
|
||||
android:id="@+id/download"
|
||||
android:title="@string/download"
|
||||
android:icon="@drawable/baseline_download_24"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/select_all"
|
||||
android:title="@string/select_all"
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -2,4 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background_dark"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -2,4 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background_light"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/terminal_icon.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/terminal_icon_background"/>
|
||||
<foreground android:drawable="@drawable/terminal_icon_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/terminal_icon_background"/>
|
||||
<foreground android:drawable="@drawable/terminal_icon_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/terminal_icon.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-hdpi/terminal_icon_round.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/terminal_icon.webp
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/terminal_icon_round.webp
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/terminal_icon.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/terminal_icon_round.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/terminal_icon.webp
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/terminal_icon_round.webp
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/terminal_icon.webp
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/terminal_icon_round.webp
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -9,7 +9,11 @@
|
|||
android:id="@+id/homeFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.HomeFragment"
|
||||
android:label="@string/app_name"
|
||||
tools:layout="@layout/fragment_home" />
|
||||
tools:layout="@layout/fragment_home" >
|
||||
<action
|
||||
android:id="@+id/action_homeFragment_to_resultCardDetailsDialog"
|
||||
app:destination="@id/resultCardDetailsDialog" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/historyFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.downloads.HistoryFragment"
|
||||
|
|
@ -80,4 +84,8 @@
|
|||
android:id="@+id/downloadLogFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.downloadLogs.DownloadLogFragment"
|
||||
android:label="DownloadLogFragment" />
|
||||
<fragment
|
||||
android:id="@+id/resultCardDetailsDialog"
|
||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog"
|
||||
android:label="ResultCardDetailsDialog" />
|
||||
</navigation>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string-array name="audio_containers">
|
||||
<item>@string/defaultValue</item>
|
||||
<item>mp3</item>
|
||||
|
|
@ -65,12 +65,14 @@
|
|||
<string-array name="format_ordering">
|
||||
<item>@string/file_size</item>
|
||||
<item>@string/container</item>
|
||||
<item>@string/codec</item>
|
||||
<item>ID</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="format_ordering_values">
|
||||
<item>filesize</item>
|
||||
<item>container</item>
|
||||
<item>codec</item>
|
||||
<item>id</item>
|
||||
</string-array>
|
||||
|
||||
|
|
@ -770,4 +772,22 @@
|
|||
<item>queue</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_codec">
|
||||
<item>@string/defaultValue</item>
|
||||
<item tools:ignore="Typos">AV1</item>
|
||||
<item>VP9</item>
|
||||
<item tools:ignore="Typos">AVC</item>
|
||||
<item>H265</item>
|
||||
<item>H264</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_codec_values">
|
||||
<item></item>
|
||||
<item>av0</item>
|
||||
<item>vp</item>
|
||||
<item>avc</item>
|
||||
<item>hev</item>
|
||||
<item>h264</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||